1. 核心组件与协作流程
1.1 整体架构
+-----------------+ +----------------+ +-----------------+ +-----------------+
| Application | | Redis | | MySQL | | MQ/Binlog |
| (Cache-Aside) |<----->| (缓存层) |<----->| (数据库) |<----->| (Canal/Kafka) |
+-----------------+ +----------------+ +-----------------+ +-----------------+
| 读/写请求 | 缓存操作 | 数据持久化 | 监听数据变更
| | | |
v v v v
读流程:优先查缓存 延迟双删策略 数据更新操作 异步更新/删除缓存
写流程:先DB后缓存 异步重试机制 Binlog日志 消息队列保证可靠性
2. 分步骤实现原理
2.1 写操作流程(应用主动更新)
-
第一步:删除缓存(第一次删除)
-
应用在更新数据库前,先删除Redis中的缓存数据。
-
目的:防止后续并发读请求命中旧缓存。
-
-
第二步:更新数据库
-
执行MySQL的更新操作(如
UPDATE
语句)。
-
-
第三步:延迟双删(第二次删除)
-
在数据库更新完成后,延迟一段时间(如500ms),再次删除缓存。
-
原理:
-
延迟时间需覆盖“其他线程可能读取旧数据并回填缓存”的时间窗口。
-
例如:线程A更新DB后,线程B可能在更新期间读取旧数据并写入缓存,延迟双删可清除残留旧数据。
-
-
-
异步重试机制
-
若第一次或第二次删除缓存失败,将删除操作提交到消息队列(如Kafka),由消费者异步重试,直至成功。
-
关键点:
-
消息队列需支持至少一次投递(保证可靠性)。
-
消费者需实现幂等性(多次删除同一缓存无副作用)。
-
-
2.2 读操作流程(Cache-Aside)
-
读取缓存命中,直接返回数据。
-
缓存未命中时,查询数据库并回填缓存。
-
回填时加分布式锁:防止多个线程并发回填相同数据(如用Redis的
SETNX
命令)。
-
2.3 Binlog监听(兜底一致性)
-
监听数据库变更
-
使用Canal或Debezium监听MySQL的Binlog日志,捕获所有数据变更事件(增删改)。
-
-
发布变更事件到消息队列
-
将变更事件(如表名、主键ID、操作类型)发送至Kafka,保证事件不丢失。
-
-
消费者处理事件
-
消费者根据事件类型(如
UPDATE
)删除或更新对应的缓存。 -
示例逻辑:
// 消费者处理Binlog事件 public void handleEvent(DataChangeEvent event) { if (event.getTable().equals("user")) { String cacheKey = "user:" + event.getId(); redis.delete(cacheKey); // 直接删除缓存,后续读请求触发回填 } }
-
-
意义:
-
作为“延迟双删”的兜底机制,防止因应用层双删失败或网络抖动导致缓存未清理。
-
确保即使应用层逻辑异常,Binlog监听仍能最终清理旧缓存。
-
3. 关键问题与解决方案
3.1 延迟双删的时间如何确定?
-
经验值:通常设置为业务读取耗时的2-3倍(如500ms)。
-
动态调整:根据实际业务监控的缓存回填时间动态优化。
3.2 如何避免缓存回填旧数据?
-
回填加锁:
// 伪代码:回填缓存时加分布式锁 String lockKey = "lock:user:" + id; if (redis.setnx(lockKey, "1", 10s)) { try { // 查询数据库并写入缓存 } finally { redis.delete(lockKey); } }
-
版本号校验:
缓存数据携带版本号(或时间戳),写入时校验版本,防止旧数据覆盖新数据。
3.3 消息队列的可靠性保障
-
生产者端:确保删除操作事件100%投递到MQ(如Kafka的ACK机制)。
-
消费者端:
-
至少一次消费(避免消息丢失)。
-
消费失败时重试,并记录死信队列(防止阻塞其他消息)。
-
3.4 缓存与数据库操作原子性问题
-
最终一致性原则:接受短暂不一致,通过异步机制最终达成一致。
-
无法完全避免的场景:
-
若强一致性要求极高(如金融交易),需牺牲性能,采用同步写(如Write-Through)+ 分布式事务。
-
4. 优缺点分析
优点 | 缺点 |
---|---|
1. 最终一致性保障,容错能力强。 | 1. 架构复杂,需维护消息队列和Binlog监听。 |
2. 高并发下性能优异,避免强一致的开销。 | 2. 延迟双删的时间设置依赖经验值。 |
3. 通过多级保障(双删+Binlog)降低不一致概率。 | 3. 短暂不一致窗口无法完全消除。 |
5. 适用场景
-
读多写少:如电商商品详情页、社交动态信息。
-
容忍最终一致性:非金融、支付类业务。
-
高并发场景:需要平衡性能与一致性的系统。
6. 总结
该组合模式通过以下四层保障实现高效一致性:
-
Cache-Aside:基础读写分离策略,避免缓存穿透。
-
延迟双删:主动清理可能的旧缓存,降低不一致窗口。
-
异步重试:保证删除操作最终成功。
-
Binlog监听:兜底清理残留旧缓存,实现最终一致性。
核心思想:
-
优先删除缓存而非更新,避免并发写导致数据混乱。
-
异步化与解耦:通过消息队列和Binlog监听,将缓存清理操作与主流程解耦,提升系统健壮性。