问题背景:
在 Cache-Aside 模式中,更新数据库后删除缓存失败会导致数据不一致。本文提供工业级三级补偿方案,实现最终一致性保障。
整体架构:
更新操作触发 → 一级延迟队列 → 二级消息队列 → 三级定时任务
方案实现:
一、第一级补偿:延迟队列(快速重试)
核心代码:
// 延迟队列初始化
@PostConstruct
public void init() {deleteQueue = redisson.getQueue("cache:delete:queue");delayedQueue = redisson.getDelayedQueue(deleteQueue);
}// 更新操作切面
@Around("@annotation(cacheUpdate)")
public Object aroundUpdate(...) {// 首次删除缓存redisson.getBucket(key).delete();// 数据库操作Object result = joinPoint.proceed();// 加入延迟队列(1秒后二次删除)delayedQueue.offer(key, 1, TimeUnit.SECONDS); return result;
}// 消费延迟队列(独立线程)
@EventListener(ApplicationReadyEvent.class)
public void startDelayConsumer() {new Thread(() -> {while (true) {String key = deleteQueue.poll(10, TimeUnit.SECONDS);if (key != null) {redisson.getBucket(key).delete();}}}).start();
}
特点:
响应时间:秒级
适用场景:高频更新业务
防抖设计:单线程顺序消费
二、第二级补偿:消息队列(可靠重试)
RocketMQ 集成示例:
// 消息监听器
@RocketMQMessageListener(topic = "CACHE_DELETE", consumerGroup = "cache-group")
public void handleDelete(String key) {try {if (!redisson.getBucket(key).delete()) {retryWithBackoff(key, 3); // 指数退避重试}} catch (Exception e) {// 进入死信队列}
}// 退避策略实现
private void retryWithBackoff(String key, int retryCount) {for (int i = 1; i <= retryCount; i++) {Thread.sleep(1000 * i);if (redisson.getBucket(key).delete()) break;}
}// 延迟队列异常处理
delayedQueue.offer(...).exceptionally(e -> {rocketMQTemplate.send("CACHE_DELETE", key); return null;
});
特点:
可靠性:99.9%+ 送达保障
重试策略:3次指数退避
容错机制:死信队列隔离异常
三、第三级补偿:定时任务(全量兜底)
Spring Scheduler 实现:
// 每天凌晨执行全量比对
@Scheduled(cron = "0 0 3 * * ?")
public void scanAndFix() {redisson.getKeys().getKeysByPattern("user:*").forEach(key -> {Long userId = extractUserId(key);User dbUser = databaseService.getUserFromDB(userId);User cacheUser = (User) redisson.getBucket(key).get();// 判断是否需要删除if ((cacheUser != null && dbUser == null) || (dbUser != null && cacheUser.getVersion() < dbUser.getVersion())) {redisson.getBucket(key).delete();}});
}
比对策略:
缓存存在但数据库已物理删除
数据版本号不一致
逻辑删除标记状态不一致
防抖容错设计
1. 防重复删除机制
String deleteFlagKey = key + ":deleting";
if (redis.setnx(deleteFlagKey, "1")) {redis.expire(deleteFlagKey, 30); // 30秒窗口期performDelete(key);
}
2. 监控报警体系
各级补偿对比