探讨缓存一致性问题

探讨缓存一致性问题

本文只探讨只读缓存,即只对缓存进行读取、写入、删除,不进行更新操作

前言

数据库的读写性能上限是比较低的,工程中经常在数据库前面加一层缓存,可能是Redis或者本地缓存。既然有缓存,那么不可避免的会遇到缓存一致性问题。

缓存一致性的概念

缓存一致是指:缓存有值,且值等于数据库中的值,如果缓存中的值不等于数据库中的值,则认为是不一致。

缓存的操作场景

一般操作缓存有两种场景:新增和更新(修改、删除)。

新增

操作流程:

  1. 向数据库写入新的数据
  2. 读取时,缓存中不存在,则进行回源并更新缓存,这时数据库和缓存是一致的
    探讨缓存一致性问题_第1张图片

更新

更新缓存的场景内其实还有两个细分场景,主要差别是更新的顺序不同。如下:

  1. 先更新数据库,后删缓存
  2. 先删缓存,后更新数据库
    探讨缓存一致性问题_第2张图片

问题以及解决方案

新增场景下不会有不一致的问题,因为读取是从数据库读取并写入到缓存中的,所以始终是一致的。
更新场景下会遇到两个问题:

  1. 单线程下的操作失败问题
  2. 并发情况下的顺序问题

单线程情况

因为更新是有两个操作步骤,即更新数据库和删除缓存,如果后续步骤失败了,那就会才造成数据不一致,例如先更新数据库但是删除缓存失败了,那么后续的请求会直接读取到缓存中的旧值;反而如果是先删除缓存,但是后续更新数据库失败了,影响倒是不大,后续的请求发现缓存不存在,会回源正确的数据。
探讨缓存一致性问题_第3张图片

解决方案

消息队列+重试

将更新操作生成消息,暂存在消息队列中,当删除缓存成功后,将消息从消息队列中丢弃,当删除失败时,执行失败策略,从消息队列中取出消息进行重试,重试超过一定次数则上报。
探讨缓存一致性问题_第4张图片

订阅binlog变更日志

创建一个更新服务订阅数据的binlog变更日志,收到数据库变更后删除缓存

并发情况

并发情况下需要对两个更新顺序分别分析

先删除缓存 后更新数据库

线程A删除缓存后,线程B进行读取,发现数据不存在,则进行回源,此时缓存值是旧的,最后线程A才将更新的新值写入数据库。
探讨缓存一致性问题_第5张图片

解决方案
  1. 设置缓存过期时间+延时删除
    设置缓存过期时间,这样数据过期后还可以再次拉取数据库进行更新,达到最终一致性。即使数据不一致,但造成的影响时间范围比较小,。或者使用延时队列在更新完数据库后再次进行删除缓存,这样即使在延时期间有其他线程读取了旧数据并更新缓存,后续也一定会再次进行更新。

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

线程A更新数据库后,还没删除缓存,这时线程B进行读取,读取到了缓存中的旧值。
探讨缓存一致性问题_第6张图片
或者如果数据库采用主从架构时,线程A更新完数据库并删除缓存后,线程B发现缓存无数据进而向从数据库拉取数据并写缓存,如果这时主数据库的数据还未同步到从数据库时,就会导致数据不一致。
探讨缓存一致性问题_第7张图片

解决方案
  1. 延时删除
    更新完数据库后延时一段时间再进行删除,避免主从数据库同步延迟造成的数据不一致
  2. 订阅数据库binlog进行删除
    可以订阅数据库的binlog,等从数据库全部同步完成后再删除缓存
  3. 加锁
    更新数据库+删除缓存合并成一个原子操作,进行加锁处理,线程A进行更新时,加锁,线程B判断有锁后,直接读取数据库数据进行返回,不再进行写缓存操作
    探讨缓存一致性问题_第8张图片

总结

探讨缓存一致性问题_第9张图片

你可能感兴趣的:(系统设计,缓存,数据一致性)