大家好,我是老三,今天又是被算法致郁的一天,写篇文章缓一缓。

这篇文章,我们来看看缓存一致性问题。

缓存一致性

我接下来会巴巴说一堆缓存一致性,但是——

作为一名暴躁老哥,我先把结论撂这了!

缓存和数据库的强一致性无法实现!

CAP理论了解一下,缓存适用的场景属于CAP中的AP,是非强一致性的场景。

那还扯个犊子的缓存一致性?洗洗睡吧。

BASE理论接着了解一下,强一致性保证不了,那只好委屈求全,尽量保证最终一致性呗。

最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

所以,我们追求的是尽可能保证缓存和数据库的最终一致性。

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

Cache Aside Pattern

在开始之前,我们先来科普一下缓存+数据库读写,最经典的Cache Aside Pattern。

  • 读取:先读取缓存,缓存里没有,读取数据库,然后返回响应,顺斌保存缓存

  • 更新:先更新数据库,然后删除缓存

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

  • 并发情况下更新缓存可能会带来种种问题,直接删除缓存更加稳妥。
  • 缓存更新在很多时候需要耗费资源,直接删除,用时再从数据库读取,写进缓存,更省性能。

一致性问题

那么我们采用这种先更新数据库,再删除缓存,可能会出现什么问题呢?

假如,我们更新数据库成功,接下来还没来删除缓存,或者删除缓存失败怎么办?

那么很明显,这时候其它线程进来读的就是脏数据。

那怎么解决呢?

解决方案

既然删除缓存失败会导致脏数据,那我们就想办法让它能删除成功呗。

消息队列重试机制

我们可以引入一个重试机制。

如果删除缓存失败,向消息队列发送消息,把删除失败的key放进去,消费消息队列,获取要删除的key,然后去重试删除。

但是,这么干,好好的业务,咱们又引入了消息队列,对现有的业务造成了入侵,复杂度又提升了。

监听binlog异步删除

其实还有另外一种办法,我们可以用一个服务(比如阿里的 canal)去监听数据库的binlog,获取需要操作的数据。

然后用另外一个服务获取订阅程序传来的信息,进行缓存删除操作。

这样一来,对我们本身的业务入侵就小了很多。

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

一致性问题

我们看一下,如果先删除缓存,再更新数据库可能会带来什么问题。

在并发情况下,先删除缓存,再更新数据库,此时数据库还未更新成功,这时候有其它线程进来了,读取缓存,缓存不存在,读取数据库,读取的是旧值,这时候,缓存不一致就发生了。

解决方案

延时双删

延时双删是什么意思呢?

就是在删除缓存,更新数据库之后,休眠一段时间后,再次删除缓存。

延时删除之后,就把缓存里缓存的旧值给删除了。

再有请求进来,就是读取数据库里的新值,再把新值保存进缓存。

当然,第二次删除也有失败的可能,怎么办呢?重试。那怎么重试呢?前面写了。

关于删除,还有一个兜底的方案——设置缓存过期时间,这样一来,哪怕缓存了脏数据,但是脏数据总有过期的时候,不至于一直不一致。

总结

我们来简单总结一下,首先对缓存的操作,删除优于更新,所以要删除,而不是更新。

删除缓存两种方式:

  • 先更新数据库,在删除缓存。缓存不一致的两种处理方式是消息队列重试机制binlog异步删除
  • 先删除缓存,再更新数据库。缓存不一致的处理方式是延时双删

当然,这些方案无疑都增加了系统的复杂度。

如果不是并发特别高的话,就没有必要过度设计。

简单的事情重复做,重复的事情认真做,认真的事情有创造性地做。

我是三分恶,一个努力学习中的程序员。

点赞关注不迷路,咱们下期见!


参考:

[1]. 缓存与数据库一致性问题深度剖析

[2]. 数据库和缓存一致性的几种实现方式,我们来聊聊?

[3]. 面试官:缓存一致性问题怎么解决?

[4]. 美团二面:Redis与MySQL双写一致性如何保证?

