第十章:性能优化
深入了解 Redis 性能优化技巧,包括内存优化、命令优化和架构优化。
最后更新: 2024-01-15
页面目录
Redis 性能优化
本章介绍 Redis 性能优化的各种技巧,帮助您构建高效的 Redis 应用。
性能指标
┌─────────────────────────────────────────────────────────────────┐
│ Redis 性能指标 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 吞吐量 │ │ 延迟 │ │ 内存使用 │ │
│ │ OPS/sec │ │ P99 │ │ RSS │ │
│ │ 100K+ │ │ < 10ms │ │ < 80% │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 连接数 │ │ CPU 使用 │ │ 带宽使用 │ │
│ │ 1000+ │ │ < 80% │ │ < 70% │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
性能监控
INFO 命令
# 内存信息
redis-cli INFO memory
# used_memory:1048576
# used_memory_human:1.00M
# used_memory_rss:2097152
# used_memory_peak:2097152
# maxmemory:1073741824
# 统计信息
redis-cli INFO stats
# total_connections_received:1000
# total_commands_processed:500000
# instantaneous_ops_per_sec:10000
# keyspace_hits:450000
# keyspace_misses:50000
# hit_rate:90%
# 复制信息
redis-cli INFO replication
# role:master
# connected_slaves:2
# master_repl_offset:12345
实时监控
# 实时统计
redis-cli --stat
# 实时键统计
redis-cli --scan | head -100 | xargs redis-cli MGET
# 实时客户端统计
redis-cli CLIENT LIST
内存优化
内存配置
# 最大内存
maxmemory 4gb
# 内存淘汰策略
maxmemory-policy allkeys-lru
# 淘汰策略选择指南
# - allkeys-lru: 通用缓存,推荐
# - volatile-lru: 带过期时间的缓存
# - allkeys-random: 不常用场景
# - noeviction: 不淘汰,只读模式
内存分析
# 查看内存使用分布
redis-cli MEMORY STATS
# 查看键的平均大小
redis-cli MEMORY USAGE key
# 查看 Redis 内存状态
redis-cli INFO memory | grep -E "used_memory|mem_fragmentation"
内存碎片
# 查看内存碎片率
redis-cli INFO memory | grep mem_fragmentation_ratio
# mem_fragmentation_ratio: 1.45
# 内存碎片率 > 1.5 时需要整理
# 解决方案:
# 1. 重启 Redis
# 2. 使用 ACTIVE DEFRAG(Redis 4.0+)
# 主动碎片整理
redis-cli CONFIG SET activedefrag yes
redis-cli CONFIG SET active-defrag-ignore-bytes 100mb
redis-cli CONFIG SET active-defrag-threshold-lower 10
redis-cli CONFIG SET active-defrag-threshold-upper 100
小对象压缩
# Hash、Zset 小对象优化
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
list-max-ziplist-size -2
set-max-intset-entries 512
Big Key 优化
识别 Big Key
# 使用 SCAN 识别
redis-cli --bigkeys
# 使用 MEMORY USAGE
for key in $(redis-cli --scan); do
size=$(redis-cli MEMORY USAGE $key)
if [ $size -gt 10485760 ]; then # > 10MB
echo "$key: $size bytes"
fi
done
# 使用 Redis 工具
redis-cli --scan | xargs -I{} redis-cli MEMORY USAGE {} | awk '$2>10485760 {print}'
Big Key 示例
| 类型 | 大小 | 建议 |
|---|---|---|
| String | > 10KB | 拆分存储 |
| Hash | > 10000 fields | 转为小 Hash |
| List | > 10000 items | 分段存储 |
| Set | > 10000 members | 分桶 |
| Zset | > 10000 members | 分桶 |
优化方案
# String 大对象拆分
# 原始:SET big:data "large_value_..."
# 优化:拆分为多个小段
MSET big:data:1 "part1" big:data:2 "part2" big:data:3 "part3"
# Hash 拆分
# 原始:HSET user:1001 field1 value1 ... field10000 value10000
# 优化:分表
HSET user:1001:1 field1 value1 ... field5000 value5000
HSET user:1001:2 field5001 value5001 ... field10000 value10000
# List 分段
LPUSH queue:tasks task1 task2 task3 ...
# 优化为多个列表
LPUSH queue:tasks:1 task1 task2
LPUSH queue:tasks:2 task3 task4
命令优化
Pipeline 使用
# 原始:逐条执行
redis-cli SET key1 value1
redis-cli SET key2 value2
redis-cli SET key3 value3
# Pipeline:批量执行
redis-cli --pipe <<EOF
SET key1 value1
SET key2 value2
SET key3 value3
EOF
Python Pipeline
import redis
client = redis.Redis(host='localhost', port=6379, decode_responses=True)
# 普通方式(3次RTT)
for i in range(1000):
client.set(f'key:{i}', f'value:{i}')
# Pipeline 方式(1次RTT)
pipe = client.pipeline()
for i in range(1000):
pipe.set(f'key:{i}', f'value:{i}')
pipe.execute()
Lua 脚本优化
-- 原子操作替代多次命令
-- 原始:
-- HGET user:1 name
-- HGET user:1 email
-- HGET user:1 phone
-- 优化为 Lua 脚本
local user = redis.call('HGETALL', KEYS[1])
for i = 1, #user, 2 do
if user[i] == ARGV[1] then
return user[i + 1]
end
end
return nil
避免阻塞命令
| 命令 | 风险 | 替代方案 |
|---|---|---|
| KEYS | O(N) 全表扫描 | SCAN |
| FLUSHDB | O(N) 全删 | UNLINK |
| SMEMBERS | O(N) 全返回 | SRANDMEMBER |
| SORT | O(N log N) | 应用层处理 |
| HGETALL | O(N) 全返回 | HSCAN |
| INFO | 低风险 | - |
# 使用 SCAN 替代 KEYS
SCAN 0 MATCH user:* COUNT 100
# 使用 UNLINK 替代 DEL
UNLINK key1 key2 key3
# 使用 HSCAN 替代 HGETALL
HSCAN key 0 COUNT 100
连接优化
连接池
import redis
# 创建连接池
pool = redis.ConnectionPool(
host='localhost',
port=6379,
max_connections=50, # 最大连接数
socket_timeout=5, # 超时时间
socket_connect_timeout=5,
health_check_interval=30 # 健康检查
)
client = redis.Redis(connection_pool=pool)
连接配置
# 最大客户端数
maxclients 10000
# TCP 配置
tcp-keepalive 300
tcp-backlog 511
# 客户端超时
timeout 300
# 客户端输出缓冲区
client-output-buffer-limit normal 256mb 64mb 60
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
慢查询优化
慢查询配置
# 记录超过 10ms 的命令
slowlog-log-slower-than 10000
# 保留最近 1000 条
slowlog-max-len 1000
查看慢查询
# 查看慢查询
redis-cli SLOWLOG GET 10
# 返回格式
# 1) 1) (integer) 5 # ID
# 2) (integer) 1500 # 执行时间(微秒)
# 3) (integer) 1705312800 # 时间戳
# 4) 1) "HGETALL" # 命令
# 2) "user:1001"
# 5) "127.0.0.1:54321" # 客户端
# 慢查询数量
redis-cli SLOWLOG LEN
# 清空慢查询
redis-cli SLOWLOG RESET
慢查询分析
import redis
client = redis.Redis(host='localhost', port=6379, decode_responses=True)
def analyze_slow_queries():
slow_queries = client.slowlog_get(10)
for query in slow_queries:
duration_us = query['duration']
duration_ms = duration_us / 1000
command = query['command']
if duration_ms > 100:
print(f"警告: {command} 执行时间 {duration_ms}ms")
# 记录告警或优化
持久化优化
RDB 配置
# 优化配置
save 900 1
save 300 10
save 60 10000
# fork 时使用 copy-on-write
# 确保有足够内存
AOF 配置
# 推荐配置
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
# 重写优化
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 避免重写阻塞
no-appendfsync-on-rewrite yes
架构优化
读写分离
import redis
from redis.cluster import RedisCluster
# Sentinel 读写分离
from redis.sentinel import Sentinel
sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1)
# 写操作 - 主节点
master = sentinel.master_for('mymaster')
master.set('key', 'value')
# 读操作 - 从节点
slave = sentinel.slave_for('mymaster')
value = slave.get('key')
集群分片
from redis.cluster import RedisCluster
# 创建集群客户端
cluster = RedisCluster(
host='127.0.0.1',
port=7000,
decode_responses=True
)
# 自动路由到正确节点
cluster.set('key1', 'value1')
cluster.set('key2', 'value2')
缓存策略
def cache_aside(key, func, ttl=3600):
"""Cache-Aside 模式"""
# 读
value = client.get(key)
if value is None:
value = func() # 从数据库读取
client.setex(key, ttl, value)
return value
def write_through(key, value, ttl=3600):
"""Write-Through 模式"""
# 同时写 Redis 和数据库
client.setex(key, ttl, value)
db.write(key, value)
性能测试
redis-benchmark
# 基本测试
redis-benchmark -h localhost -p 6379
# 指定命令测试
redis-benchmark -h localhost -p 6379 -c 100 -n 100000 -q
# 测试 SET/GET
redis-benchmark -h localhost -p 6379 -t SET,GET -n 100000
# Pipeline 测试
redis-benchmark -h localhost -p 6379 -P 16 -n 100000
# 测试特定键模式
redis-benchmark -h localhost -p 6379 -d 100 -n 100000
测试结果解读
====== SET ======
100000 requests completed in 1.23 seconds
50 parallel clients
3 bytes payload
Keep alive: 1
99.99% <= 1 milliseconds
99.99% <= 2 milliseconds
100.00% <= 3 milliseconds
常见性能问题
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 内存不足 | OOM、响应慢 | 增加内存、优化淘汰 |
| Big Key | 单命令阻塞 | 拆分键 |
| 命令复杂度 | 单命令慢 | 改用 Pipeline |
| 网络延迟 | RTT 高 | 本地部署、Pipeline |
| fork 阻塞 | 周期性卡顿 | 使用 AOF everysec |
| 客户端满 | 新连接拒绝 | 增加 maxclients |
下一步
接下来让我们学习 Redis 缓存实战。
👉 缓存实战