ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

Redis与数据库的双写一致性问题如何保证

2022-06-19 16:04:37  阅读:155  来源: 互联网

标签:缓存 删除 数据库 Redis 更新 重试 一致性 双写


在分布式系统中,一致性(Consistency)、可用性(Availability)以及分区容忍性(Partition tolerance)这个三个要素最多只能同时保证两者,而分区容忍性是基本要求,所以分布式数据系统就要在一致性和可用性之间取一个平衡。对于大部分应用,并不需要强一致性,通常会采取牺牲一致性换取高可用性。牺牲一致性,只是不再要求关系型数据库中的强一致性,只要系统能达到最终一致性即可,同时尽量对用户透明。事实上,数据库与缓存没办法做到绝对一致性,所以追求绝对一致性的业务场景,不适合引入缓存。

三种经典缓存更新模式

1. Cache Aside Pattern
旁路缓存模式,是最常用的模式。

读:先从缓存中读数据,若命中,直接返回;若未命中,从数据库中读数据并放到缓存中。
写:先更新数据库再删除缓存。

问题1:为什么先更新数据库,再删除缓存?
两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的。那先更新数据库再操作缓存就不会存在不一致吗?不是的,毕竟不是原子操作,比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,所以,会造成脏数据。理论上确实会出现,但实际可能性很小,因为这要求读在写之前发生,并且更新缓存读要在写之后,而写操作比读慢得多。

问题2:为什么是删除缓存,不是更新缓存?
删除缓存替代更新缓存不会出现读脏问题,假设两个线程都发起写操作,但是可能由于网络等原因,后写的线程先更新了缓存,先写的线程后更新,那就存在脏数据了。

2. Read/Write Through Pattern
读写穿透,服务端把缓存作为主要数据存储,但缓存和数据库是同步更新的。

Read Through:跟Cache Aside很像,区别是如果没命中,Cache Aside是由应用来将数据从数据库加载到缓存,而Read Through是由缓存自己来加载。
Write Through:当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由缓存自己更新数据库。

3. Write Behind Caching Pattern
异步缓存写入,只更新缓存,不直接更新数据库,通过批量异步的方式来更新数据库。这种方式下,缓存和数据库的一致性不强,而且可能会丢失。但是它适合频繁写的场景,MySQL的InnoDB Buffer Pool机制就使用到这种模式。

如何保证一致性

通常推荐使用先更新数据库 + 再删除缓存这种方案来操作数据库和缓存,但是如果删除缓存失败就会导致数据库和缓存不一致。那如何保证删除一定成功呢?重试 or 异步重试。

1. 重试

保证删除缓存成功是解决一致性的关键,所以失败后可以重试,但是重试也存在一些问题:重试次数设为多少比较合理?重试会一直占用线程资源,所以这种方案不太好。

2. 异步重试

方案一:把重试请求放到消息队列中,由专门的应用来进行重试,直到成功。或者直接把删除缓存这个操作放到消息队列中,消息队列保证消息成功投递,成功消费后才会删除消息,否则还会继续投递消息给消费者(符合重试场景)。
方案二:订阅数据库变更日志,再操作缓存。当一条数据发生改变时,MySQL就会产生一条binlog(变更日志),可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据,去删除对应的缓存。订阅变更日志,目前也有比较成熟的开源中间件,例如阿里的canal,可以使用canal将binlog日志采集发送到消息队列里面。

3. 缓存延迟双删

对于Redis读写分离 + 主从复制延迟,以及先删除缓存,再操作数据库这两种情况导致的数据不一致性,可以采用缓存延时双删策略(先删除缓存,再更新数据库,休眠一会,再次删除缓存)来解决,但是延迟时间设置多久才合适?第一种情况延迟时间要大于主从复制的延迟时间;第二种情况延迟时间要大于第二个线程读取数据+写入缓存的时间,这在实际场景中不好评估。

总结

  • 引入缓存后,就要考虑缓存和数据库一致性的问题,建议,先更新数据库,再删除缓存
  • 在【先更新数据库,再删除缓存】的方案下,为了保证两步都能执行成功,需要配合【消息队列】或【订阅变更日志】的方案来做,其本质是通过重试的方式保证数据一致性。
  • 在【先更新数据库,再删除缓存】方案下,【读写分离 + 主从延迟】也会导致缓存和数据库不一致,解次问题的方案是【延迟双删】,凭借经验发送【延迟消息】到队列中,延迟删除缓存,同时要也要控制主从库延迟,尽可能降低不一致发生的概率。

参考:
https://blog.csdn.net/yang31842/article/details/120300777
https://blog.csdn.net/weiwenhou/article/details/117170724?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1-117170724-blog-113881238.pc_relevant_blogantidownloadv1&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1-117170724-blog-113881238.pc_relevant_blogantidownloadv1&utm_relevant_index=1

标签:缓存,删除,数据库,Redis,更新,重试,一致性,双写
来源: https://www.cnblogs.com/lchen-java/p/16390610.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有