Redis 排障与性能优化:大 key、热 key、慢命令与延迟定位

一、结论先给

Redis 出问题,90% 落在四类:大 key、热 key、慢命令、内存。排查顺序固定:

1. 是不是真的慢 → redis-cli --latency 区分"网络慢"还是"Redis 慢"
2. 慢在哪条命令 → SLOWLOG GET
3. 有没有大 key / 热 key → --bigkeys / --hotkeys / MEMORY USAGE
4. 内存怎么了 → INFO memory(碎片率、淘汰数、峰值)

Redis 是单线程处理命令的(6.0 之后是多线程 IO + 单线程执行命令),所以任何一条命令慢,都会堵住后面所有请求。这是所有性能问题的根源。

👉 把 slowlog 或 Redis 日志粘进 日志分析工具,能直接看出 Top 慢命令和高发时段。

二、第一步:确认是不是 Redis 慢

2.1 延迟基线测量

# 在 Redis 服务器上测(排除网络因素)
redis-cli -a pwd --latency            # 持续采样,Ctrl+C 停止
redis-cli -a pwd --latency-history    # 每 15 秒输出一批
redis-cli -a pwd --latency-dist       # 分布直方图
redis-cli -a pwd --intrinsic-latency 100   # 测机器本身的延迟基线(不连 Redis)

正常情况下 Redis 延迟应该在 1 毫秒以内(同机访问)。如果:

现象 结论
服务器上测很快,应用端慢 网络问题(跨机房、带宽打满、连接数过多)
服务器上测也慢 Redis 自身问题,继续往下查
--intrinsic-latency 本身就高 机器问题(CPU 争抢、虚拟化、内存交换)

2.2 用 INFO 快速体检

redis-cli -a pwd INFO | grep -E "used_memory_human|used_memory_peak_human|mem_fragmentation_ratio|connected_clients|instantaneous_ops_per_sec|evicted_keys|keyspace_hits|keyspace_misses|latest_fork_usec"
指标 健康值 异常含义
instantaneous_ops_per_sec 视业务 突增可能是异常流量
keyspace_hits/(hits+misses) > 90% 命中率低 = 缓存设计有问题
evicted_keys 0 大于 0 说明内存不够在淘汰
mem_fragmentation_ratio 1.0-1.5 > 1.5 碎片严重;< 1 用了 swap ⚠️
latest_fork_usec < 100000(100ms) 太大说明 fork 阻塞严重
connected_clients 稳定 持续增长 = 连接泄漏

三、慢日志:找出具体是哪条命令

SLOWLOG GET 20              -- 最近 20 条慢命令
SLOWLOG LEN                 -- 慢日志条数
SLOWLOG RESET               -- 清空
CONFIG GET slowlog-log-slower-than   -- 阈值(微秒,默认 10000 = 10ms)
CONFIG GET slowlog-max-len           -- 最多保存多少条(默认 128)

调整:

CONFIG SET slowlog-log-slower-than 5000     -- 5 毫秒
CONFIG SET slowlog-max-len 1000             -- 存 1000 条,别因为覆盖丢信息

慢日志输出解读:

1) 1) (integer) 42                       ← 序号
   2) (integer) 1727073333               ← 时间戳
   3) (integer) 15234                    ← 耗时(微秒)= 15 毫秒
   4) 1) "KEYS"                          ← 命令
      2) "user:*"
   5) "10.0.0.5:45321"                   ← 来源客户端

看到 KEYS、HGETALL、SMEMBERS、LRANGE 0 -1、SORT、SUNION 大集合运算,基本就找到元凶了。

四、大 key:最隐蔽的性能杀手

4.1 扫描

redis-cli -a pwd --bigkeys            # 采样扫描,输出每种类型最大的 key
redis-cli -a pwd --memkeys            # 按内存排(Redis 7+,需 maxmemory-policy 为 LFU 类)
redis-cli -a pwd --hotkeys            # 热 key(需要 LFU 策略)

