REDIS · IN-MEMORY DATABASE

Redis
故障排查手册

THE OPS FIELD MANUAL
Memory · Big Key · Persistence · Cluster
127.0.0.1:6379> INFO
maxmemory big key slowlog replication persistence cluster 缓存三连

Redis 故障的特点是"快灾难":一旦出问题往往瞬间影响整个业务,因为它通常是关键路径。三大杀手:内存满、大 key 阻塞、缓存雪崩。每个都能让线上立刻挂掉。Redis 单线程的本质决定了——任何一个慢操作都会卡住所有请求。

Redis 诊断命令速查

排障前先 INFO 拿全局视图,再针对性深挖。生产环境慎用 KEYS / FLUSHALL / DEBUG SLEEP。

INFO
全局状态,分 section
CLIENT LIST
所有连接详情
SLOWLOG GET
慢查询日志
MEMORY USAGE
单 key 内存占用
--bigkeys
扫描大 key
--hotkeys
热点 key 扫描
--latency
延迟监控
MONITOR
实时命令流(慎用)
01

内存满了 — maxmemory 触发淘汰

EVICTION AND OOM
高频 致命
  • 写入报错 OOM command not allowed when used memory > 'maxmemory'
  • 或者 key 莫名消失(被淘汰策略干掉)
  • INFO memory 看到 used_memory 接近 maxmemory
# 看内存使用
INFO memory

# 关键字段
# used_memory_human          实际使用
# used_memory_peak_human     历史峰值
# maxmemory_human            上限
# mem_fragmentation_ratio    内存碎片率(>1.5 严重)
# maxmemory_policy           淘汰策略
策略淘汰对象规则
noeviction不淘汰满了直接报错(默认)
allkeys-lru所有 key最近最少使用
allkeys-lfu所有 key最不常用(4.0+)
allkeys-random所有 key随机
volatile-lru有过期时间的 keyLRU
volatile-lfu有过期时间的 keyLFU
volatile-random有过期时间的 key随机
volatile-ttl有过期时间的 key剩余 TTL 最小
# 缓存场景(允许丢数据,推荐)
maxmemory 8gb
maxmemory-policy allkeys-lru

# 兼做存储 + 缓存场景(只淘汰有 TTL 的)
maxmemory 8gb
maxmemory-policy volatile-lru

# 纯存储场景(数据不能丢)
maxmemory 8gb
maxmemory-policy noeviction
# 满了报错,业务必须提前感知
# 1. 动态调大 maxmemory(无需重启)
CONFIG SET maxmemory 16gb

# 2. 临时改为 LRU 让 Redis 自己淘汰
CONFIG SET maxmemory-policy allkeys-lru

# 3. 找出占用大户,见 Case 02

# 4. 内存碎片严重(碎片率 > 1.5)
CONFIG SET activedefrag yes    # 自动碎片整理(4.0+)
MEMORY PURGE                  # 主动释放(jemalloc)
noeviction 是默认 · 也是最容易踩的坑——内存满了直接拒写,业务雪崩。生产环境根据用途显式设置策略,缓存场景一律用 allkeys-lru
02

大 key 定位与清理

BIG KEY HUNTING
高频 致命
  • 占用大量内存,可能单 key 上 GB
  • 访问慢:GET 一个 100MB 的 String 阻塞几百毫秒
  • DEL 删除大 key 直接卡死(同步删除)
  • 主从同步时,大 key 阻塞复制
  • 集群迁移槽时,大 key 卡住整个迁移
# 方法 A:redis-cli 自带(线上安全,采样扫描)
redis-cli -h <host> -p 6379 --bigkeys

# 方法 B:--memkeys (按内存占用扫描,6.0+)
redis-cli --memkeys

# 方法 C:控制频率,降低对线上影响
redis-cli --bigkeys -i 0.1
# -i 0.1 每 100 次 SCAN 暂停 0.1 秒

# 方法 D:rdb_tools 离线分析(最准)
rdb --commands memory dump.rdb | sort -t, -k4 -n | tail
# 内存占用
MEMORY USAGE bigkey
# 返回字节数

