Redis 完全指南 / 13 - 性能调优
性能调优
13.1 性能基准测试
redis-benchmark
# 基本测试
redis-benchmark
# 指定参数
redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 100000 -q
# -c 50: 50 个并发连接
# -n 100000: 10 万次请求
# -q: 安静模式,只显示结果
# 测试特定命令
redis-benchmark -t set,get -n 100000 -q
# SET: 110000.00 requests per second
# GET: 120000.00 requests per second
# Pipeline 测试
redis-benchmark -t set,get -n 100000 -P 16 -q
# -P 16: 每次 Pipeline 16 个命令
# SET: 800000.00 requests per second ← 性能提升 7 倍!
# 测试特定 Key 大小
redis-benchmark -t set -n 100000 -d 256 -q
# -d 256: Value 大小 256 字节
# 使用 Lua 脚本测试
redis-benchmark -n 100000 eval "return redis.call('set',KEYS[1],ARGV[1])" 1 __rand_int__ __rand_int__
性能指标
| 指标 | 参考值 | 说明 |
|---|---|---|
| QPS(单线程) | ~10w/s | 简单命令 |
| QPS(Pipeline) | ~100w/s | 批量命令 |
| 延迟 P99 | < 1ms | 正常负载 |
| 延迟 P99 | > 10ms | 需要排查 |
13.2 Pipeline(管道)
原理
Pipeline 将多个命令打包发送,减少网络往返(RTT)次数:
# 无 Pipeline(每次等待响应)
Client → SET k1 v1 → Server
Client ← OK ← Server
Client → SET k2 v2 → Server
Client ← OK ← Server
Client → SET k3 v3 → Server
Client ← OK ← Server
# 3 次 RTT
# 使用 Pipeline(批量发送)
Client → SET k1 v1 →
SET k2 v2 → Server
SET k3 v3 →
Client ← OK ←
OK ← Server
OK ←
# 1 次 RTT
性能对比
# 无 Pipeline
redis-benchmark -t set -n 100000 -c 1 -q
# SET: 110,000 requests per second
# Pipeline 16
redis-benchmark -t set -n 100000 -c 1 -P 16 -q
# SET: 700,000 requests per second ← 6x 提升
# Pipeline 64
redis-benchmark -t set -n 100000 -c 1 -P 64 -q
# SET: 1,200,000 requests per second ← 11x 提升
Python Pipeline 示例
import redis
import time
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# 不使用 Pipeline
start = time.time()
for i in range(10000):
r.set(f'key:{i}', f'value:{i}')
print(f"No Pipeline: {time.time() - start:.2f}s")
# 使用 Pipeline
start = time.time()
pipe = r.pipeline(transaction=False) # 非事务 Pipeline
for i in range(10000):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()
print(f"Pipeline: {time.time() - start:.2f}s")
# Pipeline 批量大小控制(避免一次发送过大)
start = time.time()
pipe = r.pipeline(transaction=False)
for i in range(100000):
pipe.set(f'key:{i}', f'value:{i}')
if i % 1000 == 0:
pipe.execute() # 每 1000 个命令执行一次
pipe = r.pipeline(transaction=False)
pipe.execute()
print(f"Batched Pipeline: {time.time() - start:.2f}s")
Java Pipeline 示例
// Spring Data Redis
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void batchInsert() {
redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) {
for (int i = 0; i < 10000; i++) {
connection.set(
("key:" + i).getBytes(),
("value:" + i).getBytes()
);
}
return null;
}
});
}
Pipeline 注意事项
⚠️ 注意:
- Pipeline 中的命令不保证原子性(非事务 Pipeline)
- Pipeline 大小不宜过大(建议 100-1000 条),避免客户端/服务端缓冲区溢出
- Pipeline 不适合需要即时结果的场景
13.3 慢查询日志
配置慢查询
# 设置慢查询阈值(微秒)
redis-cli CONFIG SET slowlog-log-slower-than 10000 # 10ms
# 设置慢查询日志最大条数
redis-cli CONFIG SET slowlog-max-len 128
查看慢查询
# 获取最近 10 条慢查询
redis-cli SLOWLOG GET 10
# 1) 1) (integer) 1 # 日志 ID
# 2) (integer) 1715318400 # 发生时间
# 3) (integer) 15230 # 耗时(微秒)
# 4) 1) "KEYS" # 命令
# 2) "*"
# 5) "127.0.0.1:54321" # 客户端地址
# 6) "" # 客户端名称
# 获取慢查询日志长度
redis-cli SLOWLOG LEN
# 重置慢查询日志
redis-cli SLOWLOG RESET
常见慢查询命令
| 命令 | 时间复杂度 | 替代方案 |
|---|---|---|
KEYS * | O(N) | SCAN |
HGETALL (大 Hash) | O(N) | HSCAN |
SMEMBERS (大 Set) | O(N) | SSCAN |
LRANGE 0 -1 (大 List) | O(N) | 分页 LRANGE 0 99 |
SORT | O(N+M*log M) | ZSet 排序 |
DEL (大 Key) | O(N) | UNLINK |
FLUSHDB | O(N) | FLUSHDB ASYNC |
13.4 热 Key 处理
热 Key 定义
单个 Key 的访问频率远高于其他 Key,可能导致单节点负载过高。
发现热 Key
# 方法一:redis-cli --hotkeys(需要 LFU 策略)
redis-cli CONFIG SET maxmemory-policy allkeys-lfu
redis-cli --hotkeys
# Scanning the entire keyspace to find hot keys as well as
# average sizes per key type.
# [00.00%] Hot key 'user:1001' found so far with counter 50000
# [25.00%] Hot key 'product:hot' found so far with counter 30000
# 方法二:monitor 命令分析(生产慎用!)
redis-cli monitor | head -1000 | awk '{print $NF}' | sort | uniq -c | sort -nr | head -10
# 方法三:客户端代理层统计
# 通过 Twemproxy / Codis / ProxySQL 等代理统计 Key 访问频率
热 Key 解决方案
1. 本地缓存
import redis
import time
class LocalCache:
def __init__(self, ttl=60):
self.cache = {}
self.ttl = ttl
def get(self, key):
if key in self.cache:
value, ts = self.cache[key]
if time.time() - ts < self.ttl:
return value
return None
def set(self, key, value):
self.cache[key] = (value, time.time())
local_cache = LocalCache(ttl=30)
r = redis.Redis()
def get_hot_key(key):
# 先查本地缓存
value = local_cache.get(key)
if value is not None:
return value
# 本地缓存未命中,查 Redis
value = r.get(key)
if value is not None:
local_cache.set(key, value)
return value
2. 读写分离(多个从节点分担读压力)
# 多个从节点分散读请求
redis-slave-1 GET hot_key
redis-slave-2 GET hot_key
redis-slave-3 GET hot_key
3. Key 拆分
# 将热 Key 拆分到多个 Key
SET hot_key:0 "value"
SET hot_key:1 "value"
SET hot_key:2 "value"
SET hot_key:3 "value"
# 读取时随机选择一个
# index = random.randint(0, 3)
# GET hot_key:{index}
4. 使用 Redis Cluster 分散负载
# 将热 Key 的读请求分散到多个分片
13.5 客户端优化
连接池配置
# Python 连接池
pool = redis.ConnectionPool(
host='localhost',
port=6379,
max_connections=100,
decode_responses=True,
socket_timeout=5,
socket_connect_timeout=5,
retry_on_timeout=True
)
r = redis.Redis(connection_pool=pool)
避免大 Key 操作
# ❌ 危险操作
HGETALL big_hash # 可能返回数 MB 数据
LRANGE big_list 0 -1 # 可能返回数 MB 数据
KEYS * # 阻塞主线程
# ✅ 安全操作
HSCAN big_hash 0 COUNT 100
LRANGE big_list 0 99
SCAN 0 MATCH pattern COUNT 100
合理使用数据结构
# 计数器:使用 INCR 而非 GET + SET
# ❌
val = GET counter
val = val + 1
SET counter val
# ✅
INCR counter
13.6 服务端优化
内存优化
# 使用合适的数据编码
hash-max-listpack-entries 128
hash-max-listpack-value 64
list-max-listpack-size -2
set-max-intset-entries 512
zset-max-listpack-entries 128
zset-max-listpack-value 64
网络优化
# TCP backlog
tcp-backlog 511
# TCP keepalive
tcp-keepalive 300
# 客户端输出缓冲区
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
📌 业务场景
场景一:批量导入数据
# 使用 Pipeline 批量导入百万数据
import redis
r = redis.Redis()
pipe = r.pipeline(transaction=False)
for i in range(1000000):
pipe.set(f'import:key:{i}', f'value:{i}')
if i % 10000 == 0:
pipe.execute()
pipe = r.pipeline(transaction=False)
pipe.execute()
场景二:热点商品缓存
# 使用本地缓存 + Redis 缓存两级架构
# 本地缓存 TTL 30 秒,Redis 缓存 TTL 5 分钟
场景三:慢查询监控
# 定期检查慢查询
redis-cli SLOWLOG GET 10 | grep -E "KEYS|HGETALL|FLUSHALL"