最近建了一个技术交流群,欢迎情投意合的同学参加,群里主要评论:共享事务处理计划、深度剖析面试题并解答作业中遇到的问题,一起也能为我供给写作的素材。
欢迎加Q:312519302,进群评论
前语
在作业中,大多数的体系都在运用缓存,那你有没有想过为什么要运用缓存?运用缓存后,数据与缓存的一致性怎样确保?
带着上面的问题,咱们一起探索。
咱们刚开端做一个项目的时分,刚起步,流量很小,直接读写数据库即可,功能不错,体系稳定,架构如下图:
随时时刻推移,体系运行一段时刻,老板说,要推行咱们的体系,给用户赋能,接着搞一波营销,流量激增,成果体系报警了,体系都快挂了,赶紧排查,发现功能瓶颈在数据库。
这样,在客户端恳求数据时,假如能在缓存中射中数据,那就查询缓存,不用在去查询数据库,从而减轻数据库的压力,提高服务器的功能。
那咱们的架构变为:
看起来十分美好,可是有个问提挡在咱们面前,假如数据库的数据有修正的时分,咱们是先更新数据库,仍是先更新缓存呢?
由于引进了缓存,那么在数据更新时,不仅要更新数据库,而且要更新缓存,这两个更新操作存在前后的问题:
- 先更新数据库,再更新缓存
- 先更新缓存,再更新数据库
想了想,为了确保数据是最新的数据,那咱们挑选 先更新数据库,再更新缓存, 一顿操作猛如虎,体系优化完成后,上线,数据库压力也下来,吞吐量得到显着的提高,心里那个激动啊,我TM真是牛逼,等着老板的嘉奖。
过了一段时刻后,有人反应,我修正了数据后,检查详情,发现数据没变,可是我修正数据的时分,提示我修正成功。擦,莫非是体系出bug了?
登录服务器排查,没发现有更新数据库、缓存的失利的信息,可是发现个问题,缓存是修正前的数据,数据是修正后的数据,那这种问题是怎样产生的呢?
进一步剖析,发现缓存与数据库不一致的原因是并发导致的
先更新数据库,再更新缓存
并发为什么能导致数据与缓存不一致呢,咱们接着剖析,我举个例子,用户A与用户B一起修正一条数据
执行过程:
- 用户A更新数据库为1
- 用户B更新数据库为2****
- 用户B更新缓存2
- 用户A更新缓存为1
从上面能够看出,数据库为2,缓存也应该为1,这就造成了缓存与数据库不一致的现象
那假如咱们先更新缓存,再更新数据库呢?
先更新缓存,再更新数据库
执行过程:
- 用户A更新缓存为1
- 用户B更新缓存2
- 用户B更新数据库为2
- 用户A更新数据库为1
从上面能够看出,数据库为1,缓存也应该为2,仍是造成了缓存与数据库不一致的现象
定论: 无论是先更新数据库,再更新缓存,仍是先更新缓存,再更新数据库,在并发访问修正一条数据的时分,都会呈现不一致的状况
想了想,那不更新缓存了,直接删去缓存。也便是数据库后更新后,直接删去缓存。 在读取数据的时分,检查缓存中有没有数据,没有数据查询数据库,然后再更新缓存。
这个战略有个姓名:Cache Aside 战略, 中文:旁路缓存战略
旁路缓存战略
Cache Aside(旁路缓存)战略是最常用的,运用程序直接与「数据库、缓存」交互,并负责对缓存的保护,该战略又能够细分为「读战略」和「写战略」。
写战略的过程:
- 先更新数据库中的数据,再删去缓存中的数据。
读战略的过程:
- 假如读取的数据射中了缓存,则直接返回数据;
- 假如读取的数据没有射中缓存,则从数据库中读取数据,然后将数据写入到缓存,而且返回给用户。
问题来了,那咱们是先更新数据库,再删去缓存仍是先删去缓存,再更新数据库?
先删去缓存,再更新数据库
执行过程:
- 用户A删去缓存
- 用户B查询缓存,缓存中没有数据
- 用户B查询数据库,得到数据
- 用户B更新缓存数据
- 用户A更新数据库为最新的值
从成果看,缓存的是旧值:1,数据库是最新值:2,在并发读+写的场景下,仍然数据库跟缓存不一致。
先更新数据库,再删去缓存
执行过程
- 用户A查询缓存,缓存未射中
- 用户A查询数据库,得到值为:1
- 用户B更新数据库,值更新为:2
- 用户B删去缓存
- 用户A,把查询到的值1,回写到缓存
从成果看,缓存的是旧值:1,数据库是最新值:2,在并发读+写的场景下,仍然数据库跟缓存不一致。
从理论上来剖析,先更新数据,再删去缓存,仍然存在数据库与缓存不一致的状况,但在实践中,呈现不一致的状况概率十分低。
由于缓存的写入速度远远大于写入数据库的速度,为了以防万一,再给缓存数据加一个过期时刻,假如真呈现不一致的状况,也最多在过期时刻的这个区间不一致,过期时刻到了,从头更新缓存,也能到达终究一致性
计划挑选
以下四种计划,咱们都剖析了,那咱们到底用哪一种呢?
- 先更新数据库,再更新缓存
- 先更新缓存,再更新数据库
- 先更新数据库,再删去缓存
- 先删去缓存,再更新数据库
先说我的观念,也是大厂的计划,强烈推荐:先更新数据库,再删去缓存
原因如下:
- 缓存的写入速度远远大于写入数据库的速度,呈现不一致的的概率很低
- 设置过期时刻,假如真呈现不一致的状况,过期时刻到期,从头改写缓存,能达到终究一致性
- 正常的状况,缓存与数据强一致性,也没那么高的要求,假如真要达到强一致性,体系吞吐量必要下降
题外话:对于一致性的处理计划,我对大厂的计划很感兴趣,想了解他他们是怎样处理的。当进去后,深化了解下,运用的计划也是:先更新数据库,再删去缓存 ,终究一致性,没必要强一致性,原因跟上面三条差不多。
这样是不是很完美了嘛,等等,这种计划还有没有问题,想想…………….,看下图:
更新数据库,删去缓存是两个操作,假如删去缓存失利了呢(这种概率尽管很低),那缓存中仍然是旧值,有没有什么计划处理这个问题呢?
上面的四种计划,无论是先操作数据库,仍是操作缓存,都存在这种问题
问题原因知道了,该怎样处理呢?有两种办法:
- 重试机制
- 订阅 MySQL binlog,再操作缓存
怎样确保两个操作都执行成功
重试机制
引进音讯中间件,比如:rabbitmq
- 假如运用删去缓存失利,发送音讯到mq,然后从音讯队列中从头读取数据,然后再次删去缓存,这个便是重试机制。当然,假如重试超过的必定次数,仍是没有成功,咱们就需要向事务层发送报错信息了。
- 假如删去缓存成功,就要把数据从音讯队列中移除,避免重复操作,否则就继续重试
订阅 MySQL binlog,再操作缓存
引进canal中间件
canal,译意为水道/管道/沟渠,主要用途是根据 MySQL 数据库增量日志解析,供给增量数据订阅和消费。
canal 作业原理
- canal 模仿 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
- MySQL master 收到 dump 恳求,开端推送 binary log 给 slave (即 canal )
- canal 解析 binary log 对象(原始为 byte 流)
所以,假如要想确保「先更新数据库,再删缓存」战略第二个操作能执行成功,咱们能够运用「音讯队列来重试缓存的删去」,或许「订阅 MySQL binlog 再操作缓存」,这两种办法有一个一起的特色,都是采用异步操作缓存,也或许存在短时刻的不一致。
总结
怎样确保数据库与缓存一致性?先更新数据库,再删缓存,缓存设置一个过期时刻, 这种计划能适用95%以上的场景。
不知道你们在实践作业中,你们的项目,用的那种计划,踩过哪些坑,欢迎留言一起议论。