在高并发系统中,Redis通常作为缓存层,而MySQL等数据库作为持久化存储层。
如果设计不当,就会出现 缓存和数据库数据不一致 的问题。

常见场景:

用户修改数据
↓
数据库更新成功
↓
Redis仍然是旧数据

用户读取时就会出现 脏数据问题。

因此需要设计合理的 缓存一致性策略。


一、常见缓存模式

缓存与数据库通常有三种模式:

模式描述
Cache Aside应用程序控制缓存
Read Through缓存自动加载
Write Through同步写缓存

互联网系统最常用的是:

Cache Aside Pattern

也叫:

旁路缓存模式


二、Cache Aside 模式

读流程

请求
↓
查询Redis
↓
缓存命中 → 返回
↓
缓存未命中
↓
查询数据库
↓
写入Redis
↓
返回数据

示例代码逻辑:

```java
User user = redis.get(key);

if(user == null){
user = db.query(id);
redis.set(key, user);
}

return user;


写流程

写数据时:

更新数据库
↓
删除缓存

示例:

update user set name = 'Tom' where id = 1

redis.del(user:1)

这样下一次读取时:

重新加载数据库数据


三、为什么不先删除缓存再更新数据库?

如果顺序是:

删除缓存
↓
更新数据库

可能出现:

线程A 删除缓存
线程B 查询数据库
线程B 写入旧缓存
线程A 更新数据库

最终:

缓存 = 旧数据
数据库 = 新数据

出现 数据不一致。


四、正确写法

推荐顺序:

先更新数据库
再删除缓存

流程:

更新数据库
↓
删除Redis缓存

这样即使并发出现:

短暂不一致

缓存很快会被刷新。


五、延迟双删策略

在高并发情况下可能仍存在极小概率问题。

解决方案:

延迟双删

流程:

更新数据库
↓
删除缓存
↓
sleep 500ms
↓
再次删除缓存

示例:

redis.del(key);

Thread.sleep(500);

redis.del(key);

这样可以防止:

旧数据重新写入缓存


六、使用消息队列保证一致性

在大型系统中,通常会使用 MQ异步删除缓存。

流程:

更新数据库
↓
发送MQ消息
↓
消费者删除缓存

架构:

业务服务
↓
数据库
↓
MQ
↓
缓存服务
↓
Redis

优点:

  • 解耦
  • 高可靠
  • 可重试

七、订阅数据库 Binlog

大型互联网系统会使用 Binlog同步缓存。

流程:

数据库更新
↓
Binlog
↓
Canal
↓
更新Redis

架构:

MySQL
↓
Binlog
↓
Canal
↓
缓存更新服务
↓
Redis

优点:

  • 自动同步
  • 数据一致性更强

八、设置缓存过期时间

为了避免长期不一致:

缓存必须设置TTL

例如:

TTL = 5分钟

即使出现问题:

缓存最终会过期

保证 最终一致性。


九、最终一致性模型

互联网系统通常采用:

最终一致性

而不是:

强一致性

原因:

  • 强一致性性能低
  • 分布式系统复杂

最终一致性意味着:

短时间可能不一致
但最终数据会一致


十、最佳实践总结

Redis缓存与数据库一致性推荐方案:

Cache Aside Pattern
更新数据库
删除缓存

增强方案:

延迟双删
MQ删除缓存
Binlog同步
设置TTL

完整流程:

更新数据库
↓
删除缓存
↓
MQ通知
↓
延迟删除缓存


十一、面试高分回答总结

如果面试官问:

Redis缓存和数据库如何保证一致性?

可以这样回答:

通常采用 Cache Aside 模式,读数据时先查缓存,缓存未命中再查数据库并写入缓存;写数据时先更新数据库再删除缓存。同时可以通过延迟双删、消息队列异步删除缓存、订阅数据库Binlog等方式进一步提高缓存和数据库的一致性,整体保证最终一致性。