# 看类型和元素数
TYPE bigkey
STRLEN bigkey               # String 字节数
LLEN bigkey                 # List 长度
HLEN bigkey                 # Hash 字段数
SCARD bigkey                # Set 元素数
ZCARD bigkey                # Sorted Set 元素数
# 错误:DEL 同步删除,大 key 会阻塞几秒
DEL bigkey

# 正确:UNLINK 异步删除(4.0+)
UNLINK bigkey
# 立即返回,后台慢慢释放内存

# 渐进删除大 Hash
# 不要 DEL,用 HSCAN + HDEL 分批
HSCAN bighash 0 COUNT 100
# 拿到字段后,分批 HDEL

# 渐进删除大 List
LTRIM biglist 0 999    # 保留前 1000,其余删除
# 重复执行直到为空
类型警戒阈值拆分策略
String> 10KB压缩或拆为多个
Hashfield > 5000按 hash(id)%N 分桶
Listlength > 5000分页存储
Setmembers > 5000哈希分片
ZSetmembers > 5000按业务维度拆分
KEYS 是禁词 · KEYS * 在大库上会阻塞几秒到几十秒,生产严禁使用。改用 SCAN cursor MATCH pattern COUNT 100 渐进遍历。
03

慢查询定位 — 单线程被卡死

SLOWLOG ANALYSIS
高频 必会
  • 应用调用 Redis 偶发性 timeout
  • INFO 看到 instantaneous_ops_per_sec 突然下跌
  • 客户端报"读超时"但 Redis 看着没死
# 1. 配置慢查询阈值(微秒,默认 10000us=10ms)
CONFIG SET slowlog-log-slower-than 10000
CONFIG SET slowlog-max-len 1000

# 2. 看慢查询
SLOWLOG GET 20
# 返回:[id, timestamp, 执行时间(us), 命令]

# 3. 清空慢日志
SLOWLOG RESET

# 4. 实时延迟监控
redis-cli --latency
redis-cli --latency-history      # 历史
redis-cli --latency-dist         # 分布图
命令复杂度风险
GET / SET / DELO(1)安全
HGET / HSETO(1)安全
KEYS *O(N)禁用
HGETALLO(N)小 hash 可用,大 hash 危险
SMEMBERSO(N)同上
LRANGE 0 -1O(N)同上
SORTO(N+M log M)慎用
FLUSHALL / FLUSHDBO(N)禁用,改用 ASYNC
SUNIONSTOREO(N)大集合慢
# 错误:阻塞整个 Redis
KEYS user:*

# 正确:渐进式遍历
SCAN 0 MATCH user:* COUNT 100
# 返回 (新游标, [keys...])
# 重复调用直到游标返回 0

# 同理
HSCAN hashkey 0 MATCH field:* COUNT 100
SSCAN setkey 0 COUNT 100
ZSCAN zsetkey 0 COUNT 100
FLUSHDB 也要异步 · 大库直接 FLUSHDB 会阻塞,用 FLUSHDB ASYNC 后台清理(4.0+)。FLUSHALL ASYNC 同理。
04

缓存三连 — 穿透 / 击穿 / 雪崩

PENETRATION / BREAKDOWN / AVALANCHE
高频 必会
问题定义触发场景
缓存穿透查不存在的 key,缓存和 DB 都没有恶意攻击,大量查不存在的 ID
缓存击穿热点 key 过期瞬间,大量请求穿透到 DB某个爆款商品缓存过期
缓存雪崩大量 key 同时过期,或 Redis 宕机同一时间批量预热的缓存到期
# 方案 A:空值也缓存(简单粗暴)
SET user:not_exist:99999 "" EX 300
# 5 分钟内同样的请求直接返回空

# 方案 B:布隆过滤器(Bloom Filter,生产推荐)
# 用 RedisBloom 模块
BF.ADD user_filter user_id_123
BF.EXISTS user_filter user_id_99999
# 返回 0 → 一定不存在,直接拒绝
# 返回 1 → 可能存在,继续查
# 思路:多个请求竞争锁,只让一个去查 DB,其他等

# 1. 尝试拿锁
SET lock:product:123 "holder1" NX EX 10
# NX 不存在才设,EX 10 秒过期防死锁

