第十章:性能优化

深入了解 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 缓存实战。

👉 缓存实战