Redis刪除緩存失敗的原因和解決方案
這篇聊一個很現(xiàn)實的問題:數(shù)據(jù)庫已經(jīng)改成功了,但緩存刪除失敗了,線上怎么辦?
先給答案
如果你項目里只有一句 redis.del(key),那一致性是靠運氣。
一套更穩(wěn)的做法是:
- 主流程里先寫庫再刪緩存
- 刪除失敗立刻進入重試隊列
- 超過重試上限進入死信隊列
- 死信觸發(fā)告警和人工/自動補償
- 全鏈路打點,能看見“刪失敗率”和“補償成功率”
一句話:刪除緩存不是一個動作,而是一條可觀測、可補償?shù)逆溌贰?/strong>
為什么“刪緩存失敗”必須單獨設(shè)計
很多同學(xué)會說:“刪失敗就下次再讀庫唄。”
這句話在低并發(fā)時看起來沒毛病,但線上高峰期會出事:
- 熱點 key 還在,用戶繼續(xù)讀到舊值
- 讀流量越大,舊值傳播越快
- 你又沒有補償機制,臟數(shù)據(jù)會“活很久”
最麻煩的是:這個問題不會立刻報錯,而是以“偶發(fā)投訴”“數(shù)據(jù)不對”的形式出現(xiàn),排查成本很高。
一個真實可落地的鏈路

你會發(fā)現(xiàn),核心不是“怎么刪”,而是“刪不掉時怎么兜”。
代碼示例:主流程 + 異步重試
1. 主流程(寫庫后刪緩存)
@Service
public class ProductService {
@Resource
private ProductMapper productMapper;
@Resource
private StringRedisTemplate redisTemplate;
@Resource
private CacheDeleteProducer cacheDeleteProducer;
@Transactional(rollbackFor = Exception.class)
public void updateProduct(Product product) {
String key = "product:" + product.getId();
// 1) 數(shù)據(jù)庫是事實來源
productMapper.updateById(product);
// 2) 主流程刪緩存,失敗則入重試隊列
try {
redisTemplate.delete(key);
} catch (Exception ex) {
cacheDeleteProducer.sendDeleteEvent(key, 1);
}
}
}2. 重試消費者(指數(shù)退避 + 最大次數(shù))
@Component
public class CacheDeleteConsumer {
private static final int MAX_RETRY = 5;
@Resource
private StringRedisTemplate redisTemplate;
@Resource
private CacheDeleteProducer cacheDeleteProducer;
@Resource
private DeadLetterProducer deadLetterProducer;
public void onMessage(CacheDeleteEvent event) {
try {
redisTemplate.delete(event.getCacheKey());
// 打點:delete_success_total +1
} catch (Exception ex) {
int nextRetry = event.getRetryCount() + 1;
if (nextRetry > MAX_RETRY) {
deadLetterProducer.send(event.getCacheKey(), ex.getMessage());
return;
}
long delaySeconds = (long) Math.pow(2, nextRetry); // 2,4,8,16,32
cacheDeleteProducer.sendDeleteEvent(event.getCacheKey(), nextRetry, delaySeconds);
}
}
}3. 死信補償任務(wù)(定時巡檢)
@Component
public class CacheDeleteCompensationJob {
@Resource
private DeadLetterRepository deadLetterRepository;
@Resource
private StringRedisTemplate redisTemplate;
// 每 5 分鐘跑一次
@Scheduled(cron = "0 */5 * * * ?")
public void compensate() {
List<DeadLetterRecord> records = deadLetterRepository.queryUnresolved(200);
for (DeadLetterRecord record : records) {
try {
redisTemplate.delete(record.getCacheKey());
deadLetterRepository.markResolved(record.getId());
} catch (Exception e) {
deadLetterRepository.increaseFailCount(record.getId(), e.getMessage());
}
}
}
}這 5 個細節(jié),決定你方案能不能用
冪等性
刪緩存天生冪等,刪不存在 key 也算成功,別把它當(dāng)異常。
重試上限
不要無限重試,超過閾值必須死信,不然就是隱性消息堆積。
退避策略
固定 1 秒重試容易打爆 Redis,用指數(shù)退避更穩(wěn)。
死信可見性
死信不等于丟棄,要有告警和處理面板。
鏈路監(jiān)控
至少要有這幾個指標(biāo):
cache_delete_fail_totalcache_delete_retry_totalcache_delete_dlt_totalcache_delete_compensation_success_total
常見誤區(qū)
誤區(qū) 1:刪失敗概率很低,可以忽略
線上你總會遇到:網(wǎng)絡(luò)抖動、Redis 短暫超時、連接池耗盡。
低概率 * 高頻請求 = 可觀事故數(shù)。
誤區(qū) 2:有延遲雙刪就夠了
延遲雙刪只能覆蓋一部分并發(fā)窗口,無法替代失敗重試鏈路。
誤區(qū) 3:死信就是失敗,人工看就行
只靠人工盯死信,夜里一定會漏。
最好是“告警 + 自動補償 + 人工兜底”三層。
選型建議(按團隊規(guī)模)
| 團隊階段 | 推薦方案 |
|---|---|
| 小團隊、單體服務(wù) | 寫庫后刪緩存 + 本地重試(短期) |
| 中型團隊、多服務(wù) | 寫庫后刪緩存 + MQ 重試 + 死信告警 |
| 大團隊、高一致性要求 | 事件驅(qū)動一致性 + 死信平臺 + 自動補償任務(wù) |
最后總結(jié)
“刪除緩存失敗”不是小概率邊角料,它是緩存一致性的主戰(zhàn)場。
真正能扛線上流量的方案,通常長這樣:
- 主鏈路快:寫庫后刪緩存
- 失敗可恢復(fù):異步重試
- 極端可兜底:死信補償
- 整體可觀測:指標(biāo)和告警
把這四件事做到位,你的緩存一致性就不是“玄學(xué)”,而是工程能力。
以上就是Redis刪除緩存失敗的原因和解決方案的詳細內(nèi)容,更多關(guān)于Redis刪除緩存失敗的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis集群水平擴展、集群中添加以及刪除節(jié)點的操作
這篇文章主要介紹了Redis集群水平擴展、集群中添加以及刪除節(jié)點的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03
Redis02 使用Redis數(shù)據(jù)庫(String類型)全面解析
這篇文章主要介紹了Redis02 使用Redis數(shù)據(jù)庫(String類型)全面解析的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07