# 2. 拿到锁的查 DB 并写缓存
# 3. 没拿到的稍等几十毫秒,重读缓存

# 互斥锁更优雅的方案:Redisson(Java)
# 或者使用 Redis 的 SETNX + Lua 实现
# 方案 A:过期时间加随机值,避免同时过期
SET user:123 "data" EX 3600   # 错误:所有都 3600
SET user:123 "data" EX 3600+RANDOM(0,600)  # 正确:打散

# 方案 B:多级缓存(本地 + Redis)
# Caffeine/Guava 本地一级,Redis 二级

# 方案 C:服务降级 + 熔断
# Redis 挂了,直接返回默认值/兜底数据
# 用 Sentinel / Hystrix 熔断

# 方案 D:Redis 高可用
# 主从 + 哨兵 / Cluster,避免单点
预防 > 治疗 · 上线前必须:① 给所有缓存 key 设合理的随机过期时间;② 关键业务做降级方案;③ Redis 务必高可用(主从 + 哨兵或 Cluster);④ 用 SCAN 替换所有 KEYS;⑤ 大 key 定期巡检。
05

主从同步异常

REPLICATION FAILURE
中频 进阶
# 主库执行
INFO replication
# 关键字段
# role:master
# connected_slaves:2
# slave0:ip=10.0.0.2,state=online,offset=12345
# master_repl_offset:12345

# 从库执行
INFO replication
# role:slave
# master_link_status:up         (关键!)
# master_last_io_seconds_ago:1  (上次通信)
# master_sync_in_progress:0
阶段步骤常见故障
建立连接从库 SLAVEOF 主库密码错 / 网络不通
全量同步主库 bgsave 后传 RDBRDB 大 / 网络慢
增量同步主库传 backlogbacklog 太小,只能全量
持续复制主推送写命令网络中断 / 主库 OOM
# 增大复制 backlog(主库),减少全量同步
repl-backlog-size 256mb            # 默认 1mb 不够用
repl-backlog-ttl 3600

# 复制超时
repl-timeout 60

# 主库内存上限(必须小于 maxmemory)
# 因为复制 buffer 也占内存
client-output-buffer-limit replica 256mb 64mb 60

# 关闭从库写(必加)
replica-read-only yes
# 1. master_link_status:down
# → 检查网络、密码、防火墙
redis-cli -h <master> -p 6379 ping

# 2. 反复全量同步
# → backlog 太小,增大 repl-backlog-size

# 3. 从库一直 loading
# → RDB 太大,从库还在加载,等待即可

# 4. 主从数据不一致
# → 检查从库是否被写过(replica-read-only yes)
# → 强制全量同步
DEBUG RELOAD
主从延迟的隐形成本 · 主写从读架构下,主库写完立即读从库可能读不到。需要根据业务:重要操作走主库读,允许延迟的走从库读。或者用 WAIT 命令同步等待。
06

持久化卡顿 — RDB / AOF 问题

PERSISTENCE PERFORMANCE ISSUES
中频 进阶
对比项RDBAOF
触发方式定时 / 手动 BGSAVE每条写命令追加
文件大小小,二进制压缩大,文本
恢复速度慢(回放命令)
数据安全可能丢几分钟最多丢 1 秒
性能影响bgsave fork 短暂阻塞每次写都有开销
# 看上次 bgsave 状态
INFO persistence
# rdb_last_bgsave_status:ok
# rdb_last_bgsave_time_sec:5    上次耗时
# rdb_changes_since_last_save:12345  累积改动

# 看 fork 耗时(rdb_last_cow_size 内存复制大小)
INFO stats
# latest_fork_usec:50000   上次 fork 耗时(微秒)
appendonly yes
appendfilename "appendonly.aof"

# 三种 sync 策略
appendfsync everysec       # 推荐:每秒 fsync,最多丢 1 秒
# appendfsync always       同步 fsync,慢但最安全
# appendfsync no           由 OS 控制,快但最不安全

# AOF 重写触发条件
auto-aof-rewrite-percentage 100   # 比上次大 100% 触发
auto-aof-rewrite-min-size 64mb    # 最小 64MB 才触发

