Redis 删除缓存失败怎么办?重试、死信、补偿的工程化方案

张开发
2026/4/13 18:26:04 15 分钟阅读

分享文章

Redis 删除缓存失败怎么办?重试、死信、补偿的工程化方案
这篇聊一个很现实的问题数据库已经改成功了但缓存删除失败了线上怎么办先给答案如果你项目里只有一句redis.del(key)那一致性是靠运气。一套更稳的做法是主流程里先写库再删缓存删除失败立刻进入重试队列超过重试上限进入死信队列死信触发告警和人工/自动补偿全链路打点能看见“删失败率”和“补偿成功率”一句话删除缓存不是一个动作而是一条可观测、可补偿的链路。为什么“删缓存失败”必须单独设计很多同学会说“删失败就下次再读库呗。”这句话在低并发时看起来没毛病但线上高峰期会出事热点 key 还在用户继续读到旧值读流量越大旧值传播越快你又没有补偿机制脏数据会“活很久”最麻烦的是这个问题不会立刻报错而是以“偶发投诉”“数据不对”的形式出现排查成本很高。一个真实可落地的链路成功失败成功失败超过阈值业务写请求更新 MySQL删除 Redis key返回成功发送重试消息消费者重试删除记录成功指标进入死信队列告警通知补偿任务你会发现核心不是“怎么删”而是“删不掉时怎么兜”。代码示例主流程 异步重试1. 主流程写库后删缓存ServicepublicclassProductService{ResourceprivateProductMapperproductMapper;ResourceprivateStringRedisTemplateredisTemplate;ResourceprivateCacheDeleteProducercacheDeleteProducer;Transactional(rollbackForException.class)publicvoidupdateProduct(Productproduct){Stringkeyproduct:product.getId();// 1) 数据库是事实来源productMapper.updateById(product);// 2) 主流程删缓存失败则入重试队列try{redisTemplate.delete(key);}catch(Exceptionex){cacheDeleteProducer.sendDeleteEvent(key,1);}}}2. 重试消费者指数退避 最大次数ComponentpublicclassCacheDeleteConsumer{privatestaticfinalintMAX_RETRY5;ResourceprivateStringRedisTemplateredisTemplate;ResourceprivateCacheDeleteProducercacheDeleteProducer;ResourceprivateDeadLetterProducerdeadLetterProducer;publicvoidonMessage(CacheDeleteEventevent){try{redisTemplate.delete(event.getCacheKey());// 打点delete_success_total 1}catch(Exceptionex){intnextRetryevent.getRetryCount()1;if(nextRetryMAX_RETRY){deadLetterProducer.send(event.getCacheKey(),ex.getMessage());return;}longdelaySeconds(long)Math.pow(2,nextRetry);// 2,4,8,16,32cacheDeleteProducer.sendDeleteEvent(event.getCacheKey(),nextRetry,delaySeconds);}}}3. 死信补偿任务定时巡检ComponentpublicclassCacheDeleteCompensationJob{ResourceprivateDeadLetterRepositorydeadLetterRepository;ResourceprivateStringRedisTemplateredisTemplate;// 每 5 分钟跑一次Scheduled(cron0 */5 * * * ?)publicvoidcompensate(){ListDeadLetterRecordrecordsdeadLetterRepository.queryUnresolved(200);for(DeadLetterRecordrecord:records){try{redisTemplate.delete(record.getCacheKey());deadLetterRepository.markResolved(record.getId());}catch(Exceptione){deadLetterRepository.increaseFailCount(record.getId(),e.getMessage());}}}}这 5 个细节决定你方案能不能用幂等性删缓存天生幂等删不存在 key 也算成功别把它当异常。重试上限不要无限重试超过阈值必须死信不然就是隐性消息堆积。退避策略固定 1 秒重试容易打爆 Redis用指数退避更稳。死信可见性死信不等于丢弃要有告警和处理面板。链路监控至少要有这几个指标cache_delete_fail_totalcache_delete_retry_totalcache_delete_dlt_totalcache_delete_compensation_success_total常见误区误区 1删失败概率很低可以忽略线上你总会遇到网络抖动、Redis 短暂超时、连接池耗尽。低概率 * 高频请求 可观事故数。误区 2有延迟双删就够了延迟双删只能覆盖一部分并发窗口无法替代失败重试链路。误区 3死信就是失败人工看就行只靠人工盯死信夜里一定会漏。最好是“告警 自动补偿 人工兜底”三层。选型建议按团队规模团队阶段推荐方案小团队、单体服务写库后删缓存 本地重试短期中型团队、多服务写库后删缓存 MQ 重试 死信告警大团队、高一致性要求事件驱动一致性 死信平台 自动补偿任务最后总结“删除缓存失败”不是小概率边角料它是缓存一致性的主战场。真正能扛线上流量的方案通常长这样主链路快写库后删缓存失败可恢复异步重试极端可兜底死信补偿整体可观测指标和告警把这四件事做到位你的缓存一致性就不是“玄学”而是工程能力。

更多文章