用 Redis 作消息队列时,如何处理宕机导致的消息丢失?

考查重点:消费确认与故障恢复。区分生产写入、Redis持久化复制与消费者宕机窗口,用PEL重领、业务幂等和任务对账恢复

本题目录

参考答案

核心回答

要按生产者、Redis、消费者三个环节处理宕机,不能只说开启持久化或收到后重试。

  • 生产者未可靠投递: 保留可重投的业务任务,检查写入结果;发送超时可能是“已写入但响应丢了”,重发时需要稳定的业务标识。
  • Redis 自身宕机: 配置 AOF、备份和复制,按业务容忍度选择刷盘策略。everysec 仍有未落盘窗口,异步复制下主从切换也可能丢失未同步写入。
  • 消费者处理途中宕机: 优先使用 Stream 消费组,消息进入 PEL 待处理列表,业务结果提交成功后再 XACK。主动扫描并补领超时消息,不能只读新消息。
  • 重复投递或永久失败: 用数据库唯一约束、条件更新等保证业务幂等;有限重试后转入可追踪的失败任务或死信队列,告警并支持人工处理。

目标是让任务可以查到、可以重投、重复执行不会破坏业务。消费确认可以降低丢任务风险,不提供跨 Redis 与数据库的端到端 exactly-once 保证。

消费者宕机,为什么需要确认和补领

直接用 List 弹出消息后,消息已离开队列;此时进程宕机,业务还没完成就没有可恢复记录。若继续使用 List,需要先原子移动到处理中列表,完成后再删除,并自行实现超时恢复。Pub/Sub 不保存离线消息,也没有消费确认,适合即时通知。

Stream 消费组把消息本体与投递状态分开管理:

  1. XREADGROUP 在未使用 NOACK 时,将已投递但未确认的消息登记到 PEL。
  2. 消费者完成并提交业务结果后调用 XACK,将对应记录从该消费组的 PEL 移除;这不等于删除消息本体。
  3. 通过 XPENDING 查看积压,再用 XCLAIM 或 Redis 6.2 起提供的 XAUTOCLAIM 补领超过合理空闲时间的消息。

读取新消息使用的 > 不会自动重投全部待处理消息,需要明确编写恢复循环。补领阈值应覆盖正常处理时长,否则慢任务也会被其他消费者接管。

还有一个必然要处理的窗口:业务已提交,ACK 前宕机。消息会再次出现,消费者应按业务标识判断已完成并确认消息,而不是重复扣款、创建订单或保存同一结果。消息唯一 ID 只标识一条消息,多次重投产生的新消息还需要同一个业务标识关联。

Redis 宕机和生产双写怎样补救

RDB 是周期快照,快照后新写入的数据可能丢失;AOF everysec 通常存在约一秒的未刷盘窗口;always 提高刷盘频率,也增加写入延迟。复制与故障切换还要单独考虑,不能把主节点收到消息等同于副本已经持久保存。

重要任务应在数据库保留任务状态或待投递记录。若业务写库与入队需要可靠衔接,可以在同一数据库事务中写业务记录和待发送事件,再由投递器重试发送;也可以扫描长期未完成的任务进行对账补投。这个设计让队列记录丢失后仍有恢复依据,同时要求消费者处理重复消息。

保留策略也是可靠性的一部分:消息被 MAXLEN、删除或过期清理后,即使 PEL 还记着 ID,也无法取回原文。需要结合消费积压决定保留长度,监控未确认数量、最老待处理时间、投递失败和业务卡住任务。达到重试上限时,先持久保存失败信息及重放依据,再结束原消息的处理;死信队列也需要告警和处置流程。

参考资料

  • 国内:Guide / JavaGuide:Redis 可以做消息队列吗?:采用 List、Pub/Sub 与消费组确认机制的区别,解释消费者宕机时为什么需要保留处理中状态和恢复入口。
  • 国内:Guide / JavaGuide:Redis 持久化机制详解:依据写入缓冲区、文件同步与刷盘策略的区别,说明开启 AOF 仍需明确宕机丢失窗口。
  • 国内:小林 coding:Redis 面试题:采用消费成功后确认、主动处理待处理消息和业务幂等的解释,说明 ACK 前宕机会带来重复执行。
  • 国内:阿里云:Tair 备份与恢复概述:参考快照与增量日志的恢复粒度,解释持久化、备份与业务任务对账分别解决的问题。
  • 国外:Redis:XAUTOCLAIM:依据空闲时间、扫描游标和已删除消息的处理规则,解释补领循环及裁剪后无法恢复消息正文的边界。
  • 国外:Redis:Persistence:采用快照与不同 fsync 策略的耐久性和延迟取舍,说明 Redis 服务端仍可能丢失近期写入。
  • 国外:Redis:Replication:依据异步复制仍存在数据丢失窗口的说明,解释主节点接收成功与故障切换后的可恢复性为何不能等同。

