Redis 性能优化
问题
Redis 有哪些常见的性能问题?如何发现和解决大 Key、热 Key?Pipeline 怎么用?
答案
一、性能问题排查
# 慢查询日志
CONFIG SET slowlog-log-slower-than 10000 # 超过 10ms 记录
CONFIG SET slowlog-max-len 128
SLOWLOG GET 10 # 查看最近 10 条慢查询
# 延迟监控
redis-cli --latency # 持续测量延迟
redis-cli --latency-history # 延迟历史
# 内存分析
redis-cli --bigkeys # 扫描大 Key
redis-cli --memkeys # 内存分析(Redis 7.0+)
MEMORY DOCTOR # 内存诊断建议
二、大 Key 问题
大 Key 的危害
- 内存不均衡(Cluster 场景)
- 操作阻塞主线程(尤其是 DEL)
- 网络传输耗时长
- 持久化影响(子进程 Copy-On-Write 时大页复制)
什么算大 Key?
| 类型 | 大 Key 标准 |
|---|---|
| String | value > 10KB |
| Hash/List/Set/ZSet | 元素 > 5000 个 或 总大小 > 10MB |
发现大 Key
# 方法 1:redis-cli 扫描
redis-cli --bigkeys
# 方法 2:SCAN + MEMORY USAGE
redis-cli SCAN 0 COUNT 100
redis-cli MEMORY USAGE mykey
# 方法 3:RDB 分析工具
rdb --command memory dump.rdb --bytes 10240
删除大 Key
# ❌ 直接 DEL 会阻塞
DEL huge_hash # 如果有 100 万元素,可能阻塞数秒
# ✅ UNLINK 异步删除(Redis 4.0+)
UNLINK huge_hash # 后台线程异步释放内存
# ✅ 分批删除
# Hash:HSCAN + HDEL
# Set:SSCAN + SREM
# ZSet:ZREMRANGEBYRANK 分批
# List:LTRIM 逐步缩小
三、热 Key 问题
高并发访问同一个 key,导致单个 Redis 节点 CPU 飙高。
发现热 Key
# Redis 4.0+ LFU 模式
redis-cli --hotkeys
# proxy 层统计
# 业务层统计
解决方案
| 方案 | 说明 |
|---|---|
| 本地缓存 | 在应用层加 L1 缓存(Caffeine/Guava) |
| 读副本 | key 拆分为多个副本(key_1, key_2..random) |
| Redis Cluster 读从库 | READONLY 命令让从库提供读服务 |
四、Pipeline(管道)
Redis 客户端每次请求都有网络 RTT 开销。Pipeline 将多个命令一次性发送:
普通模式: Pipeline 模式:
cmd1 → server → response1 cmd1 ─┐
cmd2 → server → response2 cmd2 ─┤→ server → response1
cmd3 → server → response3 cmd3 ─┘ response2
response3
Pipeline 的注意事项:
- 命令数量不宜过多(建议每批 100~1000 个)
- Pipeline 不是原子操作(不同于 Lua 脚本和事务)
- 注意内存:一批命令的响应会先缓存在客户端
五、Lua 脚本优化
# 原子执行多个操作
EVAL "
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock > 0 then
redis.call('DECRBY', KEYS[1], 1)
return 1
end
return 0
" 1 stock:product:123
| 对比 | Pipeline | Lua 脚本 | 事务 (MULTI) |
|---|---|---|---|
| 原子性 | ❌ | ✅ | ✅ |
| 网络往返 | 1 次 | 1 次 | 多次 |
| 条件逻辑 | ❌ | ✅ | ❌ |
| 适用场景 | 批量独立操作 | 有条件逻辑的原子操作 | 简单原子批量 |
六、其他优化建议
| 类别 | 建议 |
|---|---|
| key 设计 | 业务前缀 + 冒号分隔,如 user:1:name |
| 避免危险命令 | 禁用 KEYS *、FLUSHALL,用 SCAN 替代 |
| 连接池 | 使用连接池,避免频繁创建连接 |
| 序列化 | Protobuf/MessagePack 比 JSON 更小更快 |
| 过期策略 | 所有缓存 key 都设过期时间 |
| 批量操作 | MGET/MSET 替代多次 GET/SET |
| Redis 6.0+ | 开启多线程 IO(io-threads 4) |
常见面试问题
Q1: 为什么 KEYS * 命令是危险的?
答案:
KEYS 命令会遍历所有 key(O(n)),在生产环境中可能有数百万个 key,会阻塞 Redis 主线程数秒。应该使用 SCAN 命令分批遍历(游标迭代,不阻塞)。
Q2: Redis 6.0 的多线程是怎么回事?
答案:
Redis 6.0 引入了 多线程网络 IO:
- 命令执行仍然是 单线程(保持原子性和简单性)
- 网络读写(read/write syscall)由多线程处理
- 适合网络 IO 成为瓶颈的场景(如大 value 传输)
配置:
io-threads 4 # 线程数(建议 CPU 核数的一半)
io-threads-do-reads yes # 读也用多线程
Q3: Redis 事务是原子的吗?
答案:
Redis 事务(MULTI/EXEC)保证的是 命令不会被其他客户端插入(串行执行),但 不支持回滚。如果事务中某条命令执行失败,其他命令仍然会执行。
真正的原子性需要用 Lua 脚本。