实施对象:memcache
设计理论:mutex
设计前提:大量并发访问并存在cache过期。大并发场合,当cache失效导致同时取不到cache,会同一时间访问db并回设cache,可能会给系统带来潜在的超负荷风险。
解决方案:
方案一:load db之前add一个mutex key ,mutex key add 成功再去取db; add 失败则sleep之后重读cache。这里mutex key其实就是锁,因为memcache是没有锁概念,加了锁就能保证一个进程去操作对一个数据对象的db取操作,从而降低了风险。但是为了防止出现死锁现象,mutex是有时间限制。伪代码如下:
If(memcache.get(key)==null){
If(memcache.add(mutex_key,1,mutex_timeout)===TRUE){
v=db.get(key);
memcache.set(key,v,timeout);
memcache.delete(mutex_key);
}else{
Sleep(50);
Retry();
}
}
解析: mutex设计理论其实就是一个防止并发访问,从而用强加锁的机制,先抢到锁的线程就直接进行数据处理“db.get-> memcache.set” ,其余,就根据mutex_key的状态进行等待。两个时间点很重要:一是mutex_key的时间,你需要评估数据从db获取然后重设到cache周期。比如3分钟,在3分钟内,任何memcahce.add操作都会失败,根据memcache的add的API定义,当前key存在add就会失败,从而实现了锁的概念,一旦存在就是加锁状态。
第二个时间点是,sleep时间,相对来说,这个时间是线程等待然后重新请求的时间,休眠状态不宜过长。
方案二:相对方案一,将cache做了一个辅助作用,它不在于memcache.get有value的时候,假设我们需要的cache数据的时间为timeout,那么我们同时设置一个timeout1为内部超时时间,其中,timeout1<timeout,当cache接近或是已经超时,我们延长cache时间TIME_OUT,目的是维持cache状态,同时启用新的任务从db读取数据并回设cache。
If(memcache.get(key)==null){
// 同方案一一样,这里就不代码补充
}else{
If(v.timeout<=now()){
If(memcache.add(mutex_key,1,mutex_timeout)===TRUE){
v.timeout+=3*60*1000; //单位是毫秒
memcache.set(key,v,KEY_TIMEOUT*2);
// 重新任务进行cache回设
v=db.get(key);
v.timeout=KEY_TIMEOUT;
memcache.set(key,v,TIME_OUT*2); //新cache替代原先的cache
memcache.delete(mutex_key);
}else{
Sleep(50);
Retry();
}
}
}
解析:方案二是在一的基础上,在cache尚未过期可以取到值的时候做了一个优化,就是评估时间接近过期前,延长cache过期时间,为的是在db数据回设cache的时间内,cache的连续性,在新数据载入缓存期间,原有缓存依然担任读取的重要责任。
相比之下,方案二在实施过程中将伪代码转向实际代码实现复杂度会提升很多,一般场合来说方案一代码足够,如果实在需要考虑在缓存过期前做一些cache提前更新,那么需要考虑设置一个内定时间限制,一旦超过这个时间限定就开