Skip to content

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=100000
bash
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

更彻底的办法:

手段效果代价
关 THPfork 快 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       210Mi

maxmemory 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=-900

maxmemory 不要设成物理内存的 100%

maxmemory 只算数据集,不含 fork 的 COW、客户端缓冲区、复制积压。设成 100% 时,一次 BGSAVE 就可能触发系统 OOM。经验值:单实例 maxmemory ≤ 物理内存 × 0.6,并且容器 limit 要比它再大 30%。

如果容器里跑:

yaml
resources:
  limits:
    memory: 12Gi
ini
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:26379

2. 防脑裂

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

三种可能,逻辑上先排除:

  1. --latency 客户端侧高(41ms),--intrinsic-latency 服务端低(8.4us)→ 网络/客户端问题,不是 Redis。
  2. mem_fragmentation_ratio:2.40 → 碎片严重,rss 4.9G 但实际只用 2G,页面换入换出导致抖动。
  3. vmstat 的 wa 有 6、bo 2048 → 磁盘 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.manifest
text
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.aof
bash
systemctl start redis-6379

截断掉的是断电时没写完的最后几条命令,通常几条,可接受。

RDB 损坏 ​

bash
redis-check-rdb /var/lib/redis/6379/dump.rdb
text
[offset 18234] Checksum OK
[offset 18234] \o/ RDB looks OK! \o/

如果校验失败,RDB 无法"修"——只能换备份。此时三条路:

  1. 有备份 → 用备份恢复(见 持久化)。
  2. 有从库 → 从从库拉一份 RDB 过来,或直接重建主从。
  3. 什么都没有 → 接受数据丢失,删掉损坏文件空库启动。
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 keyspace
text
db0:keys=182344,expires=0,avg_ttl=0

expires=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 forkBGSAVE fork、THP、碎片
进程消失dmesg -T未设 maxmemory,被 OOM killer
写失败 READONLYinfo replication连了从库/旧主
写失败 MISCONFdf -h + rdb_last_bgsave_status磁盘满
写失败 OOMinfo keyspacevolatile-* 策略但没有 TTL
起不来日志末尾 + redis-check-aof/rdb文件损坏、磁盘满、权限
慢但 CPU 不高--latency vs --intrinsic-latency网络、客户端 GC、池排队
切换后数据不一致sentinel get-master-addr-by-name客户端写死 IP、脑裂
内存不降info keyspace + mem_fragmentation_ratio无 TTL、碎片、惰性删除