分布式常见问题-如何保证数据库缓存一致性

140 阅读4分钟

数据库缓存一致性

原则

Redis 缓存和数据库一般采用最终一致性

  • 第一考虑缓存的一致性
  • 第二考虑并发一致性问题 多个线程操作一个key,要加锁
  • 第三考虑缓存的利用率

默认配置

一般会默认配置Redis缓存的过期时间,到期后会自动更新

有些场景会配置定时刷缓存的任务,例如每天晚上更新ip白名单场景

优点

  • 配置简单

不足

  • 如果更新 mysql 成功,更新 redis 却失败,就退化到了redis定时刷新;
  • 在高并发场景,业务 server 需要和 mysql,redis 同时进行连接。这样是损耗双倍的连接资源,容易造成连接数过多的问题

方案一 先刷DB再刷Redis

可能会有不一致,以及并发场景下会有问题

如何保证第二部成功

异步重试 - 重试太多还是失败,删除缓存

把操作缓存这一步,直接放到消息队列中,由消费者来操作缓存。

针对方案二的同步写 redis 进行优化,增加消息队列,将 redis 更新操作交给 kafka,由消息队列保证可靠性,再搭建一个消费服务,来异步更新 redis。

优点

  • 消息队列可以用一个句柄,很多消息队列客户端还支持本地缓存发送,有效解决了方案二连接数过多的问题;
  • 使用消息队列,实现了逻辑上的解耦;
  • 消息队列本身具有可靠性,通过手动提交等手段,可以至少一次消费到 redis。

不足

  • 依旧解决不了时序性问题,如果多台业务服务器分别处理针对同一行数据的两条请求,举个栗子,a = 1; a = 5;,如果 mysql 中是第一条先执行,而进入 kafka 的顺序是第二条先执行,那么数据就会产生不一致。
  • 引入了消息队列,同时要增加服务消费消息,成本较高。

方案二 删除缓存延迟双删

先删除缓存,在更新DB

存在并发问题

为了解决刷缓存期间,缓存去从库里取值,获取的是旧值的问题

在线程 A 删除缓存、更新完数据库之后,先「休眠一会」,再「删除」一次缓存。

为什么删除两次?

防止并发场景,被其他线程刷新

为什么延迟?

因为数据库有主从,redis去从库读,由于主还没有完全同步给从,导致redis读到旧的值

延迟多久呢?

延迟时间要大于「主从复制」的延迟时间

延迟时间要大于线程 B 读取数据库 + 写入缓存的时间

通常可能是1-5s

方案三 binLog

只需要关注数据库,拿 MySQL 举例,当一条数据发生修改时,MySQL 就会产生一条变更日志(Binlog),我们可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据,去删除对应的缓存。

  • 在 mysql 压力不大情况下,延迟较低;
  • 和业务完全解耦;
  • 解决了时序性问题。

缺点

  • 要单独搭建一个同步服务,并且引入 binlog 同步机制,成本较大。

推荐采用「先更新数据库,再删除缓存」方案,并配合「消息队列」或「订阅变更日志」的方式来做

总结

1、性能和一致性不能同时满足,为了性能考虑,通常会采用「最终一致性」的方案

2、掌握缓存和数据库一致性问题,核心问题有 3 点:缓存利用率、并发、缓存 + 数据库一起成功问题

3、失败场景下要保证一致性,常见手段就是「重试」,同步重试会影响吞吐量,所以通常会采用异步重试的方案

4、订阅变更日志的思想,本质是把权威数据源(例如 MySQL)当做 leader 副本,让其它异质系统(例如 Redis / Elasticsearch)成为它的 follower 副本,通过同步变更日志的方式,保证 leader 和 follower 之间保持一致

\