Java教程

数据库和缓存如何保持一致性

本文主要是介绍数据库和缓存如何保持一致性,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

数据库和缓存如何保持一致性

问题引入

大量的访问请求使得数据库操作频繁,结果导致服务器性能下降,为了解决该问题可以引入reids,让其作为数据库的缓存。这样,在客户端请求数据时,能从缓存中读取就可以不必去数据库中读取,从而减轻数据库压力,提高服务器性能。但是如果数据发生变化,而数据又存在于数据库和redis中,此时就会产生数据一致性问题。

方案一:先更新数据库,再更新缓存

首先结论是该方案不能解决数据一致性问题,原因出在并发上。比如现在有 A 请求和 B 请求,这两个请求同时更新同一条数据,此时如果出现如下顺序:

  1. A 请求先将数据库数据更新为 1
  2. B 请求将数据库数据更新为 2
  3. B 请求将缓存更新为 2
  4. A 请求将缓存更新为 1

此时,数据库中的数据为 2,而缓存中数据为 1,出现数据不一致的问题,所以该方案不成立。

方案二: 先更新缓存,再更新数据库

该方案依然不成立,道理如方案一种的案例一样,只不过将数据库和缓存换一下位置,最后依然会出现数据不一致。

旁路缓存策略(Cache Aside)

不更新缓存,而是删除缓存中的数据,然后,到读取数据时,发现缓存中没有数据后再去数据库中读取数据并更新到缓存中。

此时又引申出一个问题,是先删除缓存再更新数据库还是先更新数据库再删除缓存。

方案三: 先删除缓存,再更新数据库

结论是改方案不成立。假设有一用户年龄为20, A 请求更新该用户年龄为21,B 请求读取该用户年龄,此时出现如下顺序:

  1. A 请求删除缓存
  2. B 请求缓存未命中,读取数据库中年龄为 20,并将 20 更新到缓存中
  3. A 请求更新数据库中年龄为 21

此时,数据库中年龄为 21,而缓存中数据为 20,出现数据不一致问题。对于此情况有如下解决方案:

# 删除缓存
redis.delKey(X)
# 更新数据库
db.update(X)
# 睡眠
Thread.sleep(N)
# 再删除缓存
redis.delKey(X)

睡眠操作主要是为了确保 A 请求在睡眠的时候, B 请求能够在这一段时间完成所有操作。最后再删掉缓存数据,所以 A 请求的睡眠时间要大于 B 请求从数据库读数据+写入缓存的时间。但是具体睡眠多久是个玄学问题,很难评估出来,所以这个方案也只是尽可能保证数据一致性而已,还是会有数据不一致的问题,因此更建议使用方案四。

方案四: 先更新数据库,再删除缓存

假设开始年龄数据在缓存中不存在,A 请求读取年龄,B 请求更新年龄,此时出现如下顺序:

  1. A 请求缓存未命中,读取数据库中年龄为 20
  2. B 请求更新数据库年龄为 21
  3. B 请求删除缓存
  4. A 请求将 20 写入缓存

此时,缓存中数据为 20, 而数据库中数据为 21,出现数据不一致问题。但是在实际中,这个问题出现的概率并不高,因为缓存的写入通常要远远快于数据库的写入,所以认为该方案是可以保证数据一致性的。

同时也可以给缓存加上过期时间,万一出现数据不一致,一旦数据过期被移除缓存,之后请求数据就需要从数据库中取数据,然后把数据同步到缓存中进而保持数据一致性。

但是此时还存在其他问题,即删除缓存的操作不一定成功,这就会导致客户端得到的是未更新的数据。现在有如下两种方案解决这个问题。

1. 重试机制

引入消息队列,将要从缓存中删除的数据加入到消息队列中。

  • 如果应用删除缓存失败,就从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。如果重试超过一定次数还是没有成功,就需要向业务层发送报错信息了。
  • 如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作。

2. 订阅 MySQL binlog,再操作缓存

在数据库更新成功后就会产生一条变更日志,记录在 binlog 中。于是可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除。阿里巴巴的 Canal 中间件就是基于这个实现。

Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 从节点,向 MySQL 主节点发送 dump 请求,MySQL 收到请求后就会开始推送 binlog 给 Canal,Canal 解析 binlog 字节流后,转换为便于读取的结构化数据,供下游订阅使用。

补充

如果在业务中对缓存命中率有很高的要求,此时就需要更新数据库+更新缓存的方案。为了解决数据不一致问题,现有提供如下两种做法:

  1. 在更新缓存前先加个分布式锁,保证同一时间只有一个请求更新数据库和缓存,就不会产生并发问题了,但引入锁会对写入性能带来影响。
  2. 在更新完缓存时,给缓存加上较短的过期时间,这样即使出现缓存不一致问题,缓存的数据也会很快过期,对业务还是可以接受的
这篇关于数据库和缓存如何保持一致性的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!