問題背景:
在 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. 監控報警體系
各級補償對比