缓存一致性?get💡的更多相关文章

  1. 由一个bug引发的SQLite缓存一致性探索

    问题 我们在生产环境中使用SQLite时中发现建表报“table xxx already exists”错误,但DB文件中并没有该表.后面才发现这个是SQLite在实现过程中的一个bug,而这个bug ...

  2. Java的多线程机制系列:(二)缓存一致性和CAS

    一.总线锁定和缓存一致性 这是两个操作系统层面的概念.随着多核时代的到来,并发操作已经成了很正常的现象,操作系统必须要有一些机制和原语,以保证某些基本操作的原子性.首先处理器需要保证读一个字节或写一个 ...

  3. Web集群缓存一致性的思考

    共享cache+数据库实现缓存一致性: 1.1 memcache + mongo+定时器 1.1.1 memcache 优点:web集群共享数据 缺点:数据生命周期的不可预估性 1.1.2 mongo ...

  4. 缓存一致性(Cache Coherency)入门

    作者: Fabian “ryg” Giesen  来源: infoq 参考原文:http://fgiesen.wordpress.com/2014/07/07/cache-coherency/ 本文是 ...

  5. 缓存一致性(Cache Coherency)入门(转)

    参考原文:http://fgiesen.wordpress.com/2014/07/07/cache-coherency/ 本文是RAD Game Tools程序员Fabian “ryg” Giese ...

  6. 缓存一致性和跨服务器查询的数据异构解决方案canal

    当你的项目数据量上去了之后,通常会遇到两种情况,第一种情况应是最大可能的使用cache来对抗上层的高并发,第二种情况同样也是需要使用分库 分表对抗上层的高并发...逼逼逼起来容易,做起来并不那么乐观, ...

  7. 分布式缓存一致性hash算法理解

    今天阅读了一下大型网络技术架构这本苏中的分布式缓存一致性hash算法这一节,针对大型分布式系统来说,缓存在该系统中必不可少,分布式集群环境中,会出现添加缓存节点的需求,这样需要保障缓存服务器中对缓存的 ...

  8. 多线程之:MESI-CPU缓存一致性协议

    MESI(Modified Exclusive Shared Or Invalid)(也称为伊利诺斯协议,是因为该协议由伊利诺斯州立大学提出)是一种广泛使用的支持写回策略的缓存一致性协议,该协议被应用 ...

  9. 【并发编程】MESI--CPU缓存一致性协议

    原文:多线程之:MESI-CPU缓存一致性协议 概念 MESI(Modified Exclusive Shared Or Invalid)(也称为伊利诺斯协议,是因为该协议由伊利诺斯州立大学提出)是一 ...

  10. 并发研究之CPU缓存一致性协议(MESI)

    CPU缓存一致性协议MESI CPU高速缓存(Cache Memory) CPU为何要有高速缓存 CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU.这就造 ...

随机推荐

  1. 小白自学vue的第一天,加油!

    一.插值的操作 1.Mustache语法 也就是双大括号(翻译过来就是胡须的意思) 2.v-once指令 加了v-once数据不会随着数据的改变而改变,只会渲染一次 3.v-html指令 可以解析HT ...

  2. Python 列表解析式竟然支持异步?

    PEP原文:https://www.python.org/dev/peps/pep-0530 PEP标题:PEP 530 -- Asynchronous Comprehensions PEP作者:Yu ...

  3. C++ //构造函数调用规则 //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) //析构函数(空实现) //拷贝函数(值拷贝) //2.如果我们写了有参构造函数 编译器就不会提供默认构造函数 但是会提供拷贝构造函数 //3.如果我们写了拷贝函数 编译器就不再提供 默认 有参 构造函数

    //构造函数调用规则 #include <iostream> using namespace std; //1.创建一个类,C++编译器会给每个类添加至少3个函数 //默认构造(空实现) ...

  4. 数据结构与算法-排序(六)堆排序(Heap Sort)

    摘要 堆排序需要用到一种数据结构,大顶堆.大顶堆是一种二叉树结构,本质是父节点的数大于它的左右子节点的数,左右子节点的大小顺序不限制,也就是根节点是最大的值. 这里就是不断的将大顶堆的根节点的元素和尾 ...

  5. 算法竞赛中的常用JAVA API:PriorityQueue(优先队列)(转载)

    算法竞赛中的常用JAVA API:PriorityQueue(优先队列) PriorityQueue 翻译过来就是优先队列,本质是一个堆, 默认情况下堆顶每次都保留最小值,每插入一个元素,仍动态维护堆 ...

  6. 使用Cobertura做代码覆盖率测试

    经验总结:首先要把cobertura.jar包含ant的classpath路径中,其次要求它包含在测试用例的classpath中: 使用cobertura做代码覆盖率测试中出现的问题:覆盖率始终为0, ...

  7. 问题求解与程序设计(C重新回顾:个人版)一

    一.容易遗忘之转义字符 转义序列 含义 \n 换行 \t 水平制表 \\ 输出反斜杠 \a 响铃符 \'' 输出双引号 \' 输出单引号 \? 输出问号 \r 输出回车符(不换行,光标定位当前行的开始 ...

  8. NOIP 模拟 $33\; \rm Connect$

    题解 状压 \(\rm DP\). 从 \(1\) 到 \(n\) 一共只要一条路径,那么就是一条链,只要维护一个点集和当前链的末尾就行. 设 \(\rm dp_{i,j}\) 为 \(i\) 的点集 ...

  9. ReentrantReadWriteLock(读写锁)

    为了提高性能,java提供了读写锁, 读锁: 在读的地方使用读锁,可以多个线程同时读. 写锁: 在写的地方使用写锁,只要有一个线程在写,其他线程就必须等待 例子: public static Read ...

  10. sql 中的with 语句使用

    一直以来都很少使用sql中的with语句,但是看到了一篇文章中关于with的使用,它的确蛮好用,希望以后记得使用这个语句.一.with 的用法With alias_name as (select1)[ ...