InterviewGuideJava

基于 InterviewGuide

核心回答

InterviewGuide 用 Java 和 Spring 实现简历分析等异步任务,Redis Stream 负责投递,数据库保存正文、结果与任务状态。以简历分析为例,先保存 PENDING 任务,再发送包含 resumeId 和 retryCount 的消息;消费者先领取数据库任务,执行分析、保存结果并更新完成状态,最后确认消息。

恢复有两层:Stream 层主动补领未确认消息,数据库层扫描长期卡住的任务并补投。 前者解决消息交给某个消费者后无人继续处理的问题,后者为消息丢失、裁剪或业务状态停滞提供恢复依据。它允许重投,依靠业务状态约束重复执行,并不承诺 Redis 永不丢消息。

未确认消息怎样重新被消费

消费者启动时准备消费组,每个实例使用独立消费者名称。循环先调用 autoClaim(),按游标补领空闲超过 5 分钟的待处理消息,每批最多 10 条;本轮没有补领结果时,再阻塞读取该组尚未投递的新消息。由此,原消费者宕机后,恢复并不依赖它以原名称重新上线。具体顺序见补领与新消息读取。

补领成功只表示获得消息,还要通过数据库的 PENDING→PROCESSING 条件更新领取业务任务。已完成或无法领取的任务会确认消息并跳过。若旧任务仍停在 PROCESSING,补领消息不会直接使它重新执行,需要下一层数据库恢复将过期状态重置后补投。PEL 状态和业务任务状态是两套记录,不能混为一条状态机。

业务失败、重试与 ACK 的顺序

公共消费者的正常路径是:领取任务、执行业务、更新完成状态、ACK。异常路径会检查消息里的 retryCount:小于 3 时重新投递,达到上限后标记失败,再确认旧消息。这里最多是首次执行加 3 次消息重试,业务抛出的异常才会进入这条路径。

简历分析重投前先条件重置为 PENDING,新消息写入增加后的重试次数;重投入队失败时,把仍待处理的任务标为 FAILED。格式错误消息会直接确认丢弃,ACK 失败则记录日志,后续仍可能再次被补领。见消费确认与异常处理及简历分析重投。

当前超过重试上限的处理是数据库 FAILED 终态与手动重试,没有独立死信 Stream。若增加死信能力,可以保存原消息、业务标识、异常原因和重试历史,完成持久转存后再确认原消息。另一方面,分析服务会把部分模型调用异常转换为错误报告返回,队列可能仍按正常结果保存并完成;消息成功处理与模型分析结果有效也要分别判断。

队列记录丢了,数据库怎样兜底

上传流程先提交简历记录,再发送消息;发送失败会保留文件与记录,并把待处理任务标记失败,供用户重试。这是分步写入,未使用覆盖数据库和 Redis 的原子事务或事务发件箱。

数据库恢复调度器默认每分钟分别扫描过期的 PENDING 和 PROCESSING:前者默认超过 10 分钟尝试补投;后者默认 15 分钟无进展时条件重置,再补投。恢复有次数上限,避免无限循环,具体见卡住任务恢复调度。阈值需要覆盖排队和实际业务耗时,否则会误判仍在执行的任务。

消息发送使用近似 MAXLEN=1000 裁剪,未确认消息也可能先被裁掉;补领发现这类 ID 时只记录警告日志,无法恢复已裁剪的消息体。因此数据库任务和已保存的简历正文很关键,但仍需要核对恢复是否成功、失败任务是否有人处理。要进一步提高可靠性,应结合部署环境配置持久化与复制、按积压调整保留策略,并验证“入队前宕机、领取后宕机、结果保存后 ACK 前宕机”三个窗口,而不能只测试正常消费。

相关面经

1 篇

中兴

软件开发工程师
秋招一面
查看原面经 →
项目中Redis用作消息队列,如何考虑宕机消息丢失问题?

相关题目

4 题