缓存击穿:
查询一个数据库中不存在的数据,比如商品详情,查询一个不存在的id,每次都会访问db,如果有人恶意破坏,很可能直接对db造成过大地压力。
ag真人游戏的解决方案: 当通过某一个key去查询数据的时候,如果对应在数据库中的数据都不存在,我们将此key对应的value设置为一个默认的值,比如“null”,并设置一个缓存的失效时间,这时在缓存失效之前,所有通过此key的访问都被缓存挡住了。后面如果此key对应的数据在db中存在时,更新缓存中该key的信息,通过此key再去访问数据,就能拿到新的value了。
缓存失效:
在高并发的环境下,如果此时key对应的缓存失效,此时有多个进程就会去同时去查询db,然后再去同时设置缓存。这个时候如果这个key是系统中的热点key或者同时失效的数量比较多时,db访问量会瞬间增大,造成过大的压力。
ag真人游戏的解决方案:
①:将系统中key的缓存失效时间均匀地错开,防止统一时间点有大量的key对应的缓存失效;
②:当我们通过key去查询数据时,首先查询缓存,如果此时缓存中查询不到,就通过分布式锁进行加锁,取得锁的进程查db并设置缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回缓存数据或者再次查询db。
使用互斥锁(mutex key): 这种ag真人游戏的解决方案思路比较简单,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了(如下图)
如果是单机,可以用synchronized或者lock来处理,如果是分布式环境可以用分布式锁就可以了(分布式锁,可以用memcache的add, redis的setnx, zookeeper的添加节点操作)。
redis代码:
string get(string key) {
string value = redis.get(key);
if (value == null) {
if (redis.setnx(key_mutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(key_mutex, 3 * 60)
value = db.get(key);
redis.set(key, value);
redis.delete(key_mutex);
} else {
//其他线程休息50毫秒后重试
thread.sleep(50);
get(key);
}
}
}
热点key:
缓存中的某些key(可能对应用与某个促销商品)对应的value存储在集群中一台机器,使得所有流量涌向同一机器,成为系统的瓶颈,该问题的挑战在于它无法通过增加机器容量来解决。
ag真人游戏的解决方案:
①:客户端热点key缓存:将热点key对应value并缓存在客户端本地,并且设置一个失效时间。对于每次读请求,将首先检查key是否存在于本地缓存中,如果存在则直接返回,如果不存在再去访问分布式缓存的机器。
②:将热点key分散为多个子key,然后存储到缓存集群的不同机器上,这些子key对应的value都和热点key是一样的。当通过热点key去查询数据时,通过某种hash算法随机选择一个子key,然后再去访问缓存机器,将热点分散到了多个子key上。
永不过期:热点key最好能永不过期。
这里的“永远不过期”包含两层意思:
(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
为每个 value 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。
从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。
string get(final string key) {
v v = redis.get(key);
string value = v.getvalue();
long timeout = v.gettimeout();
if (v.timeout <= system.currenttimemillis()) {
// 异步更新后台异常执行
threadpool.execute(new runnable() {
public void run() {
string keymutex = "mutex:" key;
if (redis.setnx(keymutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(keymutex, 3 * 60);
string dbvalue = db.get(key);
redis.set(key, dbvalue);
redis.delete(keymutex);
}
}
});
}
return value;
}