灵魂拷问

  • 保证缓存和数据库的一致性很简单吗?

  • 有哪些方式能保证缓存和数据库的一致性呢?

  • 如果发生了缓存和数据库数据不一致的情况怎么办呢?

在上篇文章我们介绍了缓存的定义分类以及优缺点等,如果还没看的同学可以移步这里

听说你会缓存?

当我们的系统引入缓存组件之后,性能得到了大幅度提升,但是随之而来的是代码需要引入一定的复杂度,比如缓存的更新策略,写入策略,过期策略等,而其中最可能导致程序员加班的莫过于缓存和数据库的一致性问题了,既:缓存中的数据和数据库中的数据不一致。

一致性问题

说到一致性问题,这算是分布式系统中不可避免的一个痛点,或者说分布式系统天然就自带了数据一致性问题,虽然可以利用很多分布式事务解决方案来做到一致性,但是实际的系统架构设计中,我还是推崇避免分布式事务。缓存和数据库数据的一致性在产生原理上和分布式类似,其实可以把他们两个的关系看做是分布式系统中的两个操作节点。

凡是处于不同物理位置的两个操作,如果操作的是相同数据,都会遇到一致性问题

产生数据一致性问题的根本原因是对一个数据的多个操作过程,缓存和数据库数据的一致性也是这个原理,系统中最常见的操作流程是这样的:

  • 数据的请求首先查询缓存中是否存在该数据

  • 如果数据命中缓存(在缓存中存在)则直接返回数据,如果数据没有命中缓存(缓存中不存在),则去数据库中取数据

  • 从数据库中取回数据,然后把数据写入缓存

好图

从图中可以清楚的看到,对数据库的操作和对缓存的操作是两个不同阶段的操作,在任何一个操作过程中都会发生线程安全问题。比如说:

  • 当两个线程同时查询缓存的时候,可能会发生两个线程都没有命中缓存的问题

  • 如果两个线程都没有命中缓存就会发生同时查询数据库的问题

  • 接着就会发生两个线程同时回写缓存的问题

而这还不是最致命的,毕竟两个线程同时查询数据库,同时回写缓存数据在多数情况下缓存数据和数据库数据还能保持一致。最要命的是如果是两个线程都进行更新操作,最常见的更新过程是先更新数据库,然后更新缓存。下面就以最常见的用户积分场景为例,每个用户都有自己的积分,假如发生以下过程:

  • 线程A根据业务会把用户id为1的积分更新成100

  • 线程B根据业务会把用户id为1的积分更新成200

  • 在数据库层面,线程A和线程B肯定不存在并发情况,因为数据库用锁来保证了ACID(假如是mysql等关系型数据库),无论数据库中最终的值是100还是200,我们都假设正确。

  • 假设线程B在A之后更新数据库,则数据库中的值为200

  • 线程A和线程B在回写缓存过程中,很可能会发生线程A在线程B之后操作缓存的情况(因为网络调用存在不确定性),这个时候缓存内的值会被更新成100,发生了缓存和数据库不一致的情况

通过以上案例可见,解决缓存和数据库数据不一致的根本解决方案是需要把两个操作合并成逻辑上能保证事务的一个操作

两个操作看做一个操作

分布式锁

在平时开发中,利用分布式锁可能算是比较常见的解决方案了。利用分布式锁把缓存操作和数据库操作封装为逻辑上的一个操作可以保证数据的一致性,具体流程为:

  • 每个想要操作缓存和数据库的线程都必须先申请分布式锁

  • 如果成功获得锁,则进行数据库和缓存操作,操作完毕释放锁

  • 如果没有获得锁,根据不同业务可以选择阻塞等待或者轮训,或者直接返回的策略

image

利用分布式锁是解决分布式事务的一种方案,但是在一定程度上会降低系统的性能,而且分布式锁的设计要考虑到down机和死锁的意外情况,而最常见的分布式锁就是利用redis,但是也会有不少坑,具体可以参考之前的文章

redis做分布式锁可能不那么简单

删除缓存

相对于分布式锁的方案,而程序员实际中最喜欢使用的还是删除缓存的方式,在一个可能会发生不一致的场景下,我们会以数据库为主,在操作完数据库之后,不去更新缓存,而是删除缓存。这在一定意义上相当于只操作数据库,把需要维护的两个数据源变成了一个数据源。

image

这种方式要求必须先操作数据库,后操作缓存,不然的话发生不一致的几率会大很多。为什么这么说呢?因为就算是先操作数据库也会有发生不一致的几率,但是毕竟在整个操作过程中,删除缓存的操作只占整个流程时间的一小部分而已,而且我们可以利用缓存的过期时间来保证数据的最终一致性,所以在一些可以容忍数据短暂不一致的场景下可以采用这种方案的。

删除缓存方案带来的另外一个劣势是:如果同样的数据会被频繁更新,缓存会被频繁删除,当有读请求的时候又会被频繁的从数据库加载,所以这种方案适用于那种对缓存命中率不敏感的系统中。

单线程

发生缓存和数据库不一致的原因在于多个线程的同时操作,如果相同的数据始终只会有一个线程去操作,不一致的情况就会避免了,比如nodejs,可以充分利用nodejs单线程的优势。提到单线程不能不提一下Actor模型,actor模型在对于同样的对象上可以看做是单线程模式,具体有兴趣的同学可以查看之前的推文

分布式高并发下Actor模型如此优秀

单线程的模式基本上和分布式锁的方案类似,只不过单线程不需要锁就可以实现操作的顺序化,这也是单线程的优势所在。

其他方案

如果是以缓存为主呢?假如我们的应用程序只和缓存组件通信,至于持久化数据库由专门的程序负责,这样行不行呢?在理论上是可以的

image

