首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【转】一次Redis主从切换“惨案”:幕后黑手竟是千万级消费堆积

【转】一次Redis主从切换“惨案”:幕后黑手竟是千万级消费堆积

作者头像
保持热爱奔赴山海
发布2026-01-16 21:59:53
发布2026-01-16 21:59:53
1410
举报
文章被收录于专栏:数据库相关数据库相关

最近因为celery的问题,研究了下redis的stream。

关于redis stream配置不当导致的问题之前很少在网上看到遇到的,正好最近看到这篇文章,转过来。

一次Redis主从切换“惨案”:幕后黑手竟是8千万消费堆积

做运维和开发的同学应该都懂,Redis告警一响,心跳直接漏半拍。

前几天我们就撞上了这么个棘手问题:Redis主从突然切换,新主刚上位又被判定下线。排查到最后才发现,元凶居然是一个堆积了八千多万条消息的Stream。

今天就跟大家复盘整个过程,希望能帮大家避避坑。

一、告警突发:Redis 服务异常“失联”

早上,监控告警准时炸开:Redis-Server 6379 is stop

我们的 Redis 架构是一主两从三哨兵,按道理说就算主节点挂了,哨兵也会快速切换,不至于触发服务停止告警。

登录服务器第一件事就是查进程,结果出乎意料:Redis 相关进程全都正常运行。

这就奇怪了,进程没挂,为啥会告警?

二、Redis一主两从三哨兵架构原理

在排查问题前,先简单介绍下Redis哨兵架构,帮助理解后续故障链路:

  1. 主从复制:1个主节点负责写操作,2个从节点通过复制主节点数据提供读服务,实现读写分离。主节点故障时,从节点可以升级为主节点。
  2. 哨兵机制:3个哨兵节点独立运行,核心职责有三个:
    • 监控:持续检查主从节点的健康状态。
    • 选主:主节点故障时,通过投票机制从从节点中选举新主。
    • 通知:将新主节点信息同步给客户端和其他节点,完成主从切换。
  3. 核心配置:本次故障中哨兵的 down-after-milliseconds 配置为 5000ms,即节点超过5秒无响应,就会被判定为主观下线。

三、排查之路:从哨兵日志到慢查询,层层抽丝剥茧

既然进程没问题,那就从日志入手。

翻看哨兵日志后,真相浮出水面:哨兵确实触发了主从切换操作。原主节点123被判定下线,哨兵投票将121切换成新主。但诡异的是,新主上位才6秒,就被标记为 sdown(主观下线)

代码语言:javascript
复制
91495:X 16 Jan 2026 08:37:59.869 # +new-epoch 13
91495:X 16 Jan 2026 08:37:59.871 # +vote-for-leader 5d8bc4ef7e1ed90ce157d5964fd6d5d85f03052e 13
91495:X 16 Jan 2026 08:38:00.733 # +odown master mymaster 10.191.2.123 6379 #quorum 3/2
91495:X 16 Jan 2026 08:38:00.733 # Next failover delay: I will not start a failover before Fri Jan 16 08:44:00 2026
91495:X 16 Jan 2026 08:38:00.974 # +config-update-from sentinel 5d8bc4ef7e1ed90ce157d5964fd6d5d85f03052e 10.191.2.122 26379 @ mymaster 10.191.2.123 6379
91495:X 16 Jan 2026 08:38:00.974 # +switch-master mymaster 10.191.2.123 6379 10.191.2.121 6379
91495:X 16 Jan 2026 08:38:00.974 * +slave slave 10.191.2.122:6379 10.191.2.122 6379 @ mymaster 10.191.2.121 6379
91495:X 16 Jan 2026 08:38:00.974 * +slave slave 10.191.2.123:6379 10.191.2.123 6379 @ mymaster 10.191.2.121 6379
91495:X 16 Jan 2026 08:38:06.033 # +sdown master mymaster 10.191.2.121 6379
91495:X 16 Jan 2026 08:38:06.033 # +sdown slave 10.191.2.123:6379 10.191.2.123 6379 @ mymaster 10.191.2.121 6379
91495:X 16 Jan 2026 08:38:06.033 # +sdown slave 10.191.2.122:6379 10.191.2.122 6379 @ mymaster 10.191.2.121 6379

我们第一反应是网络抖动,但查了服务器的网卡流量、延迟、丢包率、CPU等指标,全都正常,网络和系统问题的嫌疑被排除。

接着检查哨兵配置,down-after-milliseconds 设的是 5000ms,也就是 5 秒超时判定,配置也不算问题。

最后把目光投向 Redis 慢查询日志,这一下直接抓到了关键线索:一条 DEL wxMsg 命令,耗时高达 9.6 秒

代码语言:javascript
复制
[redis@redis_121:/home/mpay/app/redis/bin]>> ./redis-cli -h 10.191.2.121 -a xxxxxxxxx
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
10.191.2.45:6379> SLOWLOG get
 2) 1) (integer) 2352
    2) (integer) 1768526239
    3) (integer) 9632634
    4) 1) "DEL"
       2) "wxMsg"
    5) "10.191.3.65:50840"
    6) ""

9.6秒是什么概念?远超哨兵5秒的超时阈值。Redis是单线程模型,这条慢命令直接把整个服务堵死了。

四、根因定位:百万级消费堆积,压垮 Redis 的最后一根稻草

此时单单查到慢查询还不够,还得弄清楚 wxMsg 这个 key 到底是什么来头。

