强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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
SORTO(N+M*log M)ZSet 排序
DEL (大 Key)O(N)UNLINK
FLUSHDBO(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"

🔗 扩展阅读