为什么是删除缓存值而不是更新缓存值?

数据一致性问题

  • 无论是先更新数据库还是后更新数据库都会造成数据不一致问题;
  • 例如:线程A先更新数据库数据为21,线程B并发的更新数据库数据为20,接着更新缓存为20。线程A继续更新缓存数据为21,造成不一致问题;后更新数据库同理

浪费缓存资源、造成缓存利用率不高的问题

  • 因为在每次数据发生变更的时候,都无脑更新缓存,但是缓存中的数据不一定会被马上读取,这样就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源;
  • 会造成缓存利用率不高同时也会浪费机器的性能,所以考虑到删除缓存。

Cache Asize(旁路缓存策略)

删除缓存值或者更新数据库失败而导致数据不一致

重试机制确保删除或者更新数据库成功操作;

消息队列实现:

  • 可以把要删除的缓存值或者要更新的数据库的值暂存到消息队列中。当应用没有能够成功地删除缓存值或者更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或者更新;
  • 如果能够成功地删除或者更新,我们就要把这些值从消息队列中去除,以免重复操作;所以也就保证了数据库和缓存的数据一致了;
  • 如果超过重试次数还没有成功,我们就需要向业务层发送报错信息; 

订阅变更日志实现:

  • 当一条数据发生修改时,MySQL就会产生一条变更日志(binlog),我们可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据去删除对应的缓存;
  • 订阅日志的中间件:阿里的canal。

删除缓存值或者更新数据库的时候其他线程的并发读操作,而导致数据不一致

先删缓存后更新数据库

可能存在的问题:
  • 假设线程A删除缓存值后,还没有来得及更新数据库(比如说有网络延迟),线程B就开始读取数据了,那么这个时候,线程B会发现缓存缺失,就只能去读取数据库;
  • 此时线程B是在缓存缺失的情况下读取的数据库,所以还会把旧值写入缓存,这时就可能会导致其他线程从缓存中读到旧值。
  • 等到线程B从数据库读取完数据、更新缓存后,线程A才开始更新数据库,此时缓存中的数据是旧值,而数据库中的是最新值,二者就不一致了。

延迟双删

  • 在线程A更新完数据库以后,可以让它sleep一小段,再进行一次缓存删除操作;
  • sleep一段时间段的目的就是让线程B能够完成从数据库中读取并把缺失的数据写入缓存中,然后线程A才进行再次删除;
  • 线程Asleep的时间需要大于线程B读取数据再写入缓存的时间。

伪代码


先更新数据库后删除缓存

  • 假设线程A删除了数据库中的值,但是还没来得及更新缓存,线程B就开始读取数据了,那么此时线程B就会直接从缓存中读取旧值,其他线程并发读取缓存也是同理都会读取到旧值
  • 但是一般线程A会很快删除缓存值,其他线程再次读取时,就会发生缓存缺失,从而到数据库中读取最新值。这样对业务的影响较小。

解决办法:

  • 需要在更新数据库的时候,先在redis缓存客户端暂存并发读请求,等数据库更新完、缓存值删除后,再读取数据,从而保证数据一致性;
  • 设置过期时间,保证最终的一致性。

总结

日常生产中建议:先更新数据库再删除缓存的方法

原因:

  • 先删除缓存值再更新数据库,有可能会导致请求因缓存缺失而访问数据库,给数据库带来压力
  • 如果业务应用中读取数据库和写缓存的时间不好估算,那么延迟双删中的等待时间就不好设置

图片:


参考: