前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据库缓存一致性问题

数据库缓存一致性问题

作者头像
大忽悠爱学习
发布2023-03-06 08:45:45
4030
发布2023-03-06 08:45:45
举报
文章被收录于专栏:c++与qt学习

数据库缓存一致性问题


问题: 更新数据时是先删除缓存还是先更新数据库?

  • 先删除缓存再更新数据库

问题: 两个并发操作,一个更新操作,一个查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把旧的数据读出来放到缓存中,然后更新了数据库,于是缓存中的数据还是老的数据。

  • 先更新数据库,再删除缓存

大家可以思考一下,这样就完全没有一点漏洞存在了吗?


缓存更新的常见Design Pattern

Cache Aside Pattern(旁路缓存模式)

流程:

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从cache中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

简单概括: 先更新数据库,再删除缓存。

存在的漏洞: 两个并发线程,一个读,一个写,读线程发现缓存失效,去数据库查询数据,查询完后更新redis; 但是更新redis前,写线程率先完成了写入操作,导致读线程最终放入redis的还是旧数据。

不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。


Read/Write Through Pattern(读写穿透模式)

在上面的Cache Aside套路中,我们的应用代码需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository)。所以,应用程序比较啰嗦。而Read/Write Through套路是把更新数据库(Repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。

  • Read Through

Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。

  • Write Through

Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)

对于读写缓存穿透模式而言,我们直接面向缓存代理进行数据存储管理开发,而由缓存代理帮我们实现缓存和数据库的一致性。


Write Behind Caching Pattern(异步缓存写入模式)

Write Behind 又叫 Write Back。一些了解Linux操作系统内核的同学对write back应该非常熟悉,这不就是Linux文件系统的Page Cache的算法吗?是的,你看基础这玩意全都是相通的。所以,基础很重要,我已经不是一次说过基础很重要这事了。

Write Back套路,一句说就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比(因为直接操作内存嘛 ),因为异步,write backg还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。

但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道Unix/Linux非正常关机会导致数据丢失,就是因为这个事)。在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间,空间换时间一个道理,有时候,强一致性和高性能,高可用和高性性是有冲突的。软件设计从来都是取舍Trade-Off。

另外,Write Back实现逻辑比较复杂,因为他需要track有哪数据是被更新了的,需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫lazy write。

在wikipedia上有一张write back的流程图,基本逻辑如下:


补充

为什么不能在更新完数据库后更新缓存呢?

1.线程A先发起一个写操作,第一步先更新数据库 2.线程B再发起一个写操作,第二步更新了数据库 3.由于网络等原因,线程B先更新了缓存 4.线程A更新缓存。

这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了,脏数据出现啦。如果是删除缓存取代更新缓存则不会出现这个脏数据问题。


缓存双删

有些小伙伴可能会说,不一定要先操作数据库呀,采用缓存延时双删策略就好啦?什么是延时双删呢?

1.先删除缓存 2.再更新数据库 3.休眠一会(比如1秒),再次删除缓存。

这个休眠一会,一般多久呢?都是1秒?

  • 这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒。 为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。

删除缓存重试机制

不管是延时双删还是Cache-Aside的先操作数据库再删除缓存,如果第二步的删除缓存失败呢,删除失败会导致脏数据哦~

删除失败就多删除几次呀,保证删除缓存成功呀~ 所以可以引入删除缓存重试机制

1.写请求更新数据库 2.缓存因为某些原因,删除失败 3.把删除失败的key放到消息队列 4.消费消息队列的消息,获取要删除的key 5.重试删除缓存操作


读取biglog异步删除缓存

重试删除缓存机制还可以,就是会造成好多业务代码入侵。其实,还可以通过数据库的binlog来异步淘汰key。

以mysql为例 可以使用阿里的canal将binlog日志采集发送到MQ队列里面,然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据库缓存一致性问题
  • 问题: 更新数据时是先删除缓存还是先更新数据库?
  • 缓存更新的常见Design Pattern
    • Cache Aside Pattern(旁路缓存模式)
      • Read/Write Through Pattern(读写穿透模式)
        • Write Behind Caching Pattern(异步缓存写入模式)
        • 补充
          • 为什么不能在更新完数据库后更新缓存呢?
            • 缓存双删
              • 删除缓存重试机制
                • 读取biglog异步删除缓存
                相关产品与服务
                云数据库 Redis®
                腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档