Redis缓存问题二---热点缓存key、缓存与数据库双写不一致的概念以及解决方案
- 1、热点缓存key
- 1.1、什么是热点缓存key
- 1.2、解决方案---只允许一个线程重建缓存
- 2、缓存与数据库双写不一致
- 2.1、双写不一致
- 2.2、读写并发不一致
- 2.3、解决方案
- 2.4、注意
1、热点缓存key
1.1、什么是热点缓存key
比如说有一个商品平时无人问津,但似乎突然有一个网红来代购,然后在这时候,大量的粉丝就去购买这件商品,由于这个商品平时都没有放在缓存中的(因为平时访问的人很少),所以这时候,有大量的请求过来一边访问db层,一边对这个商品的key进行缓存重建,这个key就是热点缓存key。
1.2、解决方案—只允许一个线程重建缓存
我们可以利用互斥锁来解决,此方法只允许一个线程重建缓存, 其他线程等待重建缓存的线程执行完, 重新从缓存获取数据即可。伪代码如下:
String get(String key) {
// 从Redis中获取数据
String value = redis.get(key);
// 如果value为空, 则开始重构缓存
if (value == null) {
// 只允许一个线程重建缓存, 使用nx, 并设置过期时间ex
// 使用Redis的分布式锁
String mutexKey = "mutext:key:" + key;
if (redis.set(mutexKey, "1", "ex 180", "nx")) {
// 从数据源获取数据
value = db.get(key);
// 回写Redis, 并设置过期时间
redis.setex(key, timeout, value);
// 删除key_mutex
redis.delete(mutexKey);
}// 其他线程休息50毫秒后重试
else {
Thread.sleep(50);
get(key);
}
}
return value;
}
2、缓存与数据库双写不一致
2.1、双写不一致
看下图所示,线程一在写数据库与更新缓存之间卡顿了一下,然后线程2在线程1卡顿的这个空隙去写了数据库并刷新了缓存,然后线程2都已经执行完了,线程1又把脏数据更新到了缓存,造成了数据库与缓存不一致。
2.2、读写并发不一致
下面这个图与上面基本同理,都是先更新写操作的,由于中间网络问题或者什么问题造成更新数据推后,最后造成了脏数据的更新,导致数据库与缓存双写不一致。
2.3、解决方案
1、对于并发几率很小的数据(如个人维度的订单数据、用户数据等),这种几乎不用考虑这个问题,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
2、就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单,不涉及到库存等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。
3、如果不能容忍缓存数据不一致,可以通过加读写锁保证并发读写或写写的时候按顺序排好队,读读的时候相当于无锁。
4、或者加上同步队列,保证请求线程同步执行。
5、延迟双删,这个策略的意思是在写完一个数据之后,先删除缓存,然后再等上个几十毫秒之后,再删除缓存,这样虽然可以解决一部分线程的卡顿造成双写不一致,但是,所有的写请求执行时间编程,耗费性能更严重,不推荐使用。
6、也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。如下图所示:
2.4、注意
以上我们针对的都是读多写少的情况加入缓存提高性能,如果写多读多的情况又不能容忍缓存数据不一致,那就没必要加缓存了,可以直接操作数据库。放入缓存的数据应该是对实时性、一致性要求不是很高的数据。切记不要为了用缓存,同时又要保证绝对的一致性做大量的过度设计和控制,增加系统复杂性!