一止长渊

redis缓存数据一致性问题

N 人看过
字数:1.2k字 | 预计阅读时长:4分钟

redis 缓存数据一致性问题就是指 redis 中缓存的数据与数据库中对应的数据不一致
通常解决这种 redis 不一致问题有两种解决方式:

  • 双写模式
  • 失效模式

一、双写模式

在修改数据库后,查询最新数据更新缓存。
可能出现问题:脏数据问题,由于某个线程处于卡顿原因多并发下会存在 redis 脏数据(数据不是最新)的可能 。但是该脏数据只是临时的,经过一段时间好几次写之后,会恢复成最新的数据,可以保证最终一致性,但是不能保证强一致性
解决:双写模式是在写写并发之间产生问题,所以在写数据库之前需要先获取锁,保证写数据库和写缓存是一个原子性操作,并发修改时都需要首先来获取写锁
合理方案:根据对数据一致性的容忍度,不重要的数据例如物流到达时间只需要保证最终一致性即可,就没有必要加上读写锁,对于缓存数据加上过期时间总能更新为最新数据;但是对于一致性要求很高的数据,例如股票的实时价格,这种就需要保证强一致性,就需要并发写时加上读写锁。
截屏2021-04-26 09.46.51.png

二、失效模式

修改数据库之后,直接将缓存删除。
存在问题:仍然是脏数据问题,例如三个线程,第一个线程将 db 改成 1 后删除了缓存,第二个线程将 db 改成 2,但是延时比较高导致写数据库花了较长时间才删除缓存,在第二个线程写数据库时,第三个线程前来读缓存,但是由于缓存都删除了所以从数据库 db 读取,由于数据库默认隔离级别为 REPEATABLE READ(可重复读),所以在线程 2 事务尚未提交只能读取到线程 2 事务未开始前的数据为 1,然后第三个线程更改缓存时由于超时导致最终缓存中数据更新为 1,而数据库中数据则为 2,导致了不一致性问题。
解决:失效模式是读写并发问题,所以也可以加上读写锁来解决,但是会导致系统比较笨重
合理方案:对于经常修改的数据,实时性要求较高的,读的时候就直接读数据库而不是读缓存;对于不经常修改的数据则可以不考虑一致性问题,加上过期时间,就可以了
截屏2021-04-26 09.51.20.png

三、缓存一致性——解决方案

无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?
1、如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
2、如果是菜单,商品介绍等基础数据,也可以去使用 canal 订阅 binlog 的方式。
3、缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
4、害怕脏数据,通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心脏数据,允许临时脏数据可忽略);
总结:
我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
我们不应该过度设计,增加系统的复杂性。遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。

四、canal 中间件

Canal 中间件通过订阅数据库的操作日志 binlog,当业务代码更新数据库时,binlog 发生变化,Canal 就会同步订阅到操作日志变化,就会同步更新 redis 中间的数据,这样业务代码就无须关心 redis 缓存的问题。
此外 Canal 也可以解决推荐问题,通过订阅用户浏览记录表和商品信息表,来推荐给用户的商品列表。
截屏2021-04-26 10.25.45.png

五、谷粒商城的缓存一致性问题解决方案——失效模式

1、缓存的所有数据都有过期时间,数据过期下一次读数据时主动触发更新
2、读写数据时,加上读写锁,对于经常读经常写,加上读写锁肯定会有极大的影响,但是经常读偶尔写则不加锁通过失效模式就保证最终一致性

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。