--bigkeys 是采样,可能漏掉一些。精确扫描(生产可用,SCAN 不阻塞):

#!/usr/bin/env bash
# 扫出大于 10KB 的 key(按类型逐个 MEMORY USAGE 略慢,建议低峰跑)
redis-cli -a pwd --scan | while read -r k; do
  size=$(redis-cli -a pwd MEMORY USAGE "$k")
  if [ "${size:-0}" -gt 10240 ]; then
    echo "$size  $k"
  fi
done | sort -rn | head -30

单个 key 查看:

MEMORY USAGE user:list          -- 占多少字节
DEBUG OBJECT user:list          -- ⚠️ 有额外开销,谨慎使用
LLEN user:list                  -- 列表长度
HLEN user:hash
SCARD user:set
ZCARD user:zset
STRLEN user:str
OBJECT ENCODING user:list       -- 内部编码

4.2 大 key 的四大危害

危害 表现
阻塞删除 DEL 百万成员的集合会卡住几百毫秒
网络拥塞 一次 GET 拉 5MB,带宽打满,其他请求排队
迁移/持久化慢 AOF 重写、RDB 传输、主从同步都被拖慢
内存不均 集群模式下某个分片内存远超其他

4.3 处理方案

-- 1. 异步删除(推荐),不阻塞主线程
UNLINK big_key

-- 2. 配置自动异步删除(redis.conf)
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
replica-lazy-flush yes

-- 3. 集合类分批删
--    List:每次 LTRIM 掉 100 个
LTRIM biglist 100 -1
--    Set/ZSet/Hash:用 SSCAN/HSCAN/ZSCAN + SREM/HDEL/ZREM 分批

拆分方案(以 Hash 为例):

# 原来:一个 hash 存全站用户标签
user:tags  →  { 1: "a,b", 2: "c", ... 100万条 }

# 拆分:按用户 id 取模分 100 个桶
user:tags:0  →  { 100: "a", 200: "b" }
user:tags:1  →  { 1: "a", 101: "b" }
...
# 定位:bucket = userId % 100

String 大 value 的拆分:按业务字段拆成多个 key,只取需要的部分。

五、热 key:单分片被打爆

5.1 发现

redis-cli -a pwd --hotkeys    # 需要 maxmemory-policy 是 allkeys-lfu 或 volatile-lfu

其他办法:

# 抓包统计(不影响性能)
redis-cli -a pwd MONITOR | head -10000 | awk '{print $1}' | sort | uniq -c | sort -rn | head
# ⚠️ MONITOR 严重影响性能,只跑几秒,生产慎用

客户端埋点统计(最推荐,零成本):在 SDK 层对 key 做本地计数上报。

5.2 解法

方案 做法 适用
本地缓存 应用内 Caffeine 缓存该 key,TTL 短(1-5 秒) 读多写少的热点(如首页配置)
key 分片 hot:1…hot:10,读时随机挑一个 可复制的只读数据
读写分离 增加从库分担读 通用
拆分数据结构 一个大 String 拆成 hash 的多个 field 部分场景

热 key 加随机后缀示例:

int n = 10;
String key = "hot:product:123:" + ThreadLocalRandom.current().nextInt(n);
// 写入时 10 个副本都写,读取时随机挑

六、慢命令改写对照表

慢命令 问题 改写
KEYS * 全量遍历,O(N) 阻塞 SCAN(生产)或索引集合
HGETALL 字段多时返回巨大 HSCAN 分批 / HMGET 指定字段
SMEMBERS 同上 SSCAN
LRANGE list 0 -1 全量拉列表 LRANGE 0 99 分页
ZRANGE bigzset 0 -1 同上 分页 + WITHSCORES
SORT O(N log N),大集合灾难 建索引/用 ZSet
SUNION 大集合 O(N) 预先算好存结果
连续 N 次 GET 网络 RTT × N MGET / pipeline
DEL 大 key 同步释放内存阻塞 UNLINK
EXPIRE 大量 key 一条条发 pipeline 批量

