Redis 中如何保证缓存与数据库的数据一致性?
Redis 中如何保证缓存与数据库的数据一致性?
回答重点
缓存和数据库的同步可以通过以下几种方式:
1)先更新缓存,再更新数据库
2)先更新数据库存,再更新缓存
3)先删除缓存,再更新数据库,后续等查询把数据库的数据回种到缓存中
4)先更新数据库,再删除缓存,后续等查询把数据库的数据回种到缓存中
5)缓存双删策略。更新数据库之前,删除一次缓存;更新完数据库后,再进行一次延迟删除
6)使用 Binlog 异步更新缓存,监听数据库的 Binlog 变化,通过异步方式更新 Redis 缓存
以上就是实现数据库与缓存一致性的六种方式,这里前面三种都不太推荐使用,后面三种的话其主要根据实际场景:
- 如果是要考虑实时一致性的话,先写 MySQL,再删除 Redis 应该是较为优的方案,虽然短期内数据可能不一致,不过其能尽量保证数据的一致性。
- 如果考虑最终一致性的话,推荐的是使用 binlog + 消息队列的方式,这个方案其有重试和顺序消费,能够最大限度地保证缓存与数据库的最终一致性。
扩展知识
我们以 MySQL 和 Redis 为主要实现的案例对上述几个方案再进行展开分析。
先更新缓存,再更新数据库
由于网络原因,请求顺序无法保证,可能出现先更新缓存的请求,后更新数据库,而后更新缓存的请求反而先更新了数据库,这样就出现了缓存数据为 20,数据库数据为 10,即数据不一致的情况。
先写数据库,再写缓存
这个问题上面其实一样,都是因为并发和网络问题导致的数据库与缓存不一致。
先删除缓存再写数据库
盘一下流程:
- 请求 A 先对缓存中的数据进行删除操作。
- 请求 B 这个时候来执行查询,发现缓存中数据为空,就去数据库进行查询并回写缓存。
- 这个时候请求 A 删除缓存中的数据之后,进行数据库数据的更新。
- 但此时请求 B 已经把从数据库查询到的原始数据回写缓存了,这个时候就出现了上图的情况,数据库中查询的值是 20,而缓存中的数据是 10 。
读操作获取到的数据是过时的数据,虽然写操作已经完成了,但是因为缓存被删除了,读操作就必须从数据库中读取到旧值,并不是最新的数据。
缓存双删(先删除缓存,再写数据库,然后过一段时间再删除缓存)
这个方案为了避免旧数据被回种,等待一段时间后再延迟删除缓存。
也可以使用消息队列、定时任务或者延迟任务等方式去实现延迟删除:
先写数据库,再删除缓存
先把数据库的信息修改了,然后再删除对应的缓存,然后在修改数据库期间,可以允许一定时间的缓存不一致,保证缓存的最终一致性。
不过这种模型也有一定的问题,如下图所示:
其主要原因在于有一个写操作,此时刚好缓存失效,又在同一时刻刚好有一个并发读请求过来,且回写缓存的请求晚于缓存删除,导致数据库与缓存的不一致。
从上面的表述可以知道,这个发生的概率比较低,一般而言业务上都会使用这个方案。
先写数据库,通过 Binlog ,异步更新缓存
先修改数据库,然后通过 Canal 监听数据库的 binlog 日志,记录数据库的修改信息,然后通过消息队列异步修改缓存的数据。
这里需要注意需要保证顺序消费,保证缓存中数据按顺序更新,然后再加上重试机制,避免因为网络问题导致更新失败。
强一致性补充
可以使用分布式读写锁实现强一致性。读写锁中读和读操作不互斥,读写互斥,写写互斥。
写操作流程:
- 获取写锁。
- 更新数据库。
- 删除缓存。
- 释放写锁。
读操作流程:
- 获取读锁。
- 查询缓存:如果命中缓存,释放读锁,返回结果。如果缓存未命中,读取数据库,并将数据更新到缓存。
- 释放读锁。