一、为什么要使用缓存
1、缩短服务响应时间,增加系统吞吐量,提高用户体验
2、减少数据库查询减少数据库压力,防止高并发压垮数据库导致整个服务不可用
二、什么样的数据适合写入缓存
经常查询且不频繁更新的数据,因为频繁更新的数据会增大数据库与缓存不一致的可能、造成脏数据,同时也增加了系统复杂性
三、更新数据库时如何处理缓存
推荐删除缓存。因为写请求大于读请求时,每次都更新缓存但很少有读会造成性能浪费,删除缓存只会造成一次缓存未命中,读请求在从数据库查询最新数据写入缓存就好了
读多写少且有高并发时:更新缓存。防止删除缓存时高并发导致请求都去数据库查询,进而引发缓存击穿。可以设置逻辑过期时间,缓存命中后发现已过期,查询数据库更新缓存并返回。如果重建缓存需要复杂计算耗时长,可以先把缓存中的旧数据返回,然后新起一个线程重建缓存
四、数据库与缓存不一致问题
1)、【先删除缓存,后更新数据库】发生数据不一致的情况如下:
- 线程A删除缓存
- 线程B查询数据,缓存中没有,查询数据库,拿到旧值
- 线程A更新数据库
- 线程B将拿到的旧值写入缓存
按照上述步骤就会发生数据不一致的情况,数据库里是最新值,而缓存里是旧值。产生这种情况的条件是:1要先于2执行
2)、【先更新数据库,后删除缓存】发生数据不一致的情况如下:
- 线程A查询数据,刚好缓存失效,缓存中没有
- 线程A查询数据库,拿到旧值
- 线程B更新数据库
- 线程B删除缓存
- 线程A把旧值写入缓存
同样, 按照上述步骤就会发生数据不一致的情况,数据库里是最新值,而缓存里是旧值。而产生这种情况的条件需要满足两点:
1、刚好缓存失效,2、4要先于5执行(想要让4先于5,肯定是3先于2,但更新数据库的时长小于查询时长发生的概率更小)
从发生概率上来说,【先更新数据库后删除缓存】产生数据不一致的情况发生概率更小。所以【先更新数据库后删除缓存】更优
无论哪种方式,发生数据库与缓存不一致的最终原因是每个线程对数据库和缓存的操作不是原子操作,导致当前线程在写数据库、写缓存的过程中有其他线程的介入。因此,要保证原子性就要加锁,现在都是多机器分布式部署,所以需要加分布式锁,当然加锁后性能方面也会有所下降
五、保证数据库与缓存的最终一致性
既然选择数据库和缓存双写,一定会存在数据不一致的情况,【先更新数据库后删除缓存】只能尽可能的减少这种情况的发生,但并不能完全避免,所以要保证最终一致性。
上面说的数据不一致都是因为数据库里是新值而缓存里被存放了旧值,所以我们可以把缓存中的旧值再删除一次,也就是延迟缓存双删,在写请求更新数据库并删除缓存后,延迟一段时间再一次删除缓存。
而这个延迟时间该如何设定呢?
根据造成脏数据的原因是由于读请求将读到的旧数据又写入缓存导致的,所以延迟时间为查询数据的时间+写入缓存的时间,要保证在读请求写入缓存后执行第二次删除
还可以使用阿里开源的canal订阅数据库binlog处理缓存同步。它提供了一种发布/ 订阅模式的同步机制,这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。值得注意的是,binlog需要手动打开,并且不会记录关于MySQL查询的命令和操作。