我们用命令登录 Redis 一探究竟:

代码语言:javascript
复制
10.191.2.121:6379> type wxMsg
stream
10.191.2.121:6379> XLEN wxMsg
(integer) 241
10.191.2.121:6379> XINFO GROUPS wxMsg
1) 1) "name"
   2) "stlm"
   3) "consumers"
   4) (integer) 4
   5) "pending"
   6) (integer) 83734874

这组命令的输出,直接揭开了谜底:

  1. wxMsg 是一个 Stream 类型的 key,主要用来做消息队列。
  2. 它的消息长度只有 241 条,但未确认消费的消息(pending)高达 8373 万条
  3. 再结合研发提供的 xxl-job 错误日志,当时程序正在初始化 wxMsg 这个 Stream,结果因为 Redis 命令超时直接失败。
代码语言:javascript
复制
2026-01-16 98:37:58 [com.xx1.job.core.thread.JobThread#run]-[204]-[xx1-job,JobThread-157-1768523874392]<br>--
..- JobThread Exception: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.xxl.job.core.handler.impl.MethodJobHandler.execute(MethodJobHandler.java:31)
at com.xxl.job.core.thread.JobThread.run(JobThread.java:166)
Caused by: org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException:
Command timed out after 2 second (5)

至此,整个故障链路就清晰了:

  1. wxMsg Stream 的 stlm 消费组存在海量未确认消费的消息,形成严重的消费堆积。
  2. 程序执行 DEL wxMsg 命令,试图删除这个大key。
  3. 单线程的Redis被这条耗时 9.6 秒的慢命令阻塞,无法响应哨兵的心跳检测。
  4. 哨兵判定原主节点下线,触发主从切换,将节点切换为新主。

五、解决与优化:对症下药,杜绝后患

找到根因后,我们立刻和研发确认,得到关键结论:stlm消费组属于历史遗留配置,业务侧已无使用,可以直接删除。 基于这个结论,我们采取了以下措施

  1. 紧急清理 Stream 及无效消费组
    • 操作命令:直接删除无用消费组,彻底清理堆积的 pending 消息# 查看消费组详情,确认待删除目标 10.191.2.121:6379> XINFO GROUPS wxMsg # 删除stlm消费组,消费组内的pending消息会被一并清理 10.191.2.121:6379> XGROUP DESTROY wxMsg stlm # 若wxMsg Stream也无业务用途,可直接删除整个key,异步操作 10.191.2.121:6379> UNLINK wxMsg
    • 验证命令:检查消费组和 pending 消息是否清理干净172.16.2.45:6379> XINFO GROUPS wxMsg 172.16.2.45:6379> EXISTS wxMsg
  2. 优化 Stream 消费逻辑,规范消费确认流程
    • 操作命令:协调研发修复代码,确保新增消费组的消息消费后执行确认操作# 业务代码中添加XACK命令,示例伪代码 XACK {stream_key} {group_name} {message_id}
    • 验证命令:持续监控消费者 pending 数量和空闲时间10.191.2.121:6379> XINFO CONSUMERS {stream_key} {group_name}
  3. 调整大 key 操作策略,禁止直接 DEL 超大 key
    • 操作命令:改用分批删除,避免阻塞 Redis# 对仍在使用的Stream类型大key,用XTRIM分批截断 10.191.2.121:6379> XTRIM {stream_key} MAXLEN 1000 # 循环执行直到消息长度符合预期
    • 验证命令:检查命令执行耗时和 Redis 负载# 查看慢查询日志,确认无超长耗时命令 10.191.2.121:6379> SLOWLOG get 10
  4. 加强监控告警,新增 Stream 专项监控
    • 操作命令:配置监控系统,添加以下监控项# 监控Stream的数量、消费组数量、pending消息量 # 监控命令示例(可接入Prometheus+Grafana) redis-cli XINFO GROUPS {stream_key} | grep pending
    • 验证命令:手动触发阈值测试,确认告警正常# 模拟pending消息堆积,检查监控平台是否告警

六、经验总结:这些坑,千万别踩!

复盘完这次故障,我们也总结了几个关键点,分享给大家:

  1. Redis 慢查询日志要会用:性能问题,先查慢查询,大概率能找到元凶。
  2. Stream 消息堆积要警惕:堆积过多就是定时炸弹。
  3. 大 key 操作要谨慎:单线程的 Redis 扛不住长时间的大 key 操作。
  4. 历史遗留配置要清理:定期核查无用 key 和消费组,避免占用资源引发故障。
  5. 监控维度要全面:除了常规的内存、CPU,还要关注哨兵状态、Stream 消息堆积等细分指标。

最后,想问下各位同行:你们在运维 Redis 的时候,都遇到过哪些奇葩故障?欢迎在评论区分享你的经历~

公众号:DevOps运维实践

原文地址:https://mp.weixin.qq.com/s/BknJjJonAXfTSKxvB64zGw

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一次Redis主从切换“惨案”:幕后黑手竟是8千万消费堆积
    • 一、告警突发:Redis 服务异常“失联”
    • 二、Redis一主两从三哨兵架构原理
    • 三、排查之路:从哨兵日志到慢查询,层层抽丝剥茧
    • 四、根因定位:百万级消费堆积,压垮 Redis 的最后一根稻草
    • 五、解决与优化:对症下药,杜绝后患
    • 六、经验总结:这些坑,千万别踩!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档