一、前言:慢查询是 Redis 性能的第一杀手
你是否遇到过:
- Redis CPU 突然飙到 100%?
- 接口响应时间从 5ms 暴涨到 2s?
- 监控告警"Redis 延迟过高"?
✅ 真相 :90% 的 Redis 性能问题,根源在于慢查询!
本文将带你:
- 精准定位 慢查询(不止
SLOWLOG) - 深度剖析五大常见原因
- 实战演练五种优化策略
- 构建自动化监控体系
二、Redis 慢查询:定义与核心机制
2.1 什么是慢查询?
Redis 将执行时间超过阈值的命令记录到内存中的慢查询日志(Slow Log)。
2.2 核心配置参数(redis.conf)
| 参数 | 默认值 | 说明 |
|---|---|---|
slowlog-log-slower-than |
10000 (10ms) |
超过此微秒数的命令被记录 |
slowlog-max-len |
128 |
慢查询日志最大条数(先进先出) |
💡 单位注意:1 秒 = 1,000,000 微秒
2.3 开启动态配置(无需重启)
bash
# 设置阈值为 5ms
CONFIG SET slowlog-log-slower-than 5000
# 设置日志长度为 1000 条
CONFIG SET slowlog-max-len 1000
# 永久生效需写入 redis.conf
三、慢查询诊断:四步精准定位
3.1 第一步:查看慢查询日志
bash
# 获取所有慢查询
SLOWLOG GET
# 获取最近 5 条
SLOWLOG GET 5
# 重置日志(谨慎!)
SLOWLOG RESET
返回结果解析:
java
1) 1) (integer) 12345 // 日志唯一ID
2) (integer) 1712000000 // 时间戳
3) (integer) 15000 // 执行耗时(微秒)
4) 1) "LRANGE" // 命令
2) "user:logs:1001" // Key
3) "0"
4) "-1"
5) "127.0.0.1:54321" // 客户端IP
6) "" // 客户端名称
3.2 第二步:关联业务代码
- 根据 客户端 IP/端口 定位服务实例
- 根据 命令和 Key 反查业务逻辑
- 关键问题:为什么这条命令会慢?
3.3 第三步:使用 LATENCY 工具(Redis 2.8.13+)
bash
# 查看所有延迟事件
LATENCY LATEST
# 分析特定事件(如 fork)
LATENCY HISTORY fork
# 重置历史
LATENCY RESET
✅ 优势 :比 Slowlog 更全面,可捕获非命令导致的延迟(如持久化、AOF 重写)
3.4 第四步:监控指标联动
| 监控项 | 工具 | 说明 |
|---|---|---|
latest_fork_usec |
INFO STATS |
最近一次 fork 耗时 |
blocked_clients |
INFO CLIENTS |
阻塞客户端数量 |
used_memory |
INFO MEMORY |
内存使用量(防 OOM) |
四、慢查询五大元凶与优化方案
4.1 元凶 1:BigKey 操作(最常见!)
现象:
GET一个 10MB 的 StringHGETALL一个含 10 万字段的 HashSMEMBERS一个百万级 Set
优化方案:
-
拆分 BigKey :
bash# 错误:单个 List 存 100 万订单 LPUSH orders:all order_data # 正确:按天分片 LPUSH orders:20260401 order_data -
使用游标迭代 :
bashHSCAN user:info:1001 0 COUNT 100 # 分批获取 Hash
4.2 元凶 2:高时间复杂度命令
危险命令清单:
| 命令 | 时间复杂度 | 替代方案 |
|---|---|---|
KEYS * |
O(N) | SCAN |
FLUSHALL |
O(N) | 禁用或重命名 |
SORT |
O(N+M*log(M)) | 应用层排序 |
ZRANGE key 0 -1 |
O(log(N)+M) | 限制返回数量 ZRANGE key 0 99 |
优化示例:
javascript
// 危险:获取全部成员
Set<String> all = jedis.smembers("huge-set");
// 安全:游标分批
ScanResult<String> scan = jedis.sscan("huge-set", cursor);
4.3 元凶 3:大量小请求(网络开销)
现象:
- 循环执行 1000 次
GET - 每次请求 RTT = 1ms → 总耗时 1s
优化方案:
-
Pipeline 批处理 :
javascriptPipeline p = jedis.pipelined(); for (String key : keys) { p.get(key); } List<Object> results = p.syncAndReturnAll(); // 1次网络往返 -
MGET 批量获取 :
javascriptMGET key1 key2 key3 ... key100
4.4 元凶 4:阻塞操作
阻塞命令:
BLPOP/BRPOP(列表阻塞弹出)XREADwithBLOCK(Stream 阻塞读)WAIT(等待复制完成)
优化建议:
- 设置合理的超时时间
- 监控
blocked_clients指标 - 避免在主线程中长时间阻塞
4.5 元凶 5:系统资源竞争
外部因素:
- CPU 过载:其他进程抢占资源
- Swap 使用:内存不足触发磁盘交换
- 持久化:RDB fork 或 AOF fsync 阻塞
排查命令:
javascript
# 检查 Swap
free -h
# 检查 CPU
top -p $(pgrep redis-server)
# 检查 fork 耗时
INFO | grep latest_fork_usec
五、生产级优化案例
案例:用户行为日志查询慢
问题:
LRANGE user:logs:1001 0 -1耗时 800ms- 日志 List 平均 50 万条
优化步骤:
-
禁止全量获取 :前端分页,每次只取 100 条bash
javascriptLRANGE user:logs:1001 0 99 # 第一页 LRANGE user:logs:1001 100 199 # 第二页 -
数据归档:30 天前日志移至冷存储
-
Key 设计优化 :按天分片
javascriptLPUSH logs:1001:20260401 log_data
效果:
- 查询耗时从 800ms → 2ms
- 内存占用降低 70%
六、自动化监控与告警
6.1 Shell 脚本监控
javascript
#!/bin/bash
# check_slowlog.sh
SLOW_COUNT=$(redis-cli SLOWLOG LEN)
if [ $SLOW_COUNT -gt 0 ]; then
redis-cli SLOWLOG GET 5 | mail -s "Redis Slowlog Alert" admin@example.com
fi
6.2 Prometheus + Grafana
-
采集指标:
redis_slowlog_length -
设置告警规则:
- alert: RedisSlowlogDetected expr: redis_slowlog_length > 0 for: 5m labels: severity: warning annotations: summary: "Redis 检测到慢查询"
6.3 应用层埋点
- 在 DAO 层记录 Redis 命令耗时
- 超过阈值自动上报 APM(如 SkyWalking)
七、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!