外观
Redis 常见故障处置手册
按现象查,不按原因查。每个case给:看什么命令、输出怎么读、最后怎么修。
案例 1:连接数打满,新连接被拒
现象:应用侧 JedisConnectionException: Could not get a resource from the pool,或者连上就断。
bash
redis-cli -a '密码' info clients
redis-cli -a '密码' config get maxclients
redis-cli -a '密码' info stats | grep rejected_connections
ulimit -n
cat /proc/$(pgrep -f 'redis-server .*6379' | head -1)/limits | grep 'open files'输出:
text
connected_clients:9994
cluster_connections:0
maxclients:10000
blocked_clients:0
1) "maxclients"
2) "10000"
rejected_connections:3812
1048576
Max open files 65535 65535 files解读:
connected_clients已经顶到maxclients。rejected_connections:3812表示内核已经拒了 3812 次。ulimit -n是 shell 的 1048576,但进程实际是 65535——systemd unit 里的LimitNOFILE=65535覆盖了它。
注意:maxclients 会自动被压到 LimitNOFILE - 32。所以你把 maxclients 改成 20000,unit 里还是 65535,实际生效仍是 65503。
处置顺序:
bash
# 1. 先看是谁,按客户端名分组
redis-cli -a '密码' client list | awk '{for(i=1;i<=NF;i++) if($i ~ /^name=/) print $i}' | sort | uniq -c | sort -rn | head
# 2. 看这些连接是不是都空转(idle 很大 = 泄漏)
redis-cli -a '密码' client list | awk -F'[ =]' '{print $0}' | grep 'name=order-service' | head -3
# 3. 临时扩容(治标)
redis-cli -a '密码' config set maxclients 20000输出(第 3 步):
text
8600 name=order-service
900 name=coupon-job
494 name=定论:order-service 占了 8600 条,而它的池 max-active 只有 32、部署了 8 个实例 → 32 × 8 = 256,多出来的 8300 条是连接泄漏。
为什么池也会泄漏:拿到 RedisConnection 后没 close(),或用了 RedisTemplate.execute(callback) 在 callback 里抛异常导致连接没归还(这个通常框架会兜底,但手写 factory.getConnection() 就必须自己关)。
java
// 有泄漏风险的写法
RedisConnection c = factory.getConnection();
c.set("k".getBytes(), "v".getBytes());
// 忘了 c.close(),池会一直 new 新连接
// 安全写法
try (RedisConnection c = factory.getConnection()) {
c.set("k".getBytes(), "v".getBytes());
}还要改 systemd:
ini
LimitNOFILE=100000bash
systemctl daemon-reload && systemctl restart redis-6379
redis-cli -a '密码' config set maxclients 90000
redis-cli -a '密码' config rewrite别用 client kill 直接清场
redis-cli client kill type normal 会把所有业务长连接一次断掉,瞬间引发应用侧重连风暴。正确姿势是先修应用再滚动重启。
案例 2:BGSAVE 卡住,主线程周期性停顿
现象:每隔几十分钟出现 200~800ms 的请求毛刺,日志里有 Background saving started。
bash
redis-cli -a '密码' info persistence | grep -E 'rdb_bgsave_in_progress|rdb_last_bgsave_time_sec|rdb_last_cow_size'
redis-cli -a '密码' info stats | grep latest_fork_usec
redis-cli -a '密码' latency history fork
grep -i 'transparent huge' /var/log/redis/6379.log
free -h输出:
text
rdb_bgsave_in_progress:0
rdb_last_bgsave_time_sec:14
rdb_last_cow_size:3221225472
latest_fork_usec:612000
1) 1) "fork"
2) (integer) 1761548102
3) (integer) 612
4) (integer) 891
* Transparent huge pages function is enabled.定论:
latest_fork_usec:612000= 612ms,fork 本身就把主线程卡住了。rdb_last_cow_size:3GB说明 fork 期间有 3GB 内存被复制,内存压力大。- THP 是开的,加剧 fork 延迟。
三步修:
bash
# 1. 临时关 THP
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 2. 永久关(写进 unit)
# [Service]
# ExecStartPre=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled'
# 3. 降低快照频率,改到低峰
redis-cli -a '密码' config set save "3600 1 300 100"
redis-cli -a '密码' config rewrite更彻底的办法:
| 手段 | 效果 | 代价 |
|---|---|---|
| 关 THP | fork 快 30%~50% | 大页收益没了(Redis 本来也不该用) |
| 主从分担快照 | 从库 save,主库 save "" | 需要主从 |
| 换 SSD + 独立盘 | BGSAVE 写盘不抢业务 IO | 成本 |
| 拆实例 | 单实例内存降到 8G 以下 | 架构复杂 |
换 Redis 7 + repl-diskless-sync | 减少一次落盘 | 只对复制生效 |
THP 的告警只在启动时打一次
Transparent huge pages function is enabled 只在启动日志里出现。后来有人改了内核参数,日志不会更新。要自己查 /sys/kernel/mm/transparent_hugepage/enabled,方括号里的就是当前值。
案例 3:进程被 OOM killer 杀掉
现象:Redis 突然消失,systemd 显示 status=9/KILL,没有崩溃日志。
bash
systemctl status redis-6379 --no-pager
dmesg -T | grep -i -E 'out of memory|killed process' | tail -5
journalctl -k --since '1 hour ago' | grep -i 'killed process'输出:
text
● redis-6379.service - Redis In-Memory Data Store (port 6379)
Active: failed (Result: signal) since Tue 2026-10-27 18:02:11 CST
Main PID: 21874 (code=killed, signal=KILL)
[Tue Oct 27 18:02:10 2026] Out of memory: Killed process 21874 (redis-server) total-vm:8388608kB, anon-rss:8123456kB, file-rss:0kB定论:Redis 用了 7.7GB 匿名内存,被内核杀掉。
检查:
bash
redis-cli -a '密码' config get maxmemory
redis-cli -a '密码' config get maxmemory-policy
free -h
cat /proc/meminfo | grep -E 'MemTotal|MemAvailable|SwapTotal'输出:
text
1) "maxmemory"
2) "0"
1) "maxmemory-policy"
2) "noeviction"
total used free shared buff/cache available
Mem: 15Gi 14Gi 312Mi 1.0Gi 700Mi 210Mimaxmemory 0 = 无上限。这是根因。
修复:
bash
# 1. 设成物理内存的 60%~70%,给 fork/碎片留余量
redis-cli -a '密码' config set maxmemory 10gb
redis-cli -a '密码' config set maxmemory-policy allkeys-lru
redis-cli -a '密码' config rewrite
# 2. 给 unit 加保护,让内核优先杀别人
# [Service]
# OOMScoreAdjust=-900maxmemory 不要设成物理内存的 100%
maxmemory 只算数据集,不含 fork 的 COW、客户端缓冲区、复制积压。设成 100% 时,一次 BGSAVE 就可能触发系统 OOM。经验值:单实例 maxmemory ≤ 物理内存 × 0.6,并且容器 limit 要比它再大 30%。
如果容器里跑:
yaml
resources:
limits:
memory: 12Giini
maxmemory 8gb容器 limit 12G、Redis 上限 8G,中间 4G 留给 fork 和堆外。反例是 limit 8G + maxmemory 8G,BGSAVE 期间必被 OOMKilled。
案例 4:主从切换后业务写入旧主
现象:哨兵切了主,一部分应用报 READONLY,另一部分写进了旧主,两边数据不一致。
bash
# 1. 当前真正的主是谁
redis-cli -p 26379 sentinel get-master-addr-by-name mymaster
# 2. 每个实例自报角色
for h in 10.0.0.11 10.0.0.12 10.0.0.13; do echo -n "$h: "; redis-cli -h $h -a '密码' info replication | awk -F: '/^role/{print $2}' | tr -d '\r'; done
# 3. 旧主上是否有新主没有的 key
redis-cli -h 10.0.0.11 -a '密码' info replication | grep -E 'role|master_link_status'输出:
text
1) "10.0.0.12"
2) "6379"
10.0.0.11: slave
10.0.0.12: master
10.0.0.13: slave
role:slave
master_link_status:down定论:旧主 10.0.0.11 已经被降级,但它连不上新主(master_link_status:down),说明它仍在自己扛着旧数据,而业务里有一部分客户端还连着它。
三个层面的修:
1. 应用侧(根因)
yaml
# 用哨兵,不要写死 IP
spring:
data:
redis:
sentinel:
master: mymaster
nodes: 10.0.0.11:26379,10.0.0.12:26379,10.0.0.13:263792. 防脑裂
ini
# 主库上必须有这两项
min-replicas-to-write 1
min-replicas-max-lag 10网络分区后,旧主发现"没有健康从库",直接拒绝写入(返回 NOREPLICAS),从根上避免双写。
3. 修旧主
bash
# 旧主连不上新主,先查网络和密码
redis-cli -h 10.0.0.12 -a '密码' ping
tail -30 /var/log/redis/6379.log | grep -i 'master\|sync'旧主上"多出来"的数据会丢
旧主降级为从库后做全量同步,本地数据被清空。脑裂期间写进旧主的那部分数据,唯一的抢救方法是在同步前手工导出:
bash
redis-cli -h 旧主 -a '密码' --scan --pattern '*' > /tmp/oldmaster-keys.txt
# 再用 MIGRATE 或逐 key DUMP/RESTORE 搬到新主这一步必须在它被清空前做,一旦开始全量同步就来不及。
案例 5:Redis 变慢,但 CPU 和内存都正常
现象:P99 从 2ms 涨到 80ms,slowlog 里却没有长命令。
bash
redis-cli -a '密码' --latency
redis-cli --intrinsic-latency 5 # 必须在 Redis 所在机器上跑
redis-cli -a '密码' info memory | grep -E 'used_memory_human|used_memory_rss_human|mem_fragmentation_ratio'
free -h
vmstat 1 3输出:
text
min: 0, max: 41, avg: 1.28 (2345 samples)
Latency: 1.2 us average, 8.4 us max.
used_memory_human:2.04G
used_memory_rss_human:4.90G
mem_fragmentation_ratio:2.40
total used free shared buff/cache available
Mem: 15Gi 14Gi 180Mi 2.0Gi 500Mi 120Mi
Swap: 0B 0B 0B
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 2 0 184320 12800 512000 0 0 120 2048 12000 18000 38 6 50 6 0三种可能,逻辑上先排除:
--latency客户端侧高(41ms),--intrinsic-latency服务端低(8.4us)→ 网络/客户端问题,不是 Redis。mem_fragmentation_ratio:2.40→ 碎片严重,rss4.9G 但实际只用 2G,页面换入换出导致抖动。vmstat的wa有 6、bo2048 → 磁盘 IO 有压力,可能在做BGSAVE。
进一步确认:
bash
redis-cli -a '密码' info persistence | grep -E 'rdb_bgsave_in_progress|aof_rewrite_in_progress'
redis-cli -a '密码' latency latest
redis-cli -a '密码' client list | awk '{for(i=1;i<=NF;i++) if($i ~ /^omem=/) print $i}' | sort -t= -k2 -rn | head -5输出:
text
rdb_bgsave_in_progress:1
aof_rewrite_in_progress:0
1) 1) "fork"
2) (integer) 1761548102)
3) (integer) 412)
4) (integer) 891)定论:正在 BGSAVE(rdb_bgsave_in_progress:1),且碎片率 2.4 让进程实际占用远超数据集,两者叠加。
处置:
bash
# 1. 碎片:在线整理
redis-cli -a '密码' config set activedefrag yes
redis-cli -a '密码' config set active-defrag-threshold-lower 10
# 2. 长期:低峰滚动重启回收碎片
# 单机 → 直接重启;主从 → 先切主再逐台重启如果 --latency 高而 --intrinsic-latency 低,别在 Redis 上折腾,去查:
- 客户端 GC 停顿。
- 连接池排队(
max-wait)。 - 中间有代理(Twemproxy/HAProxy)或安全组限速。
- 跨可用区网络。
案例 6:启动失败,AOF 或 RDB 损坏
现象:systemctl start 失败,日志末尾是 Bad file format 或 Short read。
bash
systemctl status redis-6379 --no-pager
tail -30 /var/log/redis/6379.log输出:
text
Bad file format reading the append only file appendonly.aof.1.incr.aof: make a backup of your AOF file, then use ./redis-check-aof --fix <filename.manifest>或:
text
Short read or OOM loading DB. Unrecoverable error, aborting now.AOF 修复
bash
systemctl stop redis-6379
cd /var/lib/redis/6379
# 先备份,别直接改
cp -a appendonlydir appendonlydir.bak.$(date +%s)
/usr/local/redis/bin/redis-check-aof --fix appendonlydir/appendonly.aof.manifesttext
The AOF file is not valid, fixing it.
AOF analyzed: filename=appendonlydir/appendonly.aof.1.incr.aof, size=1048576, ok_up_to=1042133, diff=6443
This will shrink the AOF from 1048576 bytes, with 6443 bytes, to 1042133 bytes
Continue? [y/N]: y
Successfully truncated AOF appendonlydir/appendonly.aof.1.incr.aofbash
systemctl start redis-6379截断掉的是断电时没写完的最后几条命令,通常几条,可接受。
RDB 损坏
bash
redis-check-rdb /var/lib/redis/6379/dump.rdbtext
[offset 18234] Checksum OK
[offset 18234] \o/ RDB looks OK! \o/如果校验失败,RDB 无法"修"——只能换备份。此时三条路:
- 有备份 → 用备份恢复(见 持久化)。
- 有从库 → 从从库拉一份 RDB 过来,或直接重建主从。
- 什么都没有 → 接受数据丢失,删掉损坏文件空库启动。
bash
# 最后手段
mv dump.rdb dump.rdb.corrupt
systemctl start redis-6379 # 空库启动,逐 key 报警磁盘满也会伪装成"文件损坏"
df -h 先看一眼。磁盘 100% 时 AOF 写入失败、BGSAVE 写空文件,表现和损坏一模一样。清出空间后先重启验证,再决定要不要修文件。
案例 7:报 MISCONF,所有写操作被拒
现象:MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk. Commands that may modify the data set are disabled.
这不是故障,是 Redis 在保护你——它发现落盘失败了,拒绝一切写,避免你以为数据安全。
bash
redis-cli -a '密码' info persistence | grep -E 'rdb_last_bgsave_status|aof_last_write_status'
df -h
ls -ld /var/lib/redis/6379
tail -20 /var/log/redis/6379.log输出:
text
rdb_last_bgsave_status:err
aof_last_write_status:err
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 40G 0 100% /
drwxr-xr-x 2 redis redis 4096 Oct 27 18:10 /var/lib/redis/6379
# Background saving error
# Write error saving DB on disk: No space left on device定论:磁盘满了。
bash
# 1. 腾空间(找大文件)
du -sh /var/log/* 2>/dev/null | sort -h | tail -10
# 2. 清掉旧备份/日志(确认无用再删)
journalctl --vacuum-size=200M
find /data/backup/redis -name 'dump-*.rdb.gz' -mtime +7 -delete
# 3. 恢复写入(落盘成功后自动恢复,不用重启)
redis-cli -a '密码' info persistence | grep rdb_last_bgsave_status如果 df 显示还有空间但仍失败,查 dir 的权限和 inode:
bash
ls -ld /var/lib/redis/6379
df -i /
touch /var/lib/redis/6379/test && rm /var/lib/redis/6379/test案例 8:内存没满但写不进去(OOM 报错)
现象:OOM command not allowed when used memory > 'maxmemory',但看内存似乎没到顶。
bash
redis-cli -a '密码' info memory | grep -E 'used_memory:|used_memory_human|maxmemory:'
redis-cli -a '密码' config get maxmemory-policy
redis-cli -a '密码' info stats | grep evicted_keys输出:
text
used_memory:4293002240
used_memory_human:4.00G
maxmemory:4294967296
1) "maxmemory-policy"
2) "volatile-lru"
evicted_keys:0定论:used_memory 已经 4.00G / 4.00G,且策略是 volatile-lru,但 evicted_keys:0——说明没有任何 key 设了 TTL,淘汰器无 key 可淘汰,于是新写入直接报 OOM。
bash
# 数一下有 TTL 的 key 占比
redis-cli -a '密码' info keyspacetext
db0:keys=182344,expires=0,avg_ttl=0expires=0 就是证据。
处置二选一:
bash
# 方案 A:全量缓存,允许淘汰任意 key
redis-cli -a '密码' config set maxmemory-policy allkeys-lru
# 方案 B:保持 volatile,但补齐 TTL
redis-cli -a '密码' --scan --pattern 'cache:*' | while read k; do
redis-cli -a '密码' expire "$k" 3600 >/dev/null
done上线前做一次 TTL 体检
bash
redis-cli -a '密码' info keyspace # 对比 keys 和 expires
redis-cli -a '密码' --bigkeys # 找没有 TTL 的大 key缓存 key 的 expires 应该接近 keys。长期低于 90% 就一定会演变成内存只涨不降。
快速对照表
| 现象 | 第一步命令 | 常见根因 |
|---|---|---|
| 应用连不上 | info clients + client list | 连接泄漏、maxclients、LimitNOFILE |
| 周期性卡顿 | latest_fork_usec + latency history fork | BGSAVE fork、THP、碎片 |
| 进程消失 | dmesg -T | 未设 maxmemory,被 OOM killer |
写失败 READONLY | info replication | 连了从库/旧主 |
写失败 MISCONF | df -h + rdb_last_bgsave_status | 磁盘满 |
写失败 OOM | info keyspace | volatile-* 策略但没有 TTL |
| 起不来 | 日志末尾 + redis-check-aof/rdb | 文件损坏、磁盘满、权限 |
| 慢但 CPU 不高 | --latency vs --intrinsic-latency | 网络、客户端 GC、池排队 |
| 切换后数据不一致 | sentinel get-master-addr-by-name | 客户端写死 IP、脑裂 |
| 内存不降 | info keyspace + mem_fragmentation_ratio | 无 TTL、碎片、惰性删除 |