一、结论先给
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
九、常见误区
- 慢了就加内存:大 key、热 key、慢命令不解决,加多少内存都没用。
- 用
KEYS查线上数据:一次就是事故。 MONITOR在生产长时间开着:输出量巨大,性能掉一半以上。maxmemory设成物理内存 90%:RDB fork 时 COW 需要额外内存,容易 OOM。DEL删大 key:同步阻塞,用UNLINK。- 只看 CPU 不看延迟:Redis 慢常常 CPU 并不高,是单条命令堵住了。
十、几条纪律
- 慢日志阈值设 5-10ms,长度设 1000 条,定期检查。
- 生产禁用
KEYS、FLUSHALL、CONFIG,改名或禁用(见 Redis 命令速查篇)。 - 每月跑一次
--bigkeys/--hotkeys,发现就拆。 maxmemory设物理内存 60-70%,策略选allkeys-lru。- 删大 key 用
UNLINK,开lazyfree-*系列配置。 - 客户端用连接池 + pipeline,避免大量短连接。
- 监控六项:延迟、命中率、内存水位、碎片率、evicted、fork 耗时。
十一、延伸阅读
👉 相关在线工具:日志分析 · Linux 命令速查 · 磁盘容量计算,免登录、浏览器内处理。