构建高可用的Redis 集群-- pd的后端笔记
文章目录
-
- [构建高可用的Redis 集群-- pd的后端笔记](#构建高可用的Redis 集群-- pd的后端笔记)
- [主从复制(Replication)------ 构建高可用 Redis 的基石](#主从复制(Replication)—— 构建高可用 Redis 的基石)
-
- [🧠 为什么需要主从复制?](#🧠 为什么需要主从复制?)
- [🔷 一、主从复制原理(异步复制)](#🔷 一、主从复制原理(异步复制))
- [🔧 二、如何配置主从复制?](#🔧 二、如何配置主从复制?)
- [🌰 三、Python 中如何使用主从?](#🌰 三、Python 中如何使用主从?)
- [🔍 四、监控主从状态](#🔍 四、监控主从状态)
- [⚠️ 五、主从复制的局限性](#⚠️ 五、主从复制的局限性)
- [✅ 六、生产环境最佳实践](#✅ 六、生产环境最佳实践)
- [哨兵模式(Sentinel)------ Redis 的高可用守护者](#哨兵模式(Sentinel)—— Redis 的高可用守护者)
-
- [🧠 为什么需要 Sentinel?](#🧠 为什么需要 Sentinel?)
- [🔷 一、Sentinel 架构与核心组件](#🔷 一、Sentinel 架构与核心组件)
- [🔍 二、Sentinel 工作原理(四步走)](#🔍 二、Sentinel 工作原理(四步走))
- [🔧 三、如何配置 Sentinel?](#🔧 三、如何配置 Sentinel?)
- [🌰 四、Python 客户端如何连接 Sentinel?](#🌰 四、Python 客户端如何连接 Sentinel?)
- [🔍 五、监控 Sentinel 状态](#🔍 五、监控 Sentinel 状态)
- [✅ 六、生产环境最佳实践](#✅ 六、生产环境最佳实践)
- [Redis Cluster ------ 水平扩展 Redis 的官方方案](#Redis Cluster —— 水平扩展 Redis 的官方方案)
-
- [🔷 一、核心概念:哈希槽(Hash Slot)](#🔷 一、核心概念:哈希槽(Hash Slot))
- [🔷 二、集群架构要求](#🔷 二、集群架构要求)
- [🔧 三、如何搭建 Redis Cluster?](#🔧 三、如何搭建 Redis Cluster?)
- [🌰 四、Python 客户端如何连接 Cluster?](#🌰 四、Python 客户端如何连接 Cluster?)
- [🔍 五、集群内部如何工作?](#🔍 五、集群内部如何工作?)
- 六、重要限制与注意事项
-
- [📖 Hash Tag ------ 让多个 key 落在同一个槽的魔法](#📖 Hash Tag —— 让多个 key 落在同一个槽的魔法)
- [🛠️ 七、运维常用命令](#🛠️ 七、运维常用命令)
- [✅ 八、生产环境最佳实践](#✅ 八、生产环境最佳实践)
- [Redis 性能调优 ------ 榨干每一分性能](#Redis 性能调优 —— 榨干每一分性能)
-
- [🔷 一、配置调优(redis.conf)](#🔷 一、配置调优(redis.conf))
- [🔷 二、命令级优化(避坑指南)](#🔷 二、命令级优化(避坑指南))
- [🔷 三、内存优化(防 OOM + 降成本)](#🔷 三、内存优化(防 OOM + 降成本))
- [🔷 四、网络与客户端优化](#🔷 四、网络与客户端优化)
- [🛠️ 五、性能诊断 checklist](#🛠️ 五、性能诊断 checklist)
- [✅ 六、终极建议:压测 + 监控](#✅ 六、终极建议:压测 + 监控)
主从复制(Replication)------ 构建高可用 Redis 的基石
🧠 为什么需要主从复制?
单机 Redis 有两大致命问题:
- 单点故障:Redis 挂了 → 整个服务不可用
- 读性能瓶颈:所有请求压在一台机器上
主从复制(Replication) 就是解决方案:
- 1 个主节点(Master):负责写 + 读
- N 个从节点(Slave / Replica):只读,数据与主节点最终一致
- 实现:读写分离 + 故障转移预备
🔑 核心价值:提升可用性 + 扩展读能力
🔷 一、主从复制原理(异步复制)
✅ 工作流程(简化版)
- 从节点连接主节点,发送 PSYNC 命令(Redis 2.8+ 使用部分同步)
- 主节点 fork 子进程,生成 RDB 快照(全量同步)
- 主节点将 RDB 文件传给从节点
- 从节点清空旧数据,加载 RDB
- 主节点将同步期间的写命令缓存,RDB 传输完后追加发送
- 此后,主节点每执行一个写命令,就立即转发给所有从节点(增量同步)
🔄 这个过程叫 "异步复制" ------ 主节点不等从节点确认,继续处理新请求。
📊 全量同步 vs 部分同步
| 类型 | 触发条件 | 说明 |
|---|---|---|
| 全量同步(Full Resync) | - 从节点初次连接 - 复制偏移量(offset)不匹配 - 主节点重启 | 传输整个 RDB,开销大 |
| 部分同步(Partial Resync) | 网络闪断后快速重连 | 只传断连期间的命令(靠 复制积压缓冲区 replication backlog) |
💡 复制积压缓冲区:主节点维护的一个固定大小环形队列(默认 1MB),用于缓存最近的写命令,供从节点断线重连时"补课"。
🔧 二、如何配置主从复制?
方式 1:动态配置(推荐)
bash
# 在从节点执行(临时生效)
127.0.0.1:6380> REPLICAOF 127.0.0.1 6379
OK
方式 2:配置文件(永久生效)
在从节点的 redis.conf 中添加:
conf
replicaof 192.168.1.100 6379
# 如果主节点有密码
masterauth your_redis_password
# Redis 6+ 推荐用
replica-serve-stale-data yes # 主挂了,从是否继续提供旧数据?
方式 3:Docker / Kubernetes
通过环境变量或启动参数指定主节点地址。
🌰 三、Python 中如何使用主从?
场景:读写分离
python
import redis
# 主节点(写)
master = redis.Redis(host="192.168.1.100", port=6379)
# 从节点(读)
slave1 = redis.Redis(host="192.168.1.101", port=6380)
slave2 = redis.Redis(host="192.168.1.102", port=6380)
# 写操作 → 主
master.set("user:1001:name", "Alice")
# 读操作 → 从(可轮询或随机)
name = slave1.get("user:1001:name") # 最终一致,可能有毫秒级延迟
⚠️ 注意一致性:
刚写完主,立刻从从读,可能读不到(因为复制是异步的)!
对强一致性要求高的场景,读也走主。
🔍 四、监控主从状态
查看复制信息(在主或从节点执行):
bash
127.0.0.1:6379> INFO REPLICATION
关键字段:
text
# 主节点视角
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=12345,lag=0
# 从节点视角
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up # 是否连接正常
slave_read_only:1 # 从节点是否只读(默认是)
⚠️ 五、主从复制的局限性
| 问题 | 说明 |
|---|---|
| 不能自动故障转移 | 主挂了,从不会自动变主 → 需要 哨兵(Sentinel) |
| 写能力无法扩展 | 所有写仍压在主节点 |
| 异步复制有延迟 | 从节点数据是"最终一致",非实时 |
| 从节点只读 | 不能分担写压力 |
✅ 所以:主从复制只是高可用的第一步,不是终点!
✅ 六、生产环境最佳实践
| 建议 | 说明 |
|---|---|
| 至少 1 主 2 从 | 避免单从节点故障导致无备份 |
| 从节点开启 AOF | 防止从节点重启后全量同步(可从 AOF 恢复) |
| 限制主节点内存 | 避免 RDB 太大导致全量同步超时 |
| 监控复制延迟 | 用 INFO REPLICATION 的 lag 字段告警 |
| 不要在从节点执行耗时命令 | 如 KEYS *,会阻塞复制线程 |
常见问题排查
❌ 问题 1:从节点 master_link_status: down
- 检查网络连通性(telnet master_ip 6379)
- 检查主节点是否设置了 requirepass,从节点是否配了 masterauth
- 检查防火墙/安全组
❌ 问题 2:频繁全量同步
- 原因:复制积压缓冲区太小(默认 1MB)
- 解决:增大 repl-backlog-size(如 128mb)
❌ 问题 3:从节点数据不一致
- 检查是否有人误操作直接写从节点(应设 replica-read-only yes)
- 检查主从 Redis 版本是否兼容
哨兵模式(Sentinel)------ Redis 的高可用守护者
🧠 为什么需要 Sentinel?
主从复制解决了读扩展和数据备份,但无法自动处理主节点故障:
- 主节点宕机 → 所有写入失败
- 运维手动切换 → 慢(几分钟)、易出错
- 客户端不知道新主是谁 → 服务长时间中断
Sentinel 就是 Redis 官方提供的高可用解决方案:
- 自动监控主从健康状态
- 主挂了?→ 自动选一个从节点提升为新主
- 通知所有从节点:"认新老大!"
- 通知客户端:"主地址变了!"
🔑 核心价值:无人值守的自动故障转移(Failover)
🔷 一、Sentinel 架构与核心组件
✅ 基本拓扑
text
+------------------+ +------------------+
| Redis Master |<---->| Sentinel 1 |
+------------------+ +------------------+
^ ^
| Replication | Monitoring
v v
+------------------+ +------------------+
| Redis Replica 1 |<---->| Sentinel 2 |
+------------------+ +------------------+
^ ^
| |
v v
+------------------+ +------------------+
| Redis Replica 2 |<---->| Sentinel 3 |
+------------------+ +------------------+
📌 关键原则:
- Sentinel 本身也要高可用 → 至少部署 3 个 Sentinel 实例
- Sentinel 不存储数据 → 只做监控、通知、决策
- 客户端通过 Sentinel 获取主节点地址 → 不再硬编码主 IP
🔍 二、Sentinel 工作原理(四步走)
步骤 1️⃣:监控(Monitoring)
- 每个 Sentinel 每秒向所有 Redis 节点发送 PING
- 如果主节点 连续 down-after-milliseconds 毫秒无响应 → 标记为 主观下线(SDOWN)
步骤 2️⃣:协商(Agreement)
- Sentinel 之间互相通信(通过 Redis 发布/订阅)
- 如果 多数 Sentinel(quorum) 都认为主挂了 → 标记为 客观下线(ODOWN)
💡 quorum 建议 = (N/2 + 1)- 3 个 Sentinel → quorum=2
- 5 个 Sentinel → quorum=3
步骤 3️⃣:选举(Leader Election)
- 所有 Sentinel 中选举一个 Leader(使用 Raft-like 算法)
- Leader 负责执行故障转移
步骤 4️⃣:故障转移(Failover)
- Leader 从从节点中选一个最优的(优先级、复制偏移量、runid 字典序)
- 向该从节点发送 REPLICAOF NO ONE → 提升为主
- 向其他从节点发送 REPLICAOF new_master_ip new_master_port → 认新主
- 更新 Sentinel 内部配置,并通知所有客户端
✅ 整个过程通常 10~30 秒完成!
🔧 三、如何配置 Sentinel?
- 准备 Redis 主从(假设已配置好)
- 主:192.168.1.10:6379
- 从1:192.168.1.11:6379
- 从2:192.168.1.12:6379
redis-sentinel 是 Redis 官方发行版自带的程序,从 Redis 2.8+ 开始就内置了 Sentinel 功能。
- 创建 sentinel.conf(每个 Sentinel 节点一份)
conf
# 监控名为 "mymaster" 的主节点
sentinel monitor mymaster 192.168.1.10 6379 2
# 主节点无响应多久算主观下线(毫秒)
sentinel down-after-milliseconds mymaster 5000
# 故障转移超时时间(毫秒)
sentinel failover-timeout mymaster 60000
# 最多同时有几个从节点同步新主(避免网络拥塞)
sentinel parallel-syncs mymaster 1
# 如果主节点有密码
sentinel auth-pass mymaster your_redis_password
⚠️ 关键参数解释:
- 2 = quorum(至少 2 个 Sentinel 同意才算主挂了)
- down-after-milliseconds:建议 3~10 秒,太短易误判,太长恢复慢
- 启动 Sentinel
bash
redis-sentinel /path/to/sentinel.conf
# 或
redis-server /path/to/sentinel.conf --sentinel
✅ 建议:Sentinel 和 Redis 实例部署在不同机器,避免单点故障。
🌰 四、Python 客户端如何连接 Sentinel?
不要直接连 Redis!要通过 Sentinel 获取主节点地址:
python
from redis.sentinel import Sentinel
# 连接 Sentinel 集群(提供多个 Sentinel 地址)
sentinel = Sentinel([
('192.168.1.20', 26379),
('192.168.1.21', 26379),
('192.168.1.22', 26379)
], socket_timeout=0.1)
# 获取主节点连接(自动发现当前主)
master = sentinel.master_for('mymaster', socket_timeout=0.1)
# 获取从节点连接(用于读)
slave = sentinel.slave_for('mymaster', socket_timeout=0.1)
# 正常使用
master.set('key', 'value')
print(slave.get('key'))
✅ 优势:
- 主切换后,下次调用 master_for() 会自动返回新主地址
- 客户端无需重启!
🔍 五、监控 Sentinel 状态
查看 Sentinel 信息:
bash
redis-cli -p 26379
# 列出所有监控 mymaster 的 Sentinel 实例信息(包括自己)。
sentinel sentinels mymaster
# 查看被监控的主节点(master)的详细信息。
# ✅ 用途:快速确认"现在谁是主"、"主是否健康"、"集群规模"。
sentinel master mymaster
# 列出所有从节点(replicas/slaves)的详细信息。
# ✅ 用途:排查"某个从节点是否掉队"、"哪个从最适合升主"。
# slave-priority 故障转移时的优先级(越小越优先)
sentinel replicas mymaster
✅ 六、生产环境最佳实践
| 建议 | 说明 |
|---|---|
| 至少 3 个 Sentinel | 避免脑裂(split-brain) |
| 跨机房部署 | 防止单机房故障导致 Sentinel 全挂 |
| 监控 Sentinel 日志 | 关注 +switch-master 事件 |
| 测试故障转移 | 定期演练(如 kill -9 redis-master) |
| 客户端重试机制 | 写操作失败时自动重试(应对切换窗口) |
Sentinel 的局限性
| 问题 | 说明 |
|---|---|
| 不能扩展写能力 | 仍只有一个主节点处理写 |
| 切换期间短暂不可用 | Failover 过程中写入会失败(通常 < 30 秒) |
| 配置复杂 | 需维护额外的 Sentinel 进程 |
| 不适合超大规模 | 节点数 > 100 时管理困难 |
Redis Cluster ------ 水平扩展 Redis 的官方方案
🧠 为什么需要 Redis Cluster?
| 架构 | 优点 | 缺点 |
|---|---|---|
| 单机 | 简单 | 内存/QPS 有上限 |
| 主从 + Sentinel | 高可用、读扩展 | 写无法扩展,数据仍在单机 |
| Redis Cluster | ✅ 自动分片 ✅ 高可用 ✅ 写/读都可扩展 | 配置稍复杂 |
🔑 核心思想:
把 16384 个哈希槽(hash slots) 分配给多个主节点,每个 key 属于一个槽,从而实现数据分布 + 并行处理。
🔷 一、核心概念:哈希槽(Hash Slot)
- Redis Cluster 将整个 key 空间划分为 16384 个槽(0 ~ 16383)
- 每个主节点负责一部分槽
- key → slot 的映射公式:
python
slot = CRC16(key) % 16384
🌰 举例:
- user:1001 → CRC16 → 12345 → slot 12345
- 如果 slot 12345 分配给 Node A,则 user:1001 存在 Node A
✅ 优势:
- 无需中心路由表:客户端可本地计算 slot
- 扩容/缩容只需迁移部分槽,不影响其他数据
🔷 二、集群架构要求
最小集群配置(生产推荐):
- 至少 3 个主节点(负责数据分片)
- 每个主节点至少 1 个从节点(负责高可用)
- 总共 ≥ 6 个 Redis 实例
⚠️ 官方要求:不能少于 3 主(否则无法达成故障转移共识)
节点角色:
| 角色 | 职责 |
|---|---|
| Master | 负责一部分槽的读写 |
| Replica | 复制 Master 数据,Master 挂了可接替 |
🔧 三、如何搭建 Redis Cluster?
步骤 1:准备 6 个 Redis 实例(以端口为例)
bash
# redis.conf 模板(每个实例不同 port)
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 15000
appendonly yes
⚠️ 关键配置:
- cluster-enabled yes → 启用集群模式
- cluster-config-file → 自动生成的节点信息文件(不要手动改)
步骤 2:启动所有实例
bash
redis-server redis-7000.conf
redis-server redis-7001.conf
...
redis-server redis-7005.conf
步骤 3:创建集群(Redis 5.0+ 使用 redis-cli --cluster)
bash
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
- --cluster-replicas 1 表示 每个主配 1 个从
- 命令会自动分配槽,并提示确认
✅ 输出示例:
text
>>> Assigning 16384 slots to 3 masters.
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:7003 to 127.0.0.1:7000
...
🌰 四、Python 客户端如何连接 Cluster?
不能用普通 Redis()!必须用 RedisCluster。
python
pip install redis-py-cluster
# 或 Redis 4.0+ 官方推荐(但功能较新):
# pip install redis[hiredis] # redis-py 4.0+ 内置 cluster 支持
方式 1:使用 redis-py-cluster(稳定)
python
from rediscluster import RedisCluster
startup_nodes = [
{"host": "127.0.0.1", "port": "7000"},
{"host": "127.0.0.1", "port": "7001"},
# ... 提供部分节点即可,客户端会自动发现全部
]
rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True)
# 像单机一样使用!
rc.set("user:1001:name", "Alice")
print(rc.get("user:1001:name"))
方式 2:使用 redis-py 4.0+ 内置支持(推荐新项目)
python
from redis.cluster import RedisCluster
rc = RedisCluster(
startup_nodes=[{"host": "127.0.0.1", "port": "7000"}],
decode_responses=True
)
rc.set("key", "value")
✅ 客户端会自动:
- 计算 key 所属 slot
- 缓存 slot → node 映射
- 遇到 MOVED 重定向时自动跳转
🔍 五、集群内部如何工作?
场景:客户端写入 SET user:1001 Alice
- 客户端计算 slot = CRC16("user:1001") % 16384 = 12345
- 查本地缓存:slot 12345 → Node B (7001)
- 发送命令到 Node B
- 如果 Node B 不负责该 slot(如刚扩容),返回:
text
MOVED 12345 127.0.0.1:7002
- 客户端更新缓存,并重试到新节点
故障转移(类似 Sentinel):
- 主节点挂了 → 从节点自动提升为主
- 其他节点更新集群拓扑
- 客户端收到 MOVED 或 -CLUSTERDOWN,自动重连
六、重要限制与注意事项
| 限制 | 说明 | 解决方案 |
|---|---|---|
| 不支持多 key 跨槽操作 | MGET key1 key2 若 key1/key2 在不同槽 → 报错 |
用 Hash Tag:user:{1001}:name 和 user:{1001}:email 会落在同一槽 |
| 不支持事务(MULTI/EXEC)跨槽 | 事务内所有 key 必须在同一槽 | 同上,用 Hash Tag |
| 不支持 DB 多数据库 | 只能用 DB 0 | 设计时避免多 DB |
| Lua 脚本必须只操作同一槽的 key | 否则报错 | 脚本中所有 key 加相同 {tag} |
💡 Hash Tag 规则:
{} 中的内容用于计算 slot,忽略其他字符。
例如:
- foo{bar} 和 hello{bar}:world → 同一槽
- foo{bar}1 和 foo{bar}2 → 同一槽
📖 Hash Tag ------ 让多个 key 落在同一个槽的魔法
🔑 核心规则:花括号 {} 内的内容决定槽
✅ Redis 只对 {} 中的第一个闭合部分计算哈希,忽略其他字符。
📌 规则详解:
| Key | 用于计算 slot 的部分 | 说明 |
|---|---|---|
foo{bar} |
bar |
只取 {} 内的内容 |
foo{bar}baz |
bar |
忽略 {} 前后 |
foo{bar}{qux} |
bar |
只取第一个 {} |
foo{}bar |
foo{}bar |
没有有效内容 → 整个 key 参与计算 |
foo{bar |
foo{bar |
无闭合 } → 整个 key 参与计算 |
但是在python中由于经常使用format,所以使用format时需要将{}中的内容用双重花括号包裹
python
user_id = "1001"
# 想生成: user:{1001}:name
# 错误写法(会报 KeyError):
# key = "user:{user_id}:name".format(user_id=user_id) # ❌ {user_id} 被 format 解析
# 正确写法:用双花括号转义
key = f"user:{{{user_id}}}:name"
print(key) # user:{1001}:name ✅
# 或命名参数:
key = "user:{{{user_id}}}:name".format(user_id=user_id)
🛠️ 七、运维常用命令
bash
# 连接到任意集群节点
redis-cli -c -p 7000
# 查看集群状态
127.0.0.1:7000> CLUSTER INFO
# 查看节点列表
127.0.0.1:7000> CLUSTER NODES
# 手动故障转移(主节点执行)
127.0.0.1:7000> CLUSTER FAILOVER
# 添加新主节点(需先 join,再 reshard)
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
# 迁移槽(扩容)
redis-cli --cluster reshard 127.0.0.1:7000
✅ 八、生产环境最佳实践
| 建议 | 说明 |
|---|---|
| 至少 3 主 3 从 | 满足高可用 + 故障转移 |
| 开启 AOF | 防止主从同步时全量 RDB 太大 |
监控 CLUSTER INFO |
关注 cluster_state:ok |
| 客户端启用重试 | 应对短暂 MOVED/ASK |
| 避免大 Key | 单个 key 太大会导致迁移卡住 |
Redis 性能调优 ------ 榨干每一分性能
🔷 一、配置调优(redis.conf)
✅ 1. 内存相关
conf
# 设置最大内存(必须!防 OOM)
maxmemory 8gb
# 内存淘汰策略(根据业务选)
maxmemory-policy allkeys-lru # 通用推荐
# 其他选项:
# - volatile-lru → 只淘汰带过期时间的 key
# - allkeys-lfu → 更适合"热点数据"场景(Redis 4.0+)
# - noeviction → 写满就报错(慎用!)
# 调整 LFU 参数(如果用 allkeys-lfu)
lfu-log-factor 10 # 衰减速度(越大越不敏感)
lfu-decay-time 1 # 分钟级衰减
📌 经验:
- Web 缓存 → allkeys-lru
- 热点排行榜 → allkeys-lfu
✅ 2. 持久化优化
conf
# RDB 快照(适合备份)
save 900 1 # 15分钟至少1次变更
save 300 10 # 5分钟至少10次变更
# 生产建议:关闭 save "" 或仅保留长周期快照
# AOF(高可靠)
appendonly yes
appendfsync everysec # 平衡性能与安全(默认)
# appendfsync always # 最安全但慢(每写同步)
# appendfsync no # 依赖 OS,可能丢数据
# AOF 重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
⚠️ 关键取舍:
- 性能优先 → 关 AOF,开 RDB
- 数据安全优先 → 开 AOF + everysec
✅ 3. 连接与网络
conf
# 客户端连接数(默认 10000,按需调整)
maxclients 20000
# TCP backlog(高并发必调)
tcp-backlog 511 # Linux /proc/sys/net/core/somaxconn 要 ≥ 此值
# 关闭 THP(透明大页)→ 防止 fork 延迟飙升
# 在系统层面执行:
# echo never > /sys/kernel/mm/transparent_hugepage/enabled
💡 THP 是 Redis 延迟毛刺的常见元凶!务必关闭。
🔷 二、命令级优化(避坑指南)
🚫 高危命令黑名单(生产禁用!)
| 命令 | 风险 | 替代方案 |
|---|---|---|
KEYS * |
遍历所有 key,阻塞主线程 | SCAN 渐进式迭代 |
FLUSHDB / FLUSHALL |
清空数据 | 禁用或重命名(rename-command FLUSHDB "") |
SMEMBERS big_set |
返回巨量数据 | SSCAN 分批 |
HGETALL big_hash |
同上 | HSCAN 或只取需要字段 |
python
# 用 SCAN 安全遍历
cursor = '0'
while cursor != 0:
cursor, keys = r.scan(cursor=cursor, match="user:*", count=100)
for key in keys:
# 处理 key
✅ 批量操作 > 单条循环
python
# ❌ 慢:N 次网络往返
for i in range(1000):
r.set(f"key:{i}", "value")
# ✅ 快:1 次 pipeline
with r.pipeline() as pipe:
for i in range(1000):
pipe.set(f"key:{i}", "value")
pipe.execute()
✅ 用合适的数据结构
| 场景 | 推荐结构 | 原因 |
|---|---|---|
| 缓存对象 | Hash | 比多个 string 节省内存 |
| 去重集合 | Set | O(1) 查找 |
| 有序排行榜 | Sorted Set | 支持范围查询 |
| 消息队列 | List (LPUSH/RPOP) 或 Stream | Stream 更可靠 |
🔷 三、内存优化(防 OOM + 降成本)
🔍 1. 监控内存使用
bash
# 查看内存统计
redis-cli info memory
# 关键指标:
# used_memory: 实际使用内存
# mem_fragmentation_ratio: 内存碎片率(理想 1.0~1.5)
# maxmemory: 是否设上限
🔧 2. 降低内存碎片
- 重启 Redis(最直接)
- 开启 activedefrag yes(Redis 4.0+ 自动碎片整理)
conf
activedefrag yes
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
🔧 3. 优化 key 设计
- 缩短 key 名:u:1001:n 代替 user:1001:name
- 用数字 ID 代替字符串:1001 代替 "alice"
- 避免大 Value:单 value > 1MB?考虑拆分或压缩
🔧 4. 使用 MEMORY USAGE 定位大 Key
bash
# 查看单个 key 内存占用
redis-cli MEMORY USAGE user:1001:profile
# 找出 top 大 key(需 redis-cli 4.0+)
redis-cli --bigkeys
🔷 四、网络与客户端优化
✅ 1. 客户端连接池
python
# redis-py 默认有连接池,但要合理配置
pool = redis.ConnectionPool(
host='localhost',
port=6379,
max_connections=20, # 根据并发调
retry_on_timeout=True
)
r = redis.Redis(connection_pool=pool)
⚠️ 避免每次请求新建连接!
✅ 2. 启用 pipelining / batching
- 如前所述,批量操作减少 RTT
- 对于写密集场景,合并命令是性价比最高的优化
✅ 3. 监控慢查询 & 延迟
bash
# 开启慢查询(第 8 讲)
slowlog-log-slower-than 5000
slowlog-max-len 1000
# 实时监控延迟(Redis 5.0+)
redis-cli --latency -h your_redis
✅ 4. 集群模式下避免 MOVED 重定向风暴
- 客户端缓存 slot 映射
- 使用支持集群的客户端(如 redis-py-cluster)
- 用 Hash Tag 减少跨槽操作
🛠️ 五、性能诊断 checklist
当你发现 Redis 变慢,按顺序排查:
- CPU 是否打满? → top 查看 redis-server CPU%
- 内存是否 swap? → free -m 看 swap 使用
- 慢查询日志 → SLOWLOG GET 10
- 大 Key / 大 Value? → redis-cli --bigkeys
- 持久化阻塞? → INFO PERSISTENCE 看 aof_delayed_fsync
- 网络延迟? → redis-cli --latency
- 客户端连接数过多? → INFO CLIENTS 看 connected_clients
✅ 六、终极建议:压测 + 监控
压测工具:
bash
# Redis 自带 benchmark
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000 -q
# 更真实的工具:memtier_benchmark
memtier_benchmark -s 127.0.0.1 -p 6379 --ratio=1:1 --pipeline=10
| 参数 | 含义 | 默认值 | 说明 |
|---|---|---|---|
-h 127.0.0.1 |
目标 Redis 主机 IP | 127.0.0.1 |
可改为远程地址如 192.168.1.10 |
-p 6379 |
目标端口 | 6379 |
Sentinel 或 Cluster 节点需指定对应端口 |
-c 100 |
并发客户端数量(connections) | 50 |
模拟 100 个客户端同时发请求 |
-n 100000 |
总共发送的请求数 | 100000 |
执行 10 万次操作后结束 |
-q |
quiet mode(安静模式) | ❌ 关闭 | 只输出汇总结果,不打印每条命令详情 |
📊 默认测试哪些命令?
如果不指定命令,redis-benchmark 会测试一组典型命令:
- PING_INLINE
- SET
- GET
- INCR
- LPUSH / LPOP
- SADD / SPOP
- ZADD 等
💡 它模拟的是 "简单、高频、单 key" 的操作,适合快速评估基础吞吐能力。
memtier_benchmark 由 Redis Labs(现 Redis Ltd)开发,专为贴近生产场景设计。
| 参数 | 含义 | 默认值 | 说明 |
|---|---|---|---|
-s 127.0.0.1 |
服务器地址(server) | localhost |
注意这里是 -s,不是 -h |
-p 6379 |
端口 | 6379 |
同上 |
--ratio=1:1 |
读写比例(set:get) | 1:10 |
1:1 = 50% SET + 50% GET |
--pipeline=10 |
每个连接使用 pipeline 批量发送 10 条命令 | 1 |
显著提升吞吐(减少 RTT) |
(隐含)-c 50 |
并发客户端数 | 50 |
可通过 -c 指定 |
(隐含)-t 4 |
线程数 | 4 |
可通过 -t 指定 |
例如:
--ratio=1:9→ 10% 写,90% 读(典型缓存场景)--ratio=1:0→ 全写--ratio=0:1→ 全读
✅ 为什么 memtier_benchmark 更真实?
| 能力 | redis-benchmark |
memtier_benchmark |
|---|---|---|
| 自定义读写比例 | ❌ | ✅ |
| 支持 pipeline | 需 -P |
✅ 原生支持 --pipeline |
| 使用随机 key(避免缓存命中偏差) | ❌(固定 key) | ✅(默认生成 key:1, key:2...) |
| 支持数据大小控制 | ❌ | ✅ --data-size=1024 |
| 支持多线程 | ❌(单线程) | ✅ -t 8 开 8 线程 |
| 支持 Redis Cluster | ❌ | ✅ --cluster-mode |
🛠️ 如何安装 memtier_benchmark?
bash
sudo apt-get install build-essential autoconf automake libtool pkg-config \
libssl-dev libevent-dev zlib1g-dev
git clone https://github.com/RedisLabs/memtier_benchmark.git
cd memtier_benchmark
autoreconf -ivf
./configure
make
sudo make install
监控指标(Prometheus + Grafana):
- QPS / 延迟 P99
- 内存使用率
- 拒绝连接数(rejected_connections)
- 主从复制延迟(slave_repl_offset 差值)
🎯 总结:性能调优四象限
| 维度 | 关键动作 |
|---|---|
| 配置 | 设 maxmemory、选淘汰策略、关 THP、调 tcp-backlog |
| 命令 | 禁 KEYS *、用 SCAN、批量操作、选对数据结构 |
| 内存 | 缩短 key、拆分大 Value、监控 --bigkeys、开 activedefrag |
| 网络 | 连接池、Pipeline、集群客户端、监控延迟 |