不过这种方案需要考虑几个方面:

  • 数据从缓存持久化到数据采用什么样的解决方案,是同步进行还是异步进行呢?

  • 在新数据请求的时候,如果缓存不存在,要采用什么样的方式来填充数据

  • 如果缓存模块挂掉了该怎么办?

以缓存为主的方案的优势是数据优先进入IO速度快的设备,对于那些请求量大,但是可以容忍一定数据丢失的应用非常合适,比如应用log数据的收集系统,这种系统其中一个最大的特点就是可以容忍一定数据的丢失,但是并发的请求数会非常大。所以我们就可以利用缓存设备前置的方案来应对这种应用场景

谈了千百遍的缓存数据的一致性问题相关推荐

  1. 保证一致性吗_谈了千百遍的缓存数据一致性问题

    作者 | 菜v菜 出品 | 架构师修行之路(ID:jiagoushixiuxing) 头图 | CSDN 下载自东方IC 灵魂拷问 保证缓存和数据库的一致性很简单吗? 有哪些方式能保证缓存和数据库的一 ...

  2. 谈了千百遍的缓存数据一致性问题

    作者 | 菜v菜 出品 | 架构师修行之路(ID:jiagoushixiuxing) 头图 |  CSDN 下载自东方IC 灵魂拷问 保证缓存和数据库的一致性很简单吗? 有哪些方式能保证缓存和数据库的 ...

  3. 我待数据如初恋,数据虐我千百遍

    我待数据如初恋,数据虐我千百遍 1.几个基础概念 1.1表达式 1.2语句 1.3程序 1.4函数 2.标识符 2.1关键字 2.2标识符的构成 3.基本数据类型 3.1整数和小数 3.2 布尔值和空 ...

  4. 硬不硬你说了算!近 40 张图解被问千百遍的 TCP 三次握手和四次挥手面试题

    来自:小林coding 每日一句英语学习,每天进步一点点: 前言 不管面试 Java .C/C++.Python 等开发岗位, TCP 的知识点可以说是的必问的了. 任 TCP 虐我千百遍,我仍待 T ...

  5. 吊打面试官!近 40 张图解被问千百遍的 TCP 三次握手和四次挥手面试题

    作者 | 小林coding 来源 | 小林coding(ID:CodingLin) 不管面试 Java .C/C++.Python 等开发岗位, TCP 的知识点可以说是的必问的了. 任 TCP 虐我 ...

  6. djano 字段不重复_硬不硬你说了算!全图解被问千百遍的TCP三次握手和四次挥手面试题...

    前言 不管面试 Java .C/C++.Python 等开发岗位, TCP 的知识点可以说是的必问的了. 任 TCP 虐我千百遍,我仍待 TCP 如初恋. 遥想当年校招时常因 TCP 面试题被刷,真是 ...

  7. djano 字段不重复_硬不硬你说了算!近 40 张图解被问千百遍的 TCP 三次握手和四次挥手面试题...

    脚本之家 你与百万开发者在一起 本文经授权转自公众号 小林coding (ID:CodingLin) 如若转载请联系原公众号 前言 不管面试 Java .C/C++.Python 等开发岗位,TCP的 ...

  8. djano 字段不重复_硬不硬你说了算!近 40 张图解被问千百遍的 TCP 3 次握手和 4 次挥手面试题...

    (给程序员的那些事加星标) 作者:小林Coding (本文来自作者投稿) 前言 不管面试 Java .C/C++.Python 等开发岗位, TCP 的知识点可以说是的必问的了. 任 TCP 虐我千百 ...

  9. 那些曾虐我千百遍的计算机组成原理

    那些曾虐我千百遍的计算机组成原理 本笔记知识点来源于b站狂神说:聊汇编先导课,有兴趣的小伙伴点这里:狂神聊汇编先导课 文章目录 那些曾虐我千百遍的计算机组成原理 进制的本质 有符号数和无符号数的区别 ...

最新文章

  1. 千万不要把 bool 当成函数参数
  2. No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK
  3. Poj3261 Milk Patterns
  4. Kali Linux安装Remmina无法加载RDP插件
  5. python经纬度获取县名_利用 Python 批量获取县镇运输距离
  6. 地震勘探英文专业文章中的经典句子(1)
  7. php管理nginx虚拟主机shell脚本
  8. CUDA2.1-原理之索引与warp
  9. linux网络测速qerf,cywapp.net
  10. 【BERT实践】看不懂bert没关系,用起来so easy!
  11. JS实现各种页面的刷新
  12. ServletContextListener作用(转)
  13. 关于VC6.0一些常见问题和解决方案
  14. 对象转json忽略空参
  15. CorelDRAW 2022(CDR22)软件最新版本正式发布 新增功能详解
  16. 夜深人静写算法(十四)- 基数估计 (Cardinality Estimation)
  17. 好好学习:个人知识管理精进指南
  18. 【BZOJ 3653】谈笑风生
  19. 组织行为学笔记(3)——情绪和心情
  20. 【译学】数据分析手册学习01: 导言、学习目标、指导原则

热门文章

  1. 解决WDCP3环境gbk网站编码程序乱码问题
  2. DAS,NAS,SAN在数据库存储上的应用
  3. [正则表达式] 可以解析HTML/XHTML页面的所有元素和结构的Regular Expression![ZT]
  4. 免费的数字图书馆_不仅是书籍:您当地图书馆可能提供的所有免费数字资料
  5. springboot配置允许跨域访问
  6. Java(C#)基础差异-语法
  7. 分析拼多多的崛起【产品思维】
  8. vue vue-router vuex element-ui axios 写一个代理平台的学习笔记(十一)构思商品页面...
  9. Serv-U服务器的管理3
  10. angular-ui-tab-scroll