# 重写期间不要 fsync(避免双重 IO)
no-appendfsync-on-rewrite yes
# 1. 关闭透明大页(必做!)
# /sys/kernel/mm/transparent_hugepage/enabled
# echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 否则 fork 时 COW 内存翻倍

# 2. overcommit_memory
# sysctl -w vm.overcommit_memory=1
# 允许 fork 时内存超分,避免 fork 失败

# 3. 主库可关闭持久化,从库做(常见架构)
save ""
appendonly no
# 从库:save 默认 / appendonly yes
fork 是大内存的杀手 · Redis bgsave 用 fork() 创建子进程,Linux 用 COW(写时复制),但 fork 本身要复制页表,几十 GB 实例 fork 耗时可能几百毫秒——这段时间 Redis 完全阻塞。大实例建议:① 分片 ② 主库不持久化 ③ 关闭透明大页。
07

连接数打满

MAX CLIENT CONNECTIONS
中频 入门
# 看连接数
INFO clients
# connected_clients:5000
# maxclients:10000

# 看每个连接详情(谨慎,可能输出很多)
CLIENT LIST

# 按 IP 统计连接数
CLIENT LIST | awk '{print $2}' | cut -d= -f2 | cut -d: -f1 | sort | uniq -c | sort -rn
# 动态调大(默认 10000)
CONFIG SET maxclients 20000

# 系统层面 ulimit 也要够
# Redis 自身需要 maxclients + 32 个 fd
ulimit -n 65535
# 看每个连接的空闲时间
CLIENT LIST
# idle=N 字段表示空闲秒数

# 关闭空闲超过 60s 的连接
CLIENT KILL ID <client-id>
CLIENT KILL ADDR 10.0.0.1:54321

# 配置自动断开空闲连接(慎用)
CONFIG SET timeout 300    # 5 分钟空闲断开
连接池配置参考 · Java 应用用 Lettuce/Jedis,单实例配置 max-total=50, max-idle=20。微服务多实例时,总连接 = 实例数 × max-total,确保不超过 maxclients。
08

Cluster 脑裂与故障转移

CLUSTER FAILOVER ISSUES
低频 致命
# 看集群状态
CLUSTER INFO
# cluster_state:ok
# cluster_slots_ok:16384
# cluster_known_nodes:6

# 看节点列表
CLUSTER NODES
# 字段:id ip:port@gossip-port flags master ping pong epoch state slots
# flags 关键:master/slave/fail?/handshake/noaddr

# 看槽分布
CLUSTER SLOTS

网络分区导致 cluster 一部分认为某 master 死了,提升 slave,另一部分仍在用原 master。两边都写入,数据冲突。

# 关键配置:防脑裂

# 主库失去 N 个从库连接后停止写入(避免脑裂双写)
min-replicas-to-write 1
min-replicas-max-lag 10

# Cluster 选举超时
cluster-node-timeout 15000

# 是否允许只有部分槽可用时继续工作
cluster-require-full-coverage no
# no:部分节点挂了,其他槽继续服务
# yes(默认):一个槽挂了,整个 cluster 拒绝服务
# 在 slave 上执行,提升为 master
CLUSTER FAILOVER

# 强制(master 已挂的紧急场景)
CLUSTER FAILOVER FORCE

# 接管(更激进,跳过半数同意)
CLUSTER FAILOVER TAKEOVER
# 查看槽状态
CLUSTER COUNTKEYSINSLOT 1234
CLUSTER GETKEYSINSLOT 1234 100

# 用 redis-cli 重新分片
redis-cli --cluster reshard 10.0.0.1:6379

# 修复槽不一致
redis-cli --cluster fix 10.0.0.1:6379

# 健康检查
redis-cli --cluster check 10.0.0.1:6379
Cluster 设计原则 · ① 至少 3 主 3 从 ② 主从必须跨机器/可用区 ③ cluster-node-timeout 设置合理(太短易误判,太长故障切换慢) ④ 监控 cluster_state 不为 ok 立即告警。
PREVIOUS
← MySQL 故障排查手册