Redis 故障的特点是"快灾难":一旦出问题往往瞬间影响整个业务,因为它通常是关键路径。三大杀手:内存满、大 key 阻塞、缓存雪崩。每个都能让线上立刻挂掉。Redis 单线程的本质决定了——任何一个慢操作都会卡住所有请求。
排障前先 INFO 拿全局视图,再针对性深挖。生产环境慎用 KEYS / FLUSHALL / DEBUG SLEEP。
OOM command not allowed when 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 | 有过期时间的 key | LRU |
volatile-lfu | 有过期时间的 key | LFU |
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)
allkeys-lru。
# 方法 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 | 压缩或拆为多个 |
| Hash | field > 5000 | 按 hash(id)%N 分桶 |
| List | length > 5000 | 分页存储 |
| Set | members > 5000 | 哈希分片 |
| ZSet | members > 5000 | 按业务维度拆分 |
KEYS * 在大库上会阻塞几秒到几十秒,生产严禁使用。改用 SCAN cursor MATCH pattern COUNT 100 渐进遍历。
# 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 / DEL | O(1) | 安全 |
HGET / HSET | O(1) | 安全 |
KEYS * | O(N) | 禁用 |
HGETALL | O(N) | 小 hash 可用,大 hash 危险 |
SMEMBERS | O(N) | 同上 |
LRANGE 0 -1 | O(N) | 同上 |
SORT | O(N+M log M) | 慎用 |
FLUSHALL / FLUSHDB | O(N) | 禁用,改用 ASYNC |
SUNIONSTORE | O(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 ASYNC 后台清理(4.0+)。FLUSHALL ASYNC 同理。
| 问题 | 定义 | 触发场景 |
|---|---|---|
| 缓存穿透 | 查不存在的 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,避免单点
# 主库执行 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 后传 RDB | RDB 大 / 网络慢 |
| 增量同步 | 主库传 backlog | backlog 太小,只能全量 |
| 持续复制 | 主推送写命令 | 网络中断 / 主库 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
| 对比项 | RDB | AOF |
|---|---|---|
| 触发方式 | 定时 / 手动 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
# 看连接数 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 分钟空闲断开
# 看集群状态 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