igozhang

——

    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:700110.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:700110.10.240.4:7001 CPU 过高
    • 初步怀疑 Redis 故障;两台均为 Cluster 从节点
    • 排查时常态 CPU 约 8~9%(非持续 100%),疑为 间歇性 burst 触发告警

    2. 问题原因

    2.1 结论

    类型 说明
    主因 应用 swd-mes-data-sync10.10.240.133/135)对从库发起 多连接并发 SCANCOUNT=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 场景下选用 多连接并发 SCANCOUNT=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μscalls 增速 >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
    应急脚本 保留按 nameCLIENT 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 写入方式。

    MP3