Redis 故障排查与应急手册:从理论到实战
场景:线上 Redis 集群出现性能抖动、连接异常、数据丢失等问题时的快速响应指南
一、故障分级与响应策略
在深入技术细节之前,先建立故障分级意识:
| 级别 | 现象 | 响应时间 | 核心目标 |
|---|---|---|---|
| P0 | 集群完全不可用,业务中断 | 5分钟内 | 快速恢复服务 |
| P1 | 主节点宕机,自动切换中 | 15分钟内 | 确保高可用生效 |
| P2 | 性能下降,延迟升高 | 30分钟内 | 定位根因并优化 |
| P3 | 监控告警,指标异常 | 2小时内 | 预防性处理 |
二、核心排查工具箱
2.1 必知必会的监控指标
bash
# 实时查看关键指标
redis-cli INFO stats
redis-cli INFO memory
redis-cli INFO replication
redis-cli INFO clients
黄金指标清单:
| 指标 | 健康阈值 | 异常含义 |
|---|---|---|
used_memory / maxmemory |
< 85% | 内存即将耗尽,可能触发驱逐 |
instantaneous_ops_per_sec |
视业务而定 | 突增可能意味着热 Key 或攻击 |
connected_clients |
< 10000 | 连接数过多,可能连接泄漏 |
rejected_connections |
0 | 连接被拒绝,检查 maxclients |
keyspace_hits / keyspace_misses |
命中率 > 90% | 缓存失效严重 |
latest_fork_usec |
< 100000 | Fork 耗时过长会阻塞主线程 |
2.2 慢查询分析
bash
# 设置慢查询阈值(单位:微秒)
redis-cli CONFIG SET slowlog-log-slower-than 10000
# 查看最近 10 条慢查询
redis-cli SLOWLOG GET 10
# 重置慢查询日志
redis-cli SLOWLOG RESET
慢查询分析要点:
- 关注
KEYS *、FLUSHALL、HGETALL等全量操作 - 检查是否有
O(N)命令操作大 Key - 复杂 Lua 脚本的执行时间
2.3 大 Key 扫描(线上慎用)
bash
# 安全扫描大 Key(--bigkeys 是采样统计,非精确值)
redis-cli --bigkeys
# 更精确但需要遍历(低峰期使用)
redis-cli --memkeys-samples 1000
三、六大经典故障场景与应急方案
场景一:Redis 内存飙升 / OOM
现象 :used_memory 持续增长,最终触发 OOM 或大量 Key 被驱逐
排查步骤:
bash
# 1. 查看内存使用详情
redis-cli INFO memory
# 2. 查看 Key 的内存分布
redis-cli --bigkeys
# 3. 检查内存策略
redis-cli CONFIG GET maxmemory-policy
常见根因:
| 根因 | 识别方法 | 解决方案 |
|---|---|---|
| 缓存未设置过期时间 | redis-cli INFO keyspace 查看 expires |
添加 TTL,清理无用数据 |
| 大 Key 问题 | --bigkeys 发现单个 Key > 1MB |
拆分 Hash,使用 HSCAN 分批读取 |
| 内存碎片率过高 | mem_fragmentation_ratio > 1.5 |
重启实例或启用 activedefrag |
| 写入缓冲区堆积 | client-output-buffer-limit 超限 |
调整缓冲区限制,优化消费速度 |
应急操作:
bash
# 紧急设置内存上限(防止系统 OOM)
redis-cli CONFIG SET maxmemory 8gb
# 调整驱逐策略为 volatile-lru(优先淘汰有过期时间的 Key)
redis-cli CONFIG SET maxmemory-policy volatile-lru
# 手动删除大 Key(使用 UNLINK 非阻塞删除)
redis-cli UNLINK big_hash_key
# 开启主动碎片整理(Redis 4.0+)
redis-cli CONFIG SET activedefrag yes
场景二:Redis 连接数爆满
现象 :connected_clients 接近 maxclients,新连接被拒绝
排查步骤:
bash
# 查看当前连接数
redis-cli INFO clients
# 查看连接来源
redis-cli CLIENT LIST | grep -E "(addr|name|age|idle)"
# 统计各 IP 连接数
redis-cli CLIENT LIST | awk -F'=' '/addr=/{print $2}' | cut -d':' -f1 | sort | uniq -c | sort -rn | head -20
常见根因:
| 根因 | 识别特征 | 解决方案 |
|---|---|---|
| 连接池配置不当 | 单服务节点连接数异常高 | 调整连接池 maxTotal 和 minIdle |
| 连接泄漏 | idle 时间长但未被释放 |
检查代码中 close() 是否被调用 |
| 短连接风暴 | 大量 age 很小的连接 |
使用连接池,避免频繁创建连接 |
| 客户端 Bug | 特定版本客户端异常 | 升级客户端版本 |
应急操作:
bash
# 临时提升最大连接数
redis-cli CONFIG SET maxclients 20000
# 踢掉空闲连接(危险操作,谨慎使用)
redis-cli CLIENT KILL TYPE normal IDEL 600
# 查看阻塞客户端
redis-cli CLIENT LIST | grep -i blocked
Java 连接池优化示例:
java
// Jedis 连接池配置
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100); // 最大连接数
config.setMaxIdle(50); // 最大空闲连接
config.setMinIdle(10); // 最小空闲连接
config.setTestOnBorrow(true); // 借用时验证
config.setTestWhileIdle(true); // 空闲时验证
// 关键:确保连接正确关闭
try (Jedis jedis = pool.getResource()) {
jedis.get("key");
} // 自动归还连接
场景三:Redis 主从复制中断
现象 :从节点状态为 master_link_status:down,数据不同步
排查步骤:
bash
# 查看复制状态
redis-cli INFO replication
# 查看从节点日志
tail -f /var/log/redis/redis-server.log
# 检查主节点复制积压缓冲区
redis-cli INFO stats | grep -E "(master_repl_offset|repl_backlog)"
常见根因:
| 根因 | 识别方法 | 解决方案 |
|---|---|---|
| 网络闪断 | 日志中出现 Connection timed out |
调整 repl-timeout 和 repl-ping-replica-period |
| 复制缓冲区不足 | master_repl_offset 差异大 |
增大 repl-backlog-size |
| 从节点重启 | master_link_status 为 down |
自动重连或手动 SLAVEOF |
| 主节点 RDB 生成失败 | 日志中 Can't save in background |
检查磁盘空间和 maxmemory |
应急操作:
bash
# 从节点强制重新同步(会清空从节点数据)
redis-cli SLAVEOF NO ONE
redis-cli SLAVEOF master_host master_port
# 调整复制超时时间
redis-cli CONFIG SET repl-timeout 120
redis-cli CONFIG SET repl-ping-replica-period 30
# 增大复制积压缓冲区(默认 1MB,建议 100MB+)
redis-cli CONFIG SET repl-backlog-size 104857600
场景四:Redis 性能急剧下降
现象:RT 从 ms 级上升到秒级,甚至超时
排查步骤:
bash
# 查看 CPU 使用率(Redis 是单线程,CPU 100% 意味着忙碌)
top -p $(pgrep redis-server)
# 查看命令统计
redis-cli INFO commandstats
# 实时监控命令执行
redis-cli MONITOR | head -100 # 注意:生产环境慎用,性能开销大
# 查看延迟监控
redis-cli --latency-history -i 1
常见根因:
| 根因 | 识别方法 | 解决方案 |
|---|---|---|
| 热 Key 访问 | instantaneous_ops_per_sec 突增 |
本地缓存 + Key 拆分(如 key:{hash}) |
| 大 Key 操作 | SLOWLOG 中出现 HGETALL、SMEMBERS |
拆分数据,使用 HSCAN、SSCAN |
| Fork 阻塞 | latest_fork_usec 过大 |
控制实例内存大小,使用磁盘less复制 |
| AOF 刷盘阻塞 | aof_delayed_fsync 增加 |
调整 appendfsync 策略 |
| 持久化竞争 | 备份或 AOF rewrite 期间 | 避免高峰期执行 BGSAVE |
应急操作:
bash
# 临时关闭 AOF(数据安全与性能的权衡)
redis-cli CONFIG SET appendonly no
# 调整 AOF 刷盘策略
redis-cli CONFIG SET appendfsync everysec # 或 no(最高性能)
# 禁用 THP(透明大页)减少 Fork 耗时
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 使用管道批量操作(减少 RTT)
redis-cli --pipe < commands.txt
热 Key 解决方案架构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │────▶│ Local Cache │────▶│ Redis │
│ │ │ (Caffeine/ │ │ Cluster │
│ │◀────│ Guava) │◀────│ │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
└────────── 异步更新缓存 ◀────────────┘
场景五:Redis 脑裂(Split-Brain)
现象:主从切换后,旧主节点仍在接受写请求,导致数据不一致
排查步骤:
bash
# 检查当前主从拓扑
redis-cli -h old_master INFO replication
redis-cli -h new_master INFO replication
# 检查 Sentinel 日志
tail -f /var/log/redis/sentinel.log
预防措施:
bash
# 配置 min-slaves 机制(Redis 2.8+)
redis-cli CONFIG SET min-slaves-to-write 1
redis-cli CONFIG SET min-slaves-max-lag 10
# 配置 Sentinel(自动故障转移)
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
应急操作:
bash
# 强制旧主节点降级为从节点
redis-cli -h old_master SLAVEOF new_master_ip new_master_port
# 如果数据已不一致,需要权衡:保留旧数据还是新数据
# 方案1:保留新主节点数据,丢弃旧主节点数据
redis-cli -h old_master DEBUG SEGFAULT # 强制崩溃重启,清空数据后同步
# 方案2:保留旧主节点数据,手动合并(复杂,需业务配合)
场景六:缓存雪崩 / 击穿 / 穿透
现象:大量请求直达数据库,DB 压力骤增
| 问题类型 | 现象 | 解决方案 |
|---|---|---|
| 缓存雪崩 | 大量 Key 同时过期 | 随机 TTL、多级缓存、熔断降级 |
| 缓存击穿 | 热点 Key 过期瞬间高并发 | 互斥锁、逻辑过期、永不过期 |
| 缓存穿透 | 查询不存在的 Key | 布隆过滤器、空值缓存、参数校验 |
应急操作代码示例:
java
// 缓存击穿防护:互斥锁
public String getWithLock(String key) {
String value = redis.get(key);
if (value == null) {
// 获取分布式锁
if (redis.setnx("lock:" + key, "1", 10)) {
try {
value = db.get(key);
redis.setex(key, 3600, value);
} finally {
redis.del("lock:" + key);
}
} else {
// 等待后重试
Thread.sleep(100);
return getWithLock(key);
}
}
return value;
}
// 缓存穿透防护:布隆过滤器
public String getWithBloomFilter(String key) {
if (!bloomFilter.mightContain(key)) {
return null; // 直接返回,不查 DB
}
// 正常查询缓存和 DB
}
四、标准化应急响应流程
┌─────────────────┐
│ 收到告警通知 │
└────────┬────────┘
▼
┌─────────────────┐ 是 ┌─────────────────┐
│ 服务是否可用? │────────────▶│ 立即切换流量 │
│ │ │ 到备用集群 │
└────────┬────────┘ └─────────────────┘
│ 否
▼
┌─────────────────┐
│ 查看监控大盘 │
│ (内存/CPU/连接) │
└────────┬────────┘
▼
┌─────────────────┐
│ 定位故障场景 │
│ (匹配六大场景) │
└────────┬────────┘
▼
┌─────────────────┐ 是 ┌─────────────────┐
│ 是否需要应急? │────────────▶│ 执行应急操作 │
│ │ │ (保留现场日志) │
└────────┬────────┘ └─────────────────┘
│ 否
▼
┌─────────────────┐
│ 根因分析修复 │
│ 更新故障案例库 │
└─────────────────┘
五、预防性建设清单
5.1 监控告警体系
yaml
# Prometheus + AlertManager 配置示例
groups:
- name: redis
rules:
- alert: RedisMemoryHigh
expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.85
for: 5m
annotations:
summary: "Redis 内存使用率超过 85%"
- alert: RedisConnectionsHigh
expr: redis_connected_clients / redis_config_maxclients > 0.8
for: 2m
annotations:
summary: "Redis 连接数超过 80%"
- alert: RedisReplicationLag
expr: redis_master_link_up == 0
for: 1m
annotations:
summary: "Redis 主从复制中断"
5.2 配置最佳实践
bash
# redis.conf 核心配置
maxmemory 8gb
maxmemory-policy allkeys-lru
maxclients 10000
# 持久化配置(根据业务选择)
appendonly yes
appendfsync everysec
no-appendfsync-on-rewrite yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 慢查询配置
slowlog-log-slower-than 10000
slowlog-max-len 128
# 客户端输出缓冲区
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
5.3 定期演练事项
- 月度:执行主从切换演练,验证 Sentinel/Cluster 自动故障转移
- 季度 :模拟大 Key 删除,验证
UNLINK非阻塞效果 - 半年:全量数据恢复演练,验证 RDB/AOF 文件可用性
六、总结:故障排查心法
"监控先行,日志为凭,工具辅助,经验兜底"
- 保持冷静:先确认服务可用性,再深入排查
- 现场保护 :故障时的
INFO、MONITOR、SLOWLOG及时保存 - 变更关联:故障前是否有发布、配置变更、扩容操作
- 渐进修复:优先止损,再根治,避免二次故障
- 复盘归档:每个故障都是案例,更新到团队知识库
延伸阅读:
本文基于 Redis 6.x/7.x 版本编写,部分命令在低版本可能略有差异。生产环境操作前,请务必在测试环境验证。