缓存异常有四种类型:
- 缓存和数据库的数据不一致
- 缓存雪崩
- 缓存击穿
- 缓存穿透
一、如何保证缓存与数据库双写时的数据一致性?
背景:使用到缓存,无论是本地内存做缓存,还是Redis做缓存,都会存在数据同步问题,因为配置信息缓存在内存中,而内存无法感知在数据在数据库的修改,就会造成数据库中的数据和缓存中的数据不一致的问题
针对这种现象共有四种解决方案:
- 先更新数据库,后更新缓存
- 先更新缓存,后更新数据库
- 先删除缓存,后更新数据库
- 先更新数据库,后更新缓存
第一种和第二种没有人使用。第一种存在的问题是:并发更新数据库场景下,会将脏数据刷到缓存。第二种存在的问题是:如果先更新缓存成功,但是数据库更新失败,会造成数据不一致的问题。主要使用第三种和第四种。
1、先删除缓存,后更新数据库
此方案存在的问题:请求A(更新操作),请求B(查询操作)
- 请求A进行写操作,删除缓存
- 请求B进行查询操作,发现缓存不存在
- 请求B就去数据库种查询旧值
- 请求B将旧值写入缓存
- 请求A将新值写入数据库
上述会导致数据不一致的问题。如果不给缓存设置过期时间策略,该数据永远是脏数据
解决方案一:延时双删
public void write(String key,Object data){
Redis.delkey(key);
db.updateData(data);
Thread.sleep(1000);
Redis.delkey(key);
}
解决方案二:更新与读取操作进行异步串行化
异步串行化
一个数据变更的操作,先执行删除缓存,然后更新数据库,但是在更新还没有完成的时候,如果此时有一个新的请求过来,读到了空的缓存,那么就可以先将缓存更新的请求发送到队列里,此时会在队列中积压,排在刚才更新库的操作之后,然后同步等待缓存更新完du
读操作去重
多个读库更新缓存的请求串在同一个队列中是没有意义的,因此可以做过滤
2、先更新数据库,后更新缓存
会导致数据库更新成功了,但是缓存阶段出错了,没有删除成功,那么此时再读取缓存的时候每次都是错误的数据了。此时解决方案是利用消息队列进行删除的补偿
3、什么是缓存击穿?
是指某个热点的key失效,大量请求同时访问这个过期数据,就会造成大量请求读缓存没有读到数据,从而导致高并发访问数据库,引起数据库压力剧增
从两个方面解决:
- 考虑热点key不设置过期时间
- 互斥锁使访问有序
在缓存失效后,通过互斥锁或者队列来控制读数据和写缓存的线程数量,比如某个key只允许一个线程查询数据和写数据,其他线程等待
4、什么使缓存穿透?
指的是用户请求的数据在缓存中不存在,同时数据库中也不存在,如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间内大量的请求落在数据库上,造成数据库压力过大,甚至数据库承受不住而宕机崩溃
缓存穿透的关键在于在Redis中查不到key值,
解决方法:
- 将无效的key存放进Redis中,即缓存空对象
- 使用布隆过滤器
5、什么是缓存雪崩?
指的是大量请求缓存中大面积失效的缓存数据。
解决方案:
- 缓存数据设置随机的过期时间,防止同一时间大量数据集合失效。
- 集群,将数据分布在不同的缓存数据库中。
- 限流,通过加锁或队列来控制读 数据库写缓存的线程数量