Redis 从节点 CPU 过高问题排查
ENV
| 类别 | 信息 |
|---|---|
| 操作系统 | CentOS Linux 7 (Core) |
| Redis | 6.2.13,Cluster 模式,端口 7000/7001(集群一)、7002/7003(集群二) |
| 监控组件 | redis_exporter(--is-cluster=true,监听 :9121) |
| 涉及节点 | middleware1 → 10.10.240.3;middleware2 → 10.10.240.4 |
| 告警实例 | 10.10.240.3:7001、10.10.240.4:7001(均为 slave) |
| 硬件/资源(观测值) | 单实例 RSS ≈ 39GB;连接数 ≈ 1996;驱动/固件版本排查期间未采集 |
集群拓扑(7000/7001):
| 节点 | 角色 | 说明 |
|---|---|---|
| 240.3:7000 | master | slot 0–5460 |
| 240.4:7000 | master | slot 5461–10922 |
| 240.5:7000 | master | slot 10923–16383 |
| 240.3:7001 | slave | 从 240.5:7000 同步 |
| 240.4:7001 | slave | 从 240.3:7000 同步 |
| 240.5:7001 | slave | 从 240.4:7000 同步 |
1. 问题现象
- 监控告警:
10.10.240.3:7001、10.10.240.4:7001CPU 过高 - 初步怀疑 Redis 故障;两台均为 Cluster 从节点
- 排查时常态 CPU 约 8~9%(非持续 100%),疑为 间歇性 burst 触发告警
2. 问题原因
2.1 结论
| 类型 | 说明 |
|---|---|
| 主因 | 应用 swd-mes-data-sync(10.10.240.133/135)对从库发起 多连接并发 SCAN(COUNT=20000),在 3580 万 key 库上单次耗时 18~38ms,累计 571 万次 |
| 次因 | 主库 HEAVY-JUDGEMENT-IMPORT-* 超大 RPUSH(单条 3~5 万参数)经复制 replay,从库 CPU 周期性升高 |
| 排除项 | 主从复制正常(master_link_status:up,offset 一致,无全量同步) |
问题形成原因推测:
- 开发(
swd-mes-data-sync):为实现t_wip_detail全量数据同步,在 3580 万 key 场景下选用 多连接并发 SCAN(COUNT=20000),并将扫库目标指向 7001 从库(可能意在减轻主库压力)→ 任务触发时从库 CPU 出现 间歇性 burst,引发告警。 - 开发(
HEAVY-JUDGEMENT-IMPORT相关模块):为提升批量导入效率,将大量序列号 单次 RPUSH 合并为一条命令(3 万~5.6 万参数)→ 主库慢查询 + 从库复制 replay 耗时升高,叠加 SCAN burst 进一步推高 CPU。 - 运维:未对 data-sync 扫库类任务建立 变更审批与 SCAN 监控告警,从库长期维持 51 连接/节点 的连接池而未收敛 → 问题爆发前 缺少预警,直至 CPU 监控触发告警才介入排查。
2.2 定位推导过程
Step 1 — 排除复制问题(240.3)
# 执行主机:10.10.240.3
timeout 8 bash -c 'PID=$(pgrep -f "redis-server.*:7001" | head -1); ps -p "$PID" -o pid,pcpu,pmem,rss,etime,cmd --no-headers; redis-cli -a redis_pass -p 7001 INFO replication 2>/dev/null | egrep "role|master_host|master_link_status|master_sync_in_progress|slave_repl_offset|master_repl_offset"'
关键输出:
33503 9.1 3.8 ... redis-server *:7001 [cluster]
role:slave
master_link_status:up
master_sync_in_progress:0
slave_repl_offset:1030669701612
master_repl_offset:1030669701612 # offset 一致,无复制延迟
→ 复制正常,转向命令负载分析。
Step 2 — 发现 SCAN 高耗时(240.3)
# 执行主机:10.10.240.3
timeout 8 bash -c 'redis-cli -a redis_pass -p 7001 INFO stats 2>/dev/null | egrep "instantaneous_ops_per_sec|connected_clients|expired_keys"; redis-cli -a redis_pass -p 7001 INFO commandstats 2>/dev/null | grep cmdstat_scan'
关键输出:
instantaneous_ops_per_sec:2147
cmdstat_scan:calls=5714244,usec_per_call=1528.21 # 单次 SCAN 均耗 ~1.5ms
→ SCAN 累计 CPU 消耗最大。
Step 3 — 慢日志定位来源(240.3)
# 执行主机:10.10.240.3
timeout 8 bash -c 'redis-cli -a redis_pass -p 7001 SLOWLOG GET 25 2>/dev/null | head -60; redis-cli -a redis_pass -p 7001 INFO keyspace 2>/dev/null'
关键输出:
# 大量 SCAN,来源 swd-mes-data-sync
SCAN ... MATCH swd-mes-produce-server:t_wip_detail:* COUNT 20000
client: 10.10.240.133 name=swd-mes-data-sync duration: 18~38ms
# 超大 RPUSH(主库复制)
RPUSH HEAVY-JUDGEMENT-IMPORT-xxx ... (37805 more arguments)
db0:keys=35832198,expires=35832198 # 3580 万 key
connected_clients:1996
→ 根因应用 swd-mes-data-sync;次因 超大 batch RPUSH。
Step 4 — 确认 SCAN 为间歇 burst(240.3)
# 执行主机:10.10.240.3
timeout 8 bash -c 'redis-cli -a redis_pass -p 7001 CLIENT LIST 2>/dev/null | grep -c "name=swd-mes-data-sync"; redis-cli -a redis_pass -p 7001 CLIENT LIST 2>/dev/null | grep -c "cmd=scan"'
关键输出:
swd-mes-data-sync=51
cmd=scan=0 # 当前无 SCAN,任务间歇触发
Step 5 — 240.4 交叉验证
# 执行主机:10.10.240.4
timeout 8 bash -c 'redis-cli -a redis_pass -p 7001 INFO replication 2>/dev/null | egrep "master_link_status|slave_repl_offset|master_repl_offset"; redis-cli -a redis_pass -p 7001 INFO commandstats 2>/dev/null | grep cmdstat_scan; redis-cli -a redis_pass -p 7001 SLOWLOG GET 30 2>/dev/null | grep -c SCAN'
关键输出:
master_link_status:up
cmdstat_scan:calls=5712829,usec_per_call=1517.53
SLOWLOG scan hits: 20/30
swd-mes-data-sync=51
→ 两台从库 现象一致,同一应用导致。
Step 6 — 240.4 CPU 确认
# 执行主机:10.10.240.4(修正 pgrep 自匹配)
timeout 5 bash -c 'PID=$(ps -eo pid,cmd | awk "/[r]edis-server.*:7001/{print \$1; exit}"); ps -p "$PID" -o pid,pcpu,pmem,rss,etime,cmd --no-headers'
关键输出:
88768 8.7 3.8 40874708 ... redis-server *:7001 [cluster]
→ 与 240.3(9.1%)接近,常态偏高,burst 时触发告警。
3. 解决方案
3.1 立即止血(运维)
① 暂停同步任务(推荐,需业务配合)
在 10.10.240.133/135 上停止 swd-mes-data-sync 服务或调度任务。
② 应急断开 data-sync 连接(变更操作,SCAN 再次爆发时使用)
在 240.3、240.4 各执行一次;仅断开 swd-mes-data-sync,不影响其他客户端。
redis-cli -a redis_pass -p 7001 CLIENT LIST | grep 'name=swd-mes-data-sync' | sed -n 's/.* addr=\([^ ]*\).*/\1/p' | while read addr; do redis-cli -a redis_pass -p 7001 CLIENT KILL ADDR "$addr"; done
3.2 根治(开发)
参数调整建议
| 参数 | 当前值 | 建议值 | 原因 / 后果 |
|---|---|---|---|
SCAN COUNT |
20000 | 100~500 | 当前单次 SCAN 均耗 ~1.5ms(慢日志 18~38ms);COUNT 越大单次遍历槽位越多,在 3580 万 key 库上 CPU 线性升高。降至 100~500 可显著降低单次开销,总扫完时间略增但可接受 |
| SCAN 并发连接数 | ≥15(慢日志同一秒多条 SCAN) | 1~2 | 多 cursor 并发扫同一从库,CPU 叠加;15 连接 × 20ms/次 ≈ 瞬时打满单核。单/双 cursor 顺序扫可消除 burst |
| SCAN 目标节点 | 6 节点从库(7001)同时扫 | 单主库 1 节点,低峰执行 | 从库还承担 ~2000 QPS 读流量,SCAN 与读争抢 CPU;改扫主库单节点或离线同步,从库 CPU 不受扫库影响 |
| RPUSH 单条元素数 | 3 万~5.6 万(HEAVY-JUDGEMENT-IMPORT-*) |
≤500~1000 / Pipeline 分批 | 单命令参数过多导致主库执行 10~28ms 入慢日志,复制到从库 replay 同样耗 CPU;分批后单次耗时降至 ms 级 |
| data-sync 连接池(单节点) | 51 | ≤5 | 51 连接长期 idle 占资源,任务触发时 51 路并发 SCAN;缩池后即使误触发影响面也可控 |
| 扫库方式 | SCAN MATCH t_wip_detail:* 全量 |
增量索引 / DB 同步 / RediSearch | 3580 万 key 全量 SCAN 即使优化参数仍需数小时且周期性冲击;增量方案避免重复扫库 |
3.3 验证命令
| 验证项 | 当前值(排查时) | 期望(止血/改造后) |
|---|---|---|
| 7001 进程 CPU | 240.3 9.1% / 240.4 8.7% | burst 消失后 ≤5%(常态);无 SCAN 时接近读负载基线 |
| 活跃 SCAN 连接 | burst 时 ≥15,平时 0 | 0 |
| data-sync 连接数 | 51/节点 | 止血后 0;改造后 ≤5/节点 |
| 复制状态 | up,offset 一致 |
保持 up,offset 一致 |
CPU 回落(两台各执行)
timeout 5 bash -c 'PID=$(ps -eo pid,cmd | awk "/[r]edis-server.*:7001/{print \$1; exit}"); ps -p "$PID" -o pid,pcpu,pmem,cmd --no-headers'
SCAN 是否仍在运行
redis-cli -a redis_pass -p 7001 CLIENT LIST | grep -c "cmd=scan"
data-sync 连接数
redis-cli -a redis_pass -p 7001 CLIENT LIST | grep -c "name=swd-mes-data-sync"
复制仍正常
redis-cli -a redis_pass -p 7001 INFO replication | egrep "master_link_status|master_sync_in_progress|slave_repl_offset|master_repl_offset"
4. 后续预防措施
开发侧
| 参数 / 规范 | 当前值 | 建议值 | 原因 / 后果 |
|---|---|---|---|
| SCAN 目标 | 7001 从库 | 禁止扫从库;改扫主库或 DB | 从库承担生产读流量,SCAN 直接导致 CPU 告警 |
SCAN COUNT |
20000 | 100~500 | 见 §3.2 |
| SCAN 并发 | ≥15 | 1~2 | 见 §3.2 |
| RPUSH/SADD 单条参数 | 3 万~5.6 万 | ≤1000 | 见 §3.2;未改则慢日志持续出现 10s+ 级 replay |
| 连接池大小 | 51/节点 | ≤5/节点 | 见 §3.2 |
client name |
已设置 swd-mes-data-sync |
保持 | 便于 CLIENT LIST / CLIENT KILL 精准定位,缺失则无法快速止血 |
运维侧
| 参数 / 监控项 | 当前值 | 建议值 | 原因 / 后果 |
|---|---|---|---|
| CPU 告警阈值 | 未区分基线与 burst | 基线告警 ≥15% 持续 5min;burst ≥30% 持续 1min | 当前常态 8~9%,阈值过低会频繁误报;burst 需快速响应 |
cmdstat_scan 增速 |
累计 571 万次(无增速监控) | usec_per_call >500μs 或 calls 增速 >100/min 告警 |
提前发现扫库任务启动,避免等到 CPU 飙高 |
| 慢日志 SCAN 占比 | 近 30 条中 20 条 | >5 条/5min 告警 | 直接反映 data-sync 扫库行为 |
connected_clients |
1996 | >2500 告警 | 连接泄漏或 pool 膨胀会加剧资源争抢 |
instantaneous_ops_per_sec |
2147 | 基线 ±50% 突增告警 | 与 SCAN burst 叠加时 QPS 异常可辅助判断 |
| 变更流程 | 无 | data-sync 扫库/大批量任务需审批,低峰执行 | 避免再次未经通知的全库 SCAN |
| 应急脚本 | 无 | 保留按 name 的 CLIENT KILL 脚本并演练 |
SCAN 爆发时可 1 分钟内断开 51 连接止血 |
排查命令注意:pgrep -f "redis-server.*:7001" 在 bash -c 内会自匹配,改用:
ps -eo pid,cmd | awk '/[r]edis-server.*:7001/{print $1; exit}'
报告结论: 本次 CPU 告警为 swd-mes-data-sync 间歇性大规模 SCAN 叠加 超大 RPUSH 复制 replay 所致,Redis 集群及复制无故障。优先暂停/改造 data-sync 任务,并修复 batch RPUSH 写入方式。