SORT 命令基本可以从生产环境消失了——需要排序就建 ZSet。

七、内存问题的四类成因

redis-cli -a pwd INFO memory
成因 指标 处理
数据真的大 used_memory 接近 maxmemory 扩容 / 拆分 / 淘汰策略
内存碎片 mem_fragmentation_ratio > 1.5 见下
客户端缓冲积压 client-output-buffer-limit 触发 查 CLIENT LIST 的 obl/omem
复制缓冲 从库断连时积压 调 client-output-buffer-limit slave

7.1 内存碎片

INFO memory | grep mem_fragmentation_ratio
MEMORY PURGE          -- 尝试释放碎片(jemalloc,有一定效果)
MEMORY DOCTOR         -- Redis 自己给诊断建议 ⭐好用
  • > 1.5:碎片较严重,频繁更新/删除不同大小的 key 会导致;
  • < 1:用了 swap,性能会急剧下降,必须马上处理(降低 maxmemory 或加内存);
  • 彻底解决:SHUTDOWN 后重启(会重新加载形成紧凑内存),或者启用 activedefrag:
activedefrag yes
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
active-defrag-cycle-min 5
active-defrag-cycle-max 75

7.2 淘汰与 OOM

INFO stats | grep evicted_keys      -- 被淘汰的 key 数,>0 说明内存不够
CONFIG GET maxmemory
CONFIG GET maxmemory-policy

maxmemory 建议设为物理内存的 60-70%(要给 RDB fork 的 COW 留空间,否则可能 OOM)。

八、网络与连接问题

CLIENT LIST                    -- 所有客户端,看 idle 和 omem
CLIENT LIST | wc -l
CLIENT KILL addr 10.0.0.5:45321
CLIENT KILL TYPE normal
CLIENT SETNAME app1            -- 给连接起名字,排查时一眼认出
INFO clients | grep -E "connected_clients|blocked_clients|client_recent_max_input_buffer"
现象 原因 处理
connected_clients 持续增长 连接泄漏 修代码,加 timeout
blocked_clients > 0 有 BLPOP 等阻塞命令挂着 正常,过多则检查消费者
输出缓冲巨大(omem) 客户端读得太慢(如订阅者) 调 client-output-buffer-limit
maxclients 打满 连接数超上限 调大 + 查泄漏
timeout 300                                    # 空闲 300 秒断开
maxclients 10000
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit pubsub 32mb 8mb 60  # 订阅者慢就断开,别拖垮 Redis

九、常见误区

  1. 慢了就加内存:大 key、热 key、慢命令不解决,加多少内存都没用。
  2. 用 KEYS 查线上数据:一次就是事故。
  3. MONITOR 在生产长时间开着:输出量巨大,性能掉一半以上。
  4. maxmemory 设成物理内存 90%:RDB fork 时 COW 需要额外内存,容易 OOM。
  5. DEL 删大 key:同步阻塞,用 UNLINK。
  6. 只看 CPU 不看延迟:Redis 慢常常 CPU 并不高,是单条命令堵住了。

十、几条纪律

  1. 慢日志阈值设 5-10ms,长度设 1000 条,定期检查。
  2. 生产禁用 KEYS、FLUSHALL、CONFIG,改名或禁用(见 Redis 命令速查篇)。
  3. 每月跑一次 --bigkeys / --hotkeys,发现就拆。
  4. maxmemory 设物理内存 60-70%,策略选 allkeys-lru。
  5. 删大 key 用 UNLINK,开 lazyfree-* 系列配置。
  6. 客户端用连接池 + pipeline,避免大量短连接。
  7. 监控六项:延迟、命中率、内存水位、碎片率、evicted、fork 耗时。

十一、延伸阅读

👉 相关在线工具:日志分析 · Linux 命令速查 · 磁盘容量计算,免登录、浏览器内处理。

还有 65 个免费在线工具

纯前端实现,不用注册,数据不上传服务器。

浏览全部工具