04-02-A-Redis持久化与高可用详解
📌 导读:Redis 持久化与高可用:RDB、AOF、主从、哨兵、Cluster。本篇回答两个核心问题:Redis 重启数据怎么不丢?Redis 挂了怎么自动恢复?
读完本文你将掌握:
- 能配置 RDB/AOF 混合持久化,让 Redis 重启后数据不丢、恢复又快又完整
- 能搭建主从复制并调优 repl-backlog-size,用 info replication 排查主从延迟
- 能按「场景一/二/三」的排查套路,定位主从延迟、全量同步频繁触发、脑裂数据丢失三类故障
- 能部署 3 哨兵 + 1 主 2 从,让主库宕机后自动完成故障转移
- 能规划 Cluster 的 16384 个槽位分片,并用 min-replicas 配置防脑裂
- 能根据数据量与 QPS 选型单机、主从、哨兵或 Cluster 部署形态
从 RDB/AOF 的原理与配置,到主从复制的同步流程,再到哨兵故障转移、Cluster 分片与脑裂防护,全部配流程图与配置举例。
📑 目录
- 04-02-A-Redis持久化与高可用详解
-
- 一、持久化:RDB 与 AOF
-
- 1.1 为什么需要持久化
- 1.2 RDB(快照)
- 1.3 AOF(追加日志)
- 1.4 RDB vs AOF 对比与选型
- 二、主从复制
-
- 2.1 为什么需要主从
- 2.2 全量同步流程
- 2.3 增量同步(断线重连)
- 2.4 主从配置
- 2.5 主从复制的坑
- 2.6 故障排查实战
-
- 场景一:主从延迟排查
- 场景二:全量同步频繁触发排查
- 场景三:脑裂后数据丢失恢复
- 三、哨兵(Sentinel)
-
- 3.1 哨兵三大职责
- 3.2 故障转移流程
- 3.3 哨兵配置
- 3.4 哨兵故障转移实战排查
- 四、Redis Cluster
-
- 4.1 数据分片:16384 个槽
- 4.2 Cluster 关键约束
- 4.3 客户端重定向
- 4.4 Cluster 搭建与运维命令
- 4.5 脑裂防护
- 4.6 Cluster 脑裂故障排查实战
- 五、部署形态选型
- 六、总结
一、持久化:RDB 与 AOF
1.1 为什么需要持久化
Redis 数据在内存中,进程重启或机器宕机数据就没了。持久化就是把内存数据定期或实时写到磁盘。
1.2 RDB(快照)
原理:fork 子进程,把当前内存数据序列化成二进制快照文件(dump.rdb)。
触发方式:
| SAVE | 同步保存,阻塞主线程(生产禁用) |
| BGSAVE | 异步保存,fork 子进程 |
| 配置触发 | save 900 1 等规则自动触发 |
RDB 配置:
save 900 1 # 900 秒内至少 1 次修改
save 300 10 # 300 秒内至少 10 次修改
save 60 10000 # 60 秒内至少 10000 次修改
dbfilename dump.rdb
dir /var/lib/redis
优缺点:
| 文件紧凑,恢复快 | 两次快照之间的数据会丢 |
| fork 子进程,不阻塞主线程 | 写时复制最坏情况内存翻倍,fork 大实例有毫秒级卡顿 |
| 适合备份、灾备 | 大数据量时 BGSAVE 耗时长 |
1.3 AOF(追加日志)
原理:每条写命令追加到 AOF 文件,重启时重放命令恢复数据。
刷盘策略:
| always | 每条命令都 fsync | 最高,性能最差 |
| everysec | 每秒 fsync(推荐) | 最多丢 1 秒 |
| no | 由操作系统决定 | 最低 |
AOF 配置:
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
auto-aof-rewrite-percentage 100 # 增长 100% 触发重写
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes # 混合持久化
AOF 重写:AOF 文件越来越大时,fork 子进程把当前内存数据生成新 AOF(只保留最终状态),替换旧文件。
1.4 RDB vs AOF 对比与选型
#mermaid-svg-CR3DiTtz9MQfeGeK{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CR3DiTtz9MQfeGeK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CR3DiTtz9MQfeGeK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CR3DiTtz9MQfeGeK .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-CR3DiTtz9MQfeGeK .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-CR3DiTtz9MQfeGeK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CR3DiTtz9MQfeGeK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CR3DiTtz9MQfeGeK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CR3DiTtz9MQfeGeK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CR3DiTtz9MQfeGeK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CR3DiTtz9MQfeGeK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CR3DiTtz9MQfeGeK .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-CR3DiTtz9MQfeGeK .marker.cross{stroke:#0b0b0b;}#mermaid-svg-CR3DiTtz9MQfeGeK svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-CR3DiTtz9MQfeGeK p{margin:0;}#mermaid-svg-CR3DiTtz9MQfeGeK .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-CR3DiTtz9MQfeGeK .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-CR3DiTtz9MQfeGeK .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-CR3DiTtz9MQfeGeK .cluster-label span p{background-color:transparent;}#mermaid-svg-CR3DiTtz9MQfeGeK .label text,#mermaid-svg-CR3DiTtz9MQfeGeK span{fill:#333;color:#333;}#mermaid-svg-CR3DiTtz9MQfeGeK .node rect,#mermaid-svg-CR3DiTtz9MQfeGeK .node circle,#mermaid-svg-CR3DiTtz9MQfeGeK .node ellipse,#mermaid-svg-CR3DiTtz9MQfeGeK .node polygon,#mermaid-svg-CR3DiTtz9MQfeGeK .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-CR3DiTtz9MQfeGeK .rough-node .label text,#mermaid-svg-CR3DiTtz9MQfeGeK .node .label text,#mermaid-svg-CR3DiTtz9MQfeGeK .image-shape .label,#mermaid-svg-CR3DiTtz9MQfeGeK .icon-shape .label{text-anchor:middle;}#mermaid-svg-CR3DiTtz9MQfeGeK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CR3DiTtz9MQfeGeK .rough-node .label,#mermaid-svg-CR3DiTtz9MQfeGeK .node .label,#mermaid-svg-CR3DiTtz9MQfeGeK .image-shape .label,#mermaid-svg-CR3DiTtz9MQfeGeK .icon-shape .label{text-align:center;}#mermaid-svg-CR3DiTtz9MQfeGeK .node.clickable{cursor:pointer;}#mermaid-svg-CR3DiTtz9MQfeGeK .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-CR3DiTtz9MQfeGeK .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-CR3DiTtz9MQfeGeK .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-CR3DiTtz9MQfeGeK .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-CR3DiTtz9MQfeGeK .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-CR3DiTtz9MQfeGeK .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-CR3DiTtz9MQfeGeK .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-CR3DiTtz9MQfeGeK .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-CR3DiTtz9MQfeGeK .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-CR3DiTtz9MQfeGeK .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-CR3DiTtz9MQfeGeK .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-CR3DiTtz9MQfeGeK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CR3DiTtz9MQfeGeK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CR3DiTtz9MQfeGeK rect.text{fill:none;stroke-width:0;}#mermaid-svg-CR3DiTtz9MQfeGeK .icon-shape,#mermaid-svg-CR3DiTtz9MQfeGeK .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-CR3DiTtz9MQfeGeK .icon-shape p,#mermaid-svg-CR3DiTtz9MQfeGeK .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-CR3DiTtz9MQfeGeK .icon-shape .label rect,#mermaid-svg-CR3DiTtz9MQfeGeK .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-CR3DiTtz9MQfeGeK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CR3DiTtz9MQfeGeK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CR3DiTtz9MQfeGeK :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
持久化策略选择。
允许丢几分钟数据
追求恢复速度。
选 RDB
save 900 1
save 300 10
最多丢 1 秒
追求数据完整。
选 AOF
appendfsync everysec
既要快又要完整
生产推荐。
RDB + AOF 混合
aof-use-rdb-preamble yes
| 格式 | 二进制快照 | 追加命令日志 |
| 恢复速度 | 快 | 慢(重放命令) |
| 数据完整性 | 可能丢最后一次快照后的数据 | 最多丢 1 秒(everysec) |
| 文件大小 | 小(压缩) | 大(可重写压缩) |
二、主从复制
2.1 为什么需要主从
- 高可用:主库挂了,从库顶上
- 读扩展:读请求分流到从库
- 数据备份:从库就是一份实时备份
2.2 全量同步流程
#mermaid-svg-ehOZjbDnX6E4qaD8{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ehOZjbDnX6E4qaD8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ehOZjbDnX6E4qaD8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ehOZjbDnX6E4qaD8 .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-ehOZjbDnX6E4qaD8 .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-ehOZjbDnX6E4qaD8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ehOZjbDnX6E4qaD8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ehOZjbDnX6E4qaD8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ehOZjbDnX6E4qaD8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ehOZjbDnX6E4qaD8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ehOZjbDnX6E4qaD8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ehOZjbDnX6E4qaD8 .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-ehOZjbDnX6E4qaD8 .marker.cross{stroke:#0b0b0b;}#mermaid-svg-ehOZjbDnX6E4qaD8 svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-ehOZjbDnX6E4qaD8 p{margin:0;}#mermaid-svg-ehOZjbDnX6E4qaD8 .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-ehOZjbDnX6E4qaD8 .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-ehOZjbDnX6E4qaD8 .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-ehOZjbDnX6E4qaD8 .cluster-label span p{background-color:transparent;}#mermaid-svg-ehOZjbDnX6E4qaD8 .label text,#mermaid-svg-ehOZjbDnX6E4qaD8 span{fill:#333;color:#333;}#mermaid-svg-ehOZjbDnX6E4qaD8 .node rect,#mermaid-svg-ehOZjbDnX6E4qaD8 .node circle,#mermaid-svg-ehOZjbDnX6E4qaD8 .node ellipse,#mermaid-svg-ehOZjbDnX6E4qaD8 .node polygon,#mermaid-svg-ehOZjbDnX6E4qaD8 .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-ehOZjbDnX6E4qaD8 .rough-node .label text,#mermaid-svg-ehOZjbDnX6E4qaD8 .node .label text,#mermaid-svg-ehOZjbDnX6E4qaD8 .image-shape .label,#mermaid-svg-ehOZjbDnX6E4qaD8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-ehOZjbDnX6E4qaD8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ehOZjbDnX6E4qaD8 .rough-node .label,#mermaid-svg-ehOZjbDnX6E4qaD8 .node .label,#mermaid-svg-ehOZjbDnX6E4qaD8 .image-shape .label,#mermaid-svg-ehOZjbDnX6E4qaD8 .icon-shape .label{text-align:center;}#mermaid-svg-ehOZjbDnX6E4qaD8 .node.clickable{cursor:pointer;}#mermaid-svg-ehOZjbDnX6E4qaD8 .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-ehOZjbDnX6E4qaD8 .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-ehOZjbDnX6E4qaD8 .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-ehOZjbDnX6E4qaD8 .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-ehOZjbDnX6E4qaD8 .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-ehOZjbDnX6E4qaD8 .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-ehOZjbDnX6E4qaD8 .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-ehOZjbDnX6E4qaD8 .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-ehOZjbDnX6E4qaD8 .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-ehOZjbDnX6E4qaD8 .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-ehOZjbDnX6E4qaD8 .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-ehOZjbDnX6E4qaD8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ehOZjbDnX6E4qaD8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ehOZjbDnX6E4qaD8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-ehOZjbDnX6E4qaD8 .icon-shape,#mermaid-svg-ehOZjbDnX6E4qaD8 .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-ehOZjbDnX6E4qaD8 .icon-shape p,#mermaid-svg-ehOZjbDnX6E4qaD8 .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-ehOZjbDnX6E4qaD8 .icon-shape .label rect,#mermaid-svg-ehOZjbDnX6E4qaD8 .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-ehOZjbDnX6E4qaD8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ehOZjbDnX6E4qaD8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ehOZjbDnX6E4qaD8 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
哨兵故障转移。
① 哨兵发现主库主观下线 SDOWN。
② 多数哨兵确认客观下线 ODOWN。
③ 哨兵间 Raft选举 Leader。
④ Leader 选新主:优先级 / offset / runid。
⑤ 通知从库复制新主。
⑥ 通知客户端新主地址。
2.3 增量同步(断线重连)
- 从库记录 replid + offset
- 重连时发送 PSYNC replid offset
- 主库 repl_backlog 缓冲区包含该 offset 之后的命令 → 增量同步
- 不包含 → 全量同步
2.4 主从配置
# 从库配置
replicaof 192.168.1.10 6379
replica-read-only yes # 从库只读
repl-backlog-size 64mb # 复制积压缓冲区,生产建议调大(默认 1mb 太小,断线重连易触发全量同步)
docker-compose 一键搭建 1 主 2 从:
version: "3.8"
services:
redis-master:
image: redis:7.2
container_name: redis–master
ports:
– "6379:6379"
command: ["redis-server", "–appendonly", "yes"]
redis-slave-1:
image: redis:7.2
container_name: redis–slave–1
ports:
– "6380:6379"
command: ["redis-server", "–replicaof", "redis-master", "6379", "–replica-read-only", "yes"]
depends_on:
– redis–master
redis-slave-2:
image: redis:7.2
container_name: redis–slave–2
ports:
– "6381:6379"
command: ["redis-server", "–replicaof", "redis-master", "6379", "–replica-read-only", "yes"]
depends_on:
– redis–master
主从通过 –replicaof redis-master 6379 建立复制关系(容器内用服务名 redis-master 解析主库地址);从库通过 –replica-read-only yes 设为只读,防止误写导致主从数据不一致。启动后可用 docker compose up -d 拉起,用 redis-cli -p 6380 info replication 验证从库 master_link_status:up。
2.5 主从复制的坑
| 主从延迟 | 从库单线程回放 + 网络 | 控制大 key、拆分批量写 |
| 全量同步阻塞 | BGSAVE fork 阻塞主库 | 控制内存碎片、THP 设 never |
| 脑裂 | 网络分区后旧主恢复 | min-replicas-to-write 1 |
2.6 故障排查实战
场景一:主从延迟排查
现象:从库读到的数据明显滞后于主库。
排查步骤:
redis-cli -h 192.168.1.11 -p 6379 info replication
输出示例:
# Replication
role:slave
master_host:192.168.1.10
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:102457
slave_priority:100
slave_read_only:1
对比主库的 master_repl_offset,若两者差值持续增大,说明从库回放跟不上。
redis-cli –bigkeys
输出示例:
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
——– summary ——-
Sampled 100000 keys in the keyspace!
Total key length in bytes is 1048576 (avg len 10.49)
Biggest string found so far '"user:10086:session"' has 52428800 bytes
Biggest hash found so far '"order:20260923"' has 1000000 fields
Biggest list found so far '"task:queue"' has 500000 items
监控脚本:定期采集主库 master_repl_offset 与从库 slave_repl_offset,计算差值并输出告警日志:
#!/bin/bash
# redis_repl_monitor.sh —— 主从复制延迟监控脚本
# 用法:./redis_repl_monitor.sh <主库IP> <从库IP> [阈值(默认1000)]
MASTER_HOST="${1:-192.168.1.10}"
SLAVE_HOST="${2:-192.168.1.11}"
THRESHOLD="${3:-1000}"
LOG_FILE="/var/log/redis_repl_monitor.log"
# 采集主库 master_repl_offset
master_offset=$(redis-cli -h "$MASTER_HOST" -p 6379 info replication \\
| awk -F: '/^master_repl_offset/{gsub("\\r","");print $2}')
# 采集从库 slave_repl_offset
slave_offset=$(redis-cli -h "$SLAVE_HOST" -p 6379 info replication \\
| awk -F: '/^slave_repl_offset/{gsub("\\r","");print $2}')
# 计算差值(取绝对值)
lag=$(( master_offset – slave_offset ))
[ "$lag" -lt 0 ] && lag=$(( –lag ))
now=$(date '+%Y-%m-%d %H:%M:%S')
if [ "$lag" -gt "$THRESHOLD" ]; then
echo "[$now] [ALERT] 主从延迟过大:master=$master_offset slave=$slave_offset lag=$lag (阈值=$THRESHOLD)" >> "$LOG_FILE"
echo "[$now] [ALERT] 主从延迟过大:lag=$lag"
else
echo "[$now] [OK] 主从同步正常:master=$master_offset slave=$slave_offset lag=$lag" >> "$LOG_FILE"
fi
crontab 定时执行(每 30 秒采集一次,阈值 1000):
* * * * * /opt/scripts/redis_repl_monitor.sh 192.168.1.10 192.168.1.11 1000 >> /var/log/redis_repl_monitor.log 2>&1
* * * * * sleep 30; /opt/scripts/redis_repl_monitor.sh 192.168.1.10 192.168.1.11 1000 >> /var/log/redis_repl_monitor.log 2>&1
脚本需先 chmod +x redis_repl_monitor.sh,并确保执行用户对 Redis 有 info replication 权限;告警阈值可按业务容忍度调整,延迟持续增大时结合 –bigkeys 定位大 key。
解决:对大 key 拆分(如 hash 按字段分片)、控制单次批量写入量、必要时提升从库硬件。
场景二:全量同步频繁触发排查
现象:主库频繁执行 BGSAVE,从库反复全量同步,主库 CPU/内存飙升。
排查步骤:
redis-cli -p 6379 info replication | grep backlog
输出示例:
repl_backlog_active:1
repl_backlog_size:67108864
repl_backlog_first_byte_offset:99821
repl_backlog_histlen:2636
repl_backlog_histlen 远小于 repl_backlog_size 时,说明缓冲区充足;若从库断线期间写入量超过 repl_backlog_size,就会退化为全量同步。
redis-cli -p 6379 info stats | grep rdb_changes_since_last_save
redis-cli -p 6379 info persistence | grep rdb_bgsave_in_progress
输出示例:
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
解决:调大 repl-backlog-size(建议 256mb~1gb),排查网络抖动(交换机、带宽、TCP 重传),避免从库频繁断线重连。
场景三:脑裂后数据丢失恢复
现象:网络分区期间旧主仍在接受写入,分区恢复后旧主降级为从库,期间写入的数据丢失。
排查步骤:
redis-cli -p 6379 config get min-replicas-to-write min-replicas-max-lag
输出示例:
1) "min-replicas-to-write"
2) "1"
3) "min-replicas-max-lag"
4) "10"
若 min-replicas-to-write 为 0,说明脑裂期间主库会无条件接受写入,数据丢失风险极高。
redis-cli -p 6379 info replication | grep -E "connected_slaves|slave[0-9]"
输出示例:
connected_slaves:2
slave0:ip=192.168.1.11,port=6379,state=online,offset=102457,lag=0
slave1:ip=192.168.1.12,port=6379,state=online,offset=102457,lag=0
# 在旧主上执行,降级为从库并指向新主
redis-cli -p 6379 replicaof 192.168.1.11 6379
# 确认复制状态
redis-cli -p 6379 info replication | grep master_link_status
输出示例:
master_link_status:up
解决:开启 min-replicas-to-write 1 + min-replicas-max-lag 10,脑裂期间旧主写不进去,从数据源头上避免丢失;已丢失的数据需结合 AOF/RDB 备份或业务日志补偿。
三、哨兵(Sentinel)
3.1 哨兵三大职责
#mermaid-svg-kcrOLZg6sviHoKjD{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kcrOLZg6sviHoKjD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kcrOLZg6sviHoKjD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kcrOLZg6sviHoKjD .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-kcrOLZg6sviHoKjD .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-kcrOLZg6sviHoKjD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kcrOLZg6sviHoKjD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kcrOLZg6sviHoKjD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kcrOLZg6sviHoKjD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kcrOLZg6sviHoKjD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kcrOLZg6sviHoKjD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kcrOLZg6sviHoKjD .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-kcrOLZg6sviHoKjD .marker.cross{stroke:#0b0b0b;}#mermaid-svg-kcrOLZg6sviHoKjD svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-kcrOLZg6sviHoKjD p{margin:0;}#mermaid-svg-kcrOLZg6sviHoKjD .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-kcrOLZg6sviHoKjD .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-kcrOLZg6sviHoKjD .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-kcrOLZg6sviHoKjD .cluster-label span p{background-color:transparent;}#mermaid-svg-kcrOLZg6sviHoKjD .label text,#mermaid-svg-kcrOLZg6sviHoKjD span{fill:#333;color:#333;}#mermaid-svg-kcrOLZg6sviHoKjD .node rect,#mermaid-svg-kcrOLZg6sviHoKjD .node circle,#mermaid-svg-kcrOLZg6sviHoKjD .node ellipse,#mermaid-svg-kcrOLZg6sviHoKjD .node polygon,#mermaid-svg-kcrOLZg6sviHoKjD .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-kcrOLZg6sviHoKjD .rough-node .label text,#mermaid-svg-kcrOLZg6sviHoKjD .node .label text,#mermaid-svg-kcrOLZg6sviHoKjD .image-shape .label,#mermaid-svg-kcrOLZg6sviHoKjD .icon-shape .label{text-anchor:middle;}#mermaid-svg-kcrOLZg6sviHoKjD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kcrOLZg6sviHoKjD .rough-node .label,#mermaid-svg-kcrOLZg6sviHoKjD .node .label,#mermaid-svg-kcrOLZg6sviHoKjD .image-shape .label,#mermaid-svg-kcrOLZg6sviHoKjD .icon-shape .label{text-align:center;}#mermaid-svg-kcrOLZg6sviHoKjD .node.clickable{cursor:pointer;}#mermaid-svg-kcrOLZg6sviHoKjD .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-kcrOLZg6sviHoKjD .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-kcrOLZg6sviHoKjD .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-kcrOLZg6sviHoKjD .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-kcrOLZg6sviHoKjD .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-kcrOLZg6sviHoKjD .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-kcrOLZg6sviHoKjD .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-kcrOLZg6sviHoKjD .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-kcrOLZg6sviHoKjD .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-kcrOLZg6sviHoKjD .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-kcrOLZg6sviHoKjD .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-kcrOLZg6sviHoKjD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kcrOLZg6sviHoKjD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kcrOLZg6sviHoKjD rect.text{fill:none;stroke-width:0;}#mermaid-svg-kcrOLZg6sviHoKjD .icon-shape,#mermaid-svg-kcrOLZg6sviHoKjD .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-kcrOLZg6sviHoKjD .icon-shape p,#mermaid-svg-kcrOLZg6sviHoKjD .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-kcrOLZg6sviHoKjD .icon-shape .label rect,#mermaid-svg-kcrOLZg6sviHoKjD .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-kcrOLZg6sviHoKjD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kcrOLZg6sviHoKjD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kcrOLZg6sviHoKjD :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
哨兵三大职责。
① 监控
每秒 PING 主从和哨兵。
② 自动故障转移
主库挂了选举新主。
③ 通知
把新主地址告诉客户端。
3.2 故障转移流程
#mermaid-svg-6Tf1kjgYlpWhRGz0{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-6Tf1kjgYlpWhRGz0 .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .marker.cross{stroke:#0b0b0b;}#mermaid-svg-6Tf1kjgYlpWhRGz0 svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-6Tf1kjgYlpWhRGz0 p{margin:0;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-6Tf1kjgYlpWhRGz0 .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-6Tf1kjgYlpWhRGz0 .cluster-label span p{background-color:transparent;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .label text,#mermaid-svg-6Tf1kjgYlpWhRGz0 span{fill:#333;color:#333;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .node rect,#mermaid-svg-6Tf1kjgYlpWhRGz0 .node circle,#mermaid-svg-6Tf1kjgYlpWhRGz0 .node ellipse,#mermaid-svg-6Tf1kjgYlpWhRGz0 .node polygon,#mermaid-svg-6Tf1kjgYlpWhRGz0 .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .rough-node .label text,#mermaid-svg-6Tf1kjgYlpWhRGz0 .node .label text,#mermaid-svg-6Tf1kjgYlpWhRGz0 .image-shape .label,#mermaid-svg-6Tf1kjgYlpWhRGz0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .rough-node .label,#mermaid-svg-6Tf1kjgYlpWhRGz0 .node .label,#mermaid-svg-6Tf1kjgYlpWhRGz0 .image-shape .label,#mermaid-svg-6Tf1kjgYlpWhRGz0 .icon-shape .label{text-align:center;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .node.clickable{cursor:pointer;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-6Tf1kjgYlpWhRGz0 .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-6Tf1kjgYlpWhRGz0 .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-6Tf1kjgYlpWhRGz0 .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-6Tf1kjgYlpWhRGz0 .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-6Tf1kjgYlpWhRGz0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6Tf1kjgYlpWhRGz0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .icon-shape,#mermaid-svg-6Tf1kjgYlpWhRGz0 .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .icon-shape p,#mermaid-svg-6Tf1kjgYlpWhRGz0 .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .icon-shape .label rect,#mermaid-svg-6Tf1kjgYlpWhRGz0 .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-6Tf1kjgYlpWhRGz0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6Tf1kjgYlpWhRGz0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6Tf1kjgYlpWhRGz0 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
哨兵故障转移。
① 哨兵发现主库
主观下线 SDOWN。
② 多数哨兵确认
客观下线 ODOWN。
③ 哨兵间 Raft
选举 Leader。
④ Leader 选新主:
优先级 / offset / runid。
⑤ 通知从库
复制新主。
⑥ 通知客户端
新主地址。
3.3 哨兵配置
# sentinel.conf
sentinel monitor mymaster 192.168.1.10 6379 2 # 2 票同意才判定下线
sentinel down-after-milliseconds mymaster 5000 # 5 秒无响应判定主观下线
sentinel failover-timeout mymaster 60000 # 故障转移超时
sentinel parallel-syncs mymaster 1 # 同时同步的从库数
部署建议:3 哨兵 + 1 主 2 从,哨兵分布在不同机器 / 机房。
3.4 哨兵故障转移实战排查
1. 查看当前主库地址(确认哨兵视角下谁是主库):
redis-cli -p 26379 sentinel get-master-addr-by-name mymaster
输出示例:
1) "192.168.1.10"
2) "6379"
返回当前主库的 IP 与端口。若故障转移已发生,这里会返回新主库地址,用于确认哨兵是否已切换主从。
2. 查看哨兵视角的主库状态:
redis-cli -p 26379 sentinel master mymaster
输出示例:
1) "name"
2) "mymaster"
3) "ip"
4) "192.168.1.10"
5) "port"
6) "6379"
7) "flags"
8) "master"
9) "num-slaves"
10) "2"
11) "num-other-sentinels"
12) "2"
13) "config-epoch"
14) "1"
关注 flags(master 表示主库在线,s_down/o_down 表示主观/客观下线)、num-slaves(从库数量)与 config-epoch(故障转移代数,每次切换 +1)。
3. 查看从库列表与优先级:
redis-cli -p 26379 sentinel replicas mymaster
输出示例:
1) 1) "name"
2) "192.168.1.11:6379"
3) "ip"
4) "192.168.1.11"
5) "port"
6) "6379"
7) "flags"
8) "slave"
9) "slave-priority"
10) "100"
11) "master-link-status"
12) "ok"
slave-priority 越小优先级越高,故障转移时哨兵优先选它当新主;master-link-status 为 ok 表示该从库与主库复制正常。
4. 模拟主库宕机,观察 SDOWN → ODOWN → failover 完整流程:
# 模拟主库阻塞 30 秒(生产环境请勿执行)
redis-cli -p 6379 debug sleep 30
同时观察哨兵日志(/var/log/redis/sentinel.log):
+sdown master mymaster 192.168.1.10 6379
+odown master mymaster 192.168.1.10 6379 #quorum 2/2
+try-failover master mymaster 192.168.1.10 6379
+vote-for-leader 3a2b1c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a 1
+elected-leader master mymaster 192.168.1.10 6379
+failover-state-select-slave master mymaster 192.168.1.10 6379
+selected-slave slave 192.168.1.11:6379 192.168.1.11 6379 @ mymaster 192.168.1.10 6379
+failover-state-send-slaveof-noone slave 192.168.1.11:6379 192.168.1.11 6379 @ mymaster 192.168.1.10 6379
+failover-state-wait-promotion slave 192.168.1.11:6379 192.168.1.11 6379 @ mymaster 192.168.1.10 6379
+promoted-slave slave 192.168.1.11:6379 192.168.1.11 6379 @ mymaster 192.168.1.10 6379
+failover-state-reconf-slaves master mymaster 192.168.1.10 6379
+slave-reconf-sent slave 192.168.1.12:6379 192.168.1.12 6379 @ mymaster 192.168.1.10 6379
+failover-end-for-timeout master mymaster 192.168.1.10 6379
+failover-end master mymaster 192.168.1.10 6379
+switch-master mymaster 192.168.1.10 6379 192.168.1.11 6379
日志中 +sdown(主观下线)→ +odown(客观下线,达到 quorum)→ +try-failover(发起故障转移)→ +selected-slave(选出新主)→ +promoted-slave(新主晋升)→ +switch-master(完成切换)即为完整流程。
故障转移耗时统计:用 date +%s%N 记录开始与结束时间,或直接统计日志中 +sdown 到 +switch-master 的时间差:
# 记录开始时间
start=$(date +%s%N)
# 模拟主库宕机
redis-cli -p 6379 debug sleep 30
# 等待故障转移完成,确认新主
redis-cli -p 26379 sentinel get-master-addr-by-name mymaster
# 记录结束时间并计算耗时(毫秒)
end=$(date +%s%N)
echo "故障转移耗时:$(( (end – start) / 1000000 )) ms"
输出示例:
故障转移耗时:15230 ms
故障转移耗时主要取决于 down-after-milliseconds(判定下线时间)+ 哨兵选举 + 从库晋升与同步时间。若耗时过长,可适当调小 down-after-milliseconds,并确保从库 slave-priority 配置合理。
四、Redis Cluster
4.1 数据分片:16384 个槽
#mermaid-svg-a79WzDXwqDxTUgUY{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a79WzDXwqDxTUgUY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a79WzDXwqDxTUgUY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a79WzDXwqDxTUgUY .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-a79WzDXwqDxTUgUY .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-a79WzDXwqDxTUgUY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a79WzDXwqDxTUgUY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a79WzDXwqDxTUgUY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a79WzDXwqDxTUgUY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a79WzDXwqDxTUgUY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a79WzDXwqDxTUgUY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a79WzDXwqDxTUgUY .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-a79WzDXwqDxTUgUY .marker.cross{stroke:#0b0b0b;}#mermaid-svg-a79WzDXwqDxTUgUY svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-a79WzDXwqDxTUgUY p{margin:0;}#mermaid-svg-a79WzDXwqDxTUgUY .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-a79WzDXwqDxTUgUY .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-a79WzDXwqDxTUgUY .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-a79WzDXwqDxTUgUY .cluster-label span p{background-color:transparent;}#mermaid-svg-a79WzDXwqDxTUgUY .label text,#mermaid-svg-a79WzDXwqDxTUgUY span{fill:#333;color:#333;}#mermaid-svg-a79WzDXwqDxTUgUY .node rect,#mermaid-svg-a79WzDXwqDxTUgUY .node circle,#mermaid-svg-a79WzDXwqDxTUgUY .node ellipse,#mermaid-svg-a79WzDXwqDxTUgUY .node polygon,#mermaid-svg-a79WzDXwqDxTUgUY .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-a79WzDXwqDxTUgUY .rough-node .label text,#mermaid-svg-a79WzDXwqDxTUgUY .node .label text,#mermaid-svg-a79WzDXwqDxTUgUY .image-shape .label,#mermaid-svg-a79WzDXwqDxTUgUY .icon-shape .label{text-anchor:middle;}#mermaid-svg-a79WzDXwqDxTUgUY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a79WzDXwqDxTUgUY .rough-node .label,#mermaid-svg-a79WzDXwqDxTUgUY .node .label,#mermaid-svg-a79WzDXwqDxTUgUY .image-shape .label,#mermaid-svg-a79WzDXwqDxTUgUY .icon-shape .label{text-align:center;}#mermaid-svg-a79WzDXwqDxTUgUY .node.clickable{cursor:pointer;}#mermaid-svg-a79WzDXwqDxTUgUY .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-a79WzDXwqDxTUgUY .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-a79WzDXwqDxTUgUY .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-a79WzDXwqDxTUgUY .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-a79WzDXwqDxTUgUY .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-a79WzDXwqDxTUgUY .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-a79WzDXwqDxTUgUY .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-a79WzDXwqDxTUgUY .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-a79WzDXwqDxTUgUY .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-a79WzDXwqDxTUgUY .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-a79WzDXwqDxTUgUY .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-a79WzDXwqDxTUgUY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a79WzDXwqDxTUgUY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a79WzDXwqDxTUgUY rect.text{fill:none;stroke-width:0;}#mermaid-svg-a79WzDXwqDxTUgUY .icon-shape,#mermaid-svg-a79WzDXwqDxTUgUY .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-a79WzDXwqDxTUgUY .icon-shape p,#mermaid-svg-a79WzDXwqDxTUgUY .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-a79WzDXwqDxTUgUY .icon-shape .label rect,#mermaid-svg-a79WzDXwqDxTUgUY .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-a79WzDXwqDxTUgUY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a79WzDXwqDxTUgUY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a79WzDXwqDxTUgUY :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Cluster 数据分片。
key 计算 CRC16,
对 16384 取模。
slot = CRC16(key) % 16384。
slot 0 ~ 5460,
→ 主节点 A。
slot 5461 ~ 10922,
→ 主节点 B。
slot 10923 ~ 16383,
→ 主节点 C。
4.2 Cluster 关键约束
| 多 key 操作 | 必须用 {hashtag} 保证同 slot |
| 事务 / Lua | 只能操作同 slot 的 key |
| 数据库 | 只有 db0,不支持 SELECT |
| 最小节点 | 3 主(推荐 3 主 3 从) |
4.3 客户端重定向
- MOVED:slot 已迁移到其他节点,客户端更新路由表
- ASK:slot 正在迁移,临时重定向到目标节点
4.4 Cluster 搭建与运维命令
1. 创建集群(3 主 3 从,–cluster-replicas 1 表示每个主节点配 1 个从节点):
redis-cli –cluster create 192.168.1.10:6379 192.168.1.11:6379 192.168.1.12:6379 192.168.1.13:6379 192.168.1.14:6379 192.168.1.15:6379 –cluster-replicas 1
输出示例:
>>> Performing hash slots allocation on 6 nodes…
Master[0] -> Slots 0 – 5460
Master[1] -> Slots 5461 – 10922
Master[2] -> Slots 10923 – 16383
一键把 6 个节点组成 3 主 3 从集群,自动完成 16384 个槽位的分配。
2. 添加节点(把新节点加入集群):
redis-cli –cluster add-node 192.168.1.16:6379 192.168.1.10:6379
输出示例:
>>> Adding node 192.168.1.16:6379 to cluster 192.168.1.10:6379
>>> Send CLUSTER MEET to node 192.168.1.16:6379 to make it join the cluster.
[OK] New node added correctly.
新节点加入后默认不持有任何槽位,需配合 –cluster reshard 迁移槽位才能分担数据。
3. 在线迁移槽位(把 500 个槽从源节点迁到目标节点):
redis-cli –cluster reshard 192.168.1.10:6379 –cluster-from 192.168.1.10:6379 –cluster-to 192.168.1.16:6379 –cluster-slots 500 –cluster-yes
输出示例:
Moving slot 0 from 192.168.1.10:6379 to 192.168.1.16:6379:
槽位在线迁移实战:下面演示把 500 个槽从源节点 192.168.1.10:6379 在线迁移到目标节点 192.168.1.16:6379 的完整过程。
① 迁移前确认源节点槽位分布:
redis-cli -p 6379 cluster nodes
输出示例:
e7d1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9 192.168.1.10:6379@16379 myself,master – 0 0 1 connected 0-5460
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 192.168.1.16:6379@16379 master – 0 0 2 connected
迁移前目标节点 192.168.1.16:6379 不持有任何槽位(connected 后无槽位范围),源节点持有 0-5460 共 5461 个槽。
② 执行在线迁移(把 500 个槽从源节点迁到目标节点):
redis-cli –cluster reshard 192.168.1.10:6379 \\
–cluster-from e7d1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9 \\
–cluster-to a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 \\
–cluster-slots 500 –cluster-yes
输出示例:
>>> Performing Cluster Check (using node 192.168.1.10:6379)
M: e7d1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9 192.168.1.10:6379
slots:[0-5460] (5461 slots) master
M: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 192.168.1.16:6379
slots:[] (0 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots…
>>> Check slots coverage…
[OK] All 16384 slots covered.
>>> Ready to move 500 slots.
Source nodes:
M: e7d1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9 192.168.1.10:6379
Destination node:
M: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 192.168.1.16:6379
Moving slot 0 from 192.168.1.10:6379 to 192.168.1.16:6379:
Moving slot 1 from 192.168.1.10:6379 to 192.168.1.16:6379:
…
Moving slot 499 from 192.168.1.10:6379 to 192.168.1.16:6379:
–cluster-from 与 –cluster-to 传节点 id(不是 IP:端口),–cluster-slots 500 指定迁移槽数,–cluster-yes 跳过交互确认。迁移是逐槽进行的,每个槽内的 key 会被 MIGRATE 命令搬到目标节点,期间服务不中断。
③ 迁移后确认槽位归属:
redis-cli -p 6379 cluster nodes
输出示例:
e7d1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9 192.168.1.10:6379@16379 myself,master – 0 0 1 connected 500-5460
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 192.168.1.16:6379@16379 master – 0 0 2 connected 0-499
迁移完成后,源节点槽位变为 500-5460,目标节点持有 0-499 共 500 个槽,槽位不重叠、总数仍为 16384。
④ 迁移期间的 ASK 重定向机制:
迁移是逐槽进行的,某个槽在迁移过程中会处于「迁移中」状态。此时客户端访问该槽的 key,会经历如下流程:
#mermaid-svg-vafRYEncIieXoD1M{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vafRYEncIieXoD1M .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vafRYEncIieXoD1M .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vafRYEncIieXoD1M .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-vafRYEncIieXoD1M .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-vafRYEncIieXoD1M .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vafRYEncIieXoD1M .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vafRYEncIieXoD1M .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vafRYEncIieXoD1M .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vafRYEncIieXoD1M .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vafRYEncIieXoD1M .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vafRYEncIieXoD1M .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-vafRYEncIieXoD1M .marker.cross{stroke:#0b0b0b;}#mermaid-svg-vafRYEncIieXoD1M svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-vafRYEncIieXoD1M p{margin:0;}#mermaid-svg-vafRYEncIieXoD1M .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-vafRYEncIieXoD1M .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-vafRYEncIieXoD1M .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-vafRYEncIieXoD1M .cluster-label span p{background-color:transparent;}#mermaid-svg-vafRYEncIieXoD1M .label text,#mermaid-svg-vafRYEncIieXoD1M span{fill:#333;color:#333;}#mermaid-svg-vafRYEncIieXoD1M .node rect,#mermaid-svg-vafRYEncIieXoD1M .node circle,#mermaid-svg-vafRYEncIieXoD1M .node ellipse,#mermaid-svg-vafRYEncIieXoD1M .node polygon,#mermaid-svg-vafRYEncIieXoD1M .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-vafRYEncIieXoD1M .rough-node .label text,#mermaid-svg-vafRYEncIieXoD1M .node .label text,#mermaid-svg-vafRYEncIieXoD1M .image-shape .label,#mermaid-svg-vafRYEncIieXoD1M .icon-shape .label{text-anchor:middle;}#mermaid-svg-vafRYEncIieXoD1M .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vafRYEncIieXoD1M .rough-node .label,#mermaid-svg-vafRYEncIieXoD1M .node .label,#mermaid-svg-vafRYEncIieXoD1M .image-shape .label,#mermaid-svg-vafRYEncIieXoD1M .icon-shape .label{text-align:center;}#mermaid-svg-vafRYEncIieXoD1M .node.clickable{cursor:pointer;}#mermaid-svg-vafRYEncIieXoD1M .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-vafRYEncIieXoD1M .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-vafRYEncIieXoD1M .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-vafRYEncIieXoD1M .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-vafRYEncIieXoD1M .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-vafRYEncIieXoD1M .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-vafRYEncIieXoD1M .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-vafRYEncIieXoD1M .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-vafRYEncIieXoD1M .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-vafRYEncIieXoD1M .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-vafRYEncIieXoD1M .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-vafRYEncIieXoD1M div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vafRYEncIieXoD1M .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vafRYEncIieXoD1M rect.text{fill:none;stroke-width:0;}#mermaid-svg-vafRYEncIieXoD1M .icon-shape,#mermaid-svg-vafRYEncIieXoD1M .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-vafRYEncIieXoD1M .icon-shape p,#mermaid-svg-vafRYEncIieXoD1M .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-vafRYEncIieXoD1M .icon-shape .label rect,#mermaid-svg-vafRYEncIieXoD1M .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-vafRYEncIieXoD1M .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vafRYEncIieXoD1M .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vafRYEncIieXoD1M :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
客户端访问迁移中的 slot。
① 客户端按路由表
访问源节点。
② 源节点发现
该 slot 正在迁移。
③ 源节点返回
ASK 重定向,
+ 目标节点地址。
④ 客户端发送
ASKING 命令,
再发原命令。
⑤ 目标节点
执行命令并返回结果。
客户端访问迁移中 slot 的 key 时,源节点返回:
(error) ASK 500 192.168.1.16:6379
ASK 与 MOVED 的区别:MOVED 表示槽位已永久迁移到目标节点,客户端应更新本地路由表,后续请求直接发往新节点;ASK 表示槽位正在迁移,只是本次请求临时重定向到目标节点,客户端不更新路由表,下次访问仍先发往源节点。收到 ASK 后,客户端需先向目标节点发送 ASKING 命令(表示「我知道这个槽在迁移,请允许我访问」),再发送原始命令,目标节点才会执行。
⑤ 迁移期间客户端访问示例:
# 访问迁移中的 key(假设 user:10086 落在 slot 500)
redis-cli -c -p 6379 get user:10086
输出示例:
-> Redirected to slot [500] located at 192.168.1.16:6379
"hello"
redis-cli -c(cluster 模式)会自动处理 ASK/MOVED 重定向:收到 ASK 时自动发送 ASKING 后重试,对客户端透明。生产环境使用支持 Cluster 的客户端(如 Jedis、Lettuce、Redisson)时,重定向由客户端库自动完成。
4. 查看槽位分配:
redis-cli -p 6379 cluster info
输出示例:
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_known_nodes:6
查看集群整体状态与槽位分配情况,cluster_state:ok 表示集群健康。
5. 查看节点状态:
redis-cli -p 6379 cluster nodes
输出示例:
e7d1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9 192.168.1.10:6379@16379 myself,master – 0 0 1 connected 0-5460
查看每个节点的 id、角色、地址与负责的槽位范围,用于确认主从关系与槽位归属。
6. docker-compose 一键搭建 3 主 3 从 Cluster:
version: "3.8"
services:
redis-cluster-master-1:
image: redis:7.2
container_name: redis–cluster–master–1
network_mode: host
command: ["redis-server", "–port", "6379", "–cluster-enabled", "yes", "–cluster-config-file", "nodes-6379.conf", "–cluster-node-timeout", "5000", "–appendonly", "yes"]
redis-cluster-master-2:
image: redis:7.2
container_name: redis–cluster–master–2
network_mode: host
command: ["redis-server", "–port", "6380", "–cluster-enabled", "yes", "–cluster-config-file", "nodes-6380.conf", "–cluster-node-timeout", "5000", "–appendonly", "yes"]
redis-cluster-master-3:
image: redis:7.2
container_name: redis–cluster–master–3
network_mode: host
command: ["redis-server", "–port", "6381", "–cluster-enabled", "yes", "–cluster-config-file", "nodes-6381.conf", "–cluster-node-timeout", "5000", "–appendonly", "yes"]
redis-cluster-slave-1:
image: redis:7.2
container_name: redis–cluster–slave–1
network_mode: host
command: ["redis-server", "–port", "6382", "–cluster-enabled", "yes", "–cluster-config-file", "nodes-6382.conf", "–cluster-node-timeout", "5000", "–appendonly", "yes"]
redis-cluster-slave-2:
image: redis:7.2
container_name: redis–cluster–slave–2
network_mode: host
command: ["redis-server", "–port", "6383", "–cluster-enabled", "yes", "–cluster-config-file", "nodes-6383.conf", "–cluster-node-timeout", "5000", "–appendonly", "yes"]
redis-cluster-slave-3:
image: redis:7.2
container_name: redis–cluster–slave–3
network_mode: host
command: ["redis-server", "–port", "6384", "–cluster-enabled", "yes", "–cluster-config-file", "nodes-6384.conf", "–cluster-node-timeout", "5000", "–appendonly", "yes"]
每个节点都开启 cluster-enabled yes 进入集群模式;cluster-config-file 指定集群状态文件(每个节点独立,不能共用);cluster-node-timeout 5000 表示节点间 5 秒无响应即判定疑似下线。这里使用 network_mode: host 让容器直接复用宿主机网络,避免端口映射导致集群节点间通过容器 IP 通信失败。启动后可用 docker compose up -d 拉起全部 6 个节点。
7. 初始化集群(把 6 个节点组成 3 主 3 从,–cluster-replicas 1 表示每个主节点配 1 个从节点):
redis-cli –cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 –cluster-replicas 1
输出示例:
>>> Performing hash slots allocation on 6 nodes…
Master[0] -> Slots 0 – 5460
Master[1] -> Slots 5461 – 10922
Master[2] -> Slots 10923 – 16383
Adding replica 127.0.0.1:6383 to 127.0.0.1:6379
Adding replica 127.0.0.1:6384 to 127.0.0.1:6380
Adding replica 127.0.0.1:6382 to 127.0.0.1:6381
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
…
[OK] All 16384 slots covered
命令会自动把 16384 个槽位均分给 3 个主节点(0-5460、5461-10922、10923-16383),并为每个主节点分配 1 个从节点。执行过程中会询问是否接受槽位分配方案,输入 yes 确认即可。
8. 验证集群状态:
redis-cli –cluster check 127.0.0.1:6379
输出示例:
127.0.0.1:6379 (e7d1f2a3…) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:6380 (a1b2c3d4…) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:6381 (f4e5d6c7…) -> 0 keys | 5462 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: e7d1f2a3… 127.0.0.1:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: a1b2c3d4… 127.0.0.1:6380
slots:[5461-10922] (5461 slots) master
1 additional replica(s)
M: f4e5d6c7… 127.0.0.1:6381
slots:[10923-16383] (5462 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots…
>>> Check slots coverage…
[OK] All 16384 slots covered.
redis-cli –cluster check 会逐节点检查槽位分配、主从关系与槽位覆盖情况。看到 [OK] All 16384 slots covered 即表示集群健康、16384 个槽位全部有主节点负责,可以正常对外服务。
9. Java 客户端操作 Cluster 实战:
上面都是命令行操作,实际业务中我们通过 Java 客户端访问 Cluster。下面以 Jedis 为例,演示连接、读写、槽位重定向与故障转移感知的完整写法。
① 引入依赖(Maven):
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.1.0</version>
</dependency>
② 连接 Cluster 并读写(Jedis 会自动处理 MOVED/ASK 重定向):
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;
import java.util.HashSet;
import java.util.Set;
public class ClusterDemo {
public static void main(String[] args) {
// 只需提供任意一个节点的地址,Jedis 会自动拉取全集群拓扑
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 6379));
nodes.add(new HostAndPort("127.0.0.1", 6380));
nodes.add(new HostAndPort("127.0.0.1", 6381));
try (JedisCluster cluster = new JedisCluster(nodes, 5000, 5000, 3)) {
// 写入:key 会按 CRC16(key) % 16384 路由到对应主节点
cluster.set("user:10086", "hello");
cluster.set("order:20260923", "paid");
// 读取:自动跟随槽位路由
String user = cluster.get("user:10086");
System.out.println("user:10086 = " + user);
// 多 key 操作必须用 {hashtag} 保证落在同一 slot
cluster.mset("{cart:10086}:item1", "apple", "{cart:10086}:item2", "banana");
System.out.println("cart items = " + cluster.mget("{cart:10086}:item1", "{cart:10086}:item2"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
JedisCluster 构造参数依次为:节点集合、连接超时、读取超时、最大重试次数。客户端内部维护一份槽位路由表,收到 MOVED 会更新路由表并重试,收到 ASK 会先发 ASKING 再重试,对业务代码完全透明。
③ 槽位在线迁移期间的 Java 访问(验证 ASK 重定向对客户端透明):
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;
import java.util.HashSet;
import java.util.Set;
public class ClusterReshardAccess {
public static void main(String[] args) throws Exception {
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 6379));
try (JedisCluster cluster = new JedisCluster(nodes, 5000, 5000, 3)) {
// 迁移前写入一批 key
for (int i = 0; i < 1000; i++) {
cluster.set("migrate:key:" + i, "value-" + i);
}
System.out.println("迁移前写入完成");
// 模拟迁移期间持续读写(此时部分 slot 处于迁移中,会触发 ASK 重定向)
for (int i = 0; i < 1000; i++) {
String v = cluster.get("migrate:key:" + i);
if (!("value-" + i).equals(v)) {
System.out.println("数据不一致: key=" + i + " value=" + v);
}
}
System.out.println("迁移期间读写全部成功,ASK 重定向对客户端透明");
}
}
}
迁移期间客户端无需任何改动:JedisCluster 收到 ASK 后自动发送 ASKING 并重试,业务代码无感知。若使用 Lettuce 或 Redisson,同样内置了重定向处理,只需在连接时传入集群节点列表即可。
④ 故障转移感知(主节点宕机后,客户端自动切换到新主):
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;
import java.util.HashSet;
import java.util.Set;
public class ClusterFailoverAware {
public static void main(String[] args) throws Exception {
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 6379));
nodes.add(new HostAndPort("127.0.0.1", 6380));
nodes.add(new HostAndPort("127.0.0.1", 6381));
try (JedisCluster cluster = new JedisCluster(nodes, 5000, 5000, 3)) {
cluster.set("failover:demo", "before");
// 模拟主节点宕机(生产环境请勿执行)
// redis-cli -p 6379 debug sleep 30
// 故障转移完成后,客户端自动感知新主,读写不受影响
String v = cluster.get("failover:demo");
System.out.println("故障转移后读取 = " + v);
cluster.set("failover:demo", "after");
System.out.println("故障转移后写入成功");
}
}
}
主节点宕机后,集群完成故障转移,JedisCluster 通过 MOVED 重定向自动感知新主地址并更新路由表,业务无需重启。建议在客户端侧开启连接池并设置合理的重试次数,以平滑度过故障转移窗口。
⑤ 连接池化(生产推荐,避免频繁创建连接):
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashSet;
import java.util.Set;
public class ClusterPoolDemo {
public static JedisCluster buildCluster() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200); // 最大连接数
poolConfig.setMaxIdle(50); // 最大空闲连接
poolConfig.setMinIdle(10); // 最小空闲连接
poolConfig.setMaxWaitMillis(3000); // 获取连接最大等待时间
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 6379));
nodes.add(new HostAndPort("127.0.0.1", 6380));
nodes.add(new HostAndPort("127.0.0.1", 6381));
return new JedisCluster(nodes, 5000, 5000, 3, poolConfig);
}
public static void main(String[] args) {
try (JedisCluster cluster = buildCluster()) {
cluster.set("pool:demo", "ok");
System.out.println(cluster.get("pool:demo"));
}
}
}
生产环境务必使用连接池:JedisCluster 内部为每个节点维护一个连接池,setMaxTotal 需按节点数 × 单节点并发合理设置,避免连接数不足导致请求排队。
4.5 脑裂防护
# 主库至少要有 1 个从库连接才接受写
min-replicas-to-write 1
min-replicas-max-lag 10
4.6 Cluster 脑裂故障排查实战
现象:网络分区期间旧主仍在接受写入,分区恢复后旧主降级为从库,期间写入的数据丢失,且集群可能短暂进入 cluster_state:fail。
排查步骤:
1. 查看集群状态,确认 cluster_state:fail 的判定标准:
redis-cli -p 6379 cluster info
输出示例(健康状态):
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_state:fail 的判定标准:当任意一个持有槽位的主节点处于 fail 状态(即该主节点不可达,且其从节点未能及时接管),或任意一个槽位处于 fail 状态(cluster_slots_fail > 0)时,集群会拒绝服务,cluster_state 变为 fail。此时客户端会收到 CLUSTERDOWN The cluster is down 错误。若只是部分槽位处于 pfail(疑似下线)状态,集群仍可对外服务,但需尽快处理。
2. 检查主从角色是否错乱:
redis-cli -p 6379 cluster nodes
输出示例(脑裂后角色错乱):
e7d1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9 192.168.1.10:6379@16379 myself,slave – 0 0 1 connected 0-5460
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 192.168.1.11:6379@16379 master – 0 0 2 connected 0-5460
正常情况下,每个持有槽位的主节点应只有一个 master 角色且槽位不重叠。若发现两个节点同时以 master 角色持有同一段槽位(如上面 192.168.1.10 与 192.168.1.11 都显示 connected 0-5460),说明发生了脑裂——旧主未及时降级,新主已接管同一批槽位。此时旧主上的写入会丢失,需立即确认 min-replicas 配置并手动降级旧主。
3. 验证 min-replicas-to-write 1 配置是否生效:
redis-cli -p 6379 config get min-replicas-to-write min-replicas-max-lag
输出示例:
1) "min-replicas-to-write"
2) "1"
3) "min-replicas-max-lag"
4) "10"
配置生效后,当主库在 min-replicas-max-lag(10 秒)内没有任何从库在线时,主库会拒绝写入,返回 (error) NOREPLICAS Not enough good replicas to write.。验证方法:断开主库的所有从库连接后尝试写入:
redis-cli -p 6379 set user:10086 "hello"
预期输出(从库全部断开后):
(error) NOREPLICAS Not enough good replicas to write.
恢复从库连接后再次写入,应返回 OK,说明 min-replicas-to-write 已生效,脑裂期间旧主无法接受写入,从数据源头上避免丢失。
4. 脑裂后数据补偿的两种方案:
方案一:AOF 重放(旧主开启了 AOF 且脑裂期间有写入时):
# 1. 停止旧主,避免继续写入
redis-cli -p 6379 shutdown nosave
# 2. 备份旧主的 AOF 文件
cp /var/lib/redis/appendonly.aof /var/lib/redis/appendonly.aof.bak
# 3. 用 redis-check-aof 修复并截断损坏的尾部
redis-check-aof –fix /var/lib/redis/appendonly.aof
# 4. 重启旧主,AOF 重放恢复脑裂期间写入的数据
redis-server /etc/redis/redis.conf
AOF 重放适合脑裂期间写入量不大、且旧主 AOF 文件完整的场景。注意:重放前需先确认这些数据没有与新主数据冲突,否则可能覆盖新主已写入的正确数据。
方案二:业务日志回放(AOF 不可用或数据冲突时):
# 1. 从业务日志中提取脑裂时间窗口内的写命令(示例:从应用日志过滤 SET/DEL 等写操作)
grep -E "SET|DEL|HSET|LPUSH" /var/log/app/redis_write.log \\
| awk -F'[][]' '$2 >= "2026-09-23 10:00:00" && $2 <= "2026-09-23 10:05:00"' \\
> /tmp/split_brain_replay.txt
# 2. 逐条回放到新主(需先确认 key 归属的 slot 在新主上)
redis-cli -p 6379 –pipe < /tmp/split_brain_replay.txt
# 3. 校验回放结果
redis-cli -p 6379 mget user:10086 order:20260923
业务日志回放适合脑裂期间写入量大、或 AOF 文件已损坏的场景。回放前务必先核对 key 的 slot 归属(用 redis-cli -p 6379 cluster keyslot user:10086),确保目标 key 由新主持有,避免回放到错误节点。
解决:开启 min-replicas-to-write 1 + min-replicas-max-lag 10 从源头防脑裂写入;已丢失数据按「AOF 重放」或「业务日志回放」两种方案补偿,回放前先确认数据无冲突、slot 归属正确。
五、部署形态选型
#mermaid-svg-wjx5JCa9bOuUtjHf{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wjx5JCa9bOuUtjHf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wjx5JCa9bOuUtjHf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wjx5JCa9bOuUtjHf .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-wjx5JCa9bOuUtjHf .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-wjx5JCa9bOuUtjHf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wjx5JCa9bOuUtjHf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wjx5JCa9bOuUtjHf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wjx5JCa9bOuUtjHf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wjx5JCa9bOuUtjHf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wjx5JCa9bOuUtjHf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wjx5JCa9bOuUtjHf .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-wjx5JCa9bOuUtjHf .marker.cross{stroke:#0b0b0b;}#mermaid-svg-wjx5JCa9bOuUtjHf svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-wjx5JCa9bOuUtjHf p{margin:0;}#mermaid-svg-wjx5JCa9bOuUtjHf .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-wjx5JCa9bOuUtjHf .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-wjx5JCa9bOuUtjHf .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-wjx5JCa9bOuUtjHf .cluster-label span p{background-color:transparent;}#mermaid-svg-wjx5JCa9bOuUtjHf .label text,#mermaid-svg-wjx5JCa9bOuUtjHf span{fill:#333;color:#333;}#mermaid-svg-wjx5JCa9bOuUtjHf .node rect,#mermaid-svg-wjx5JCa9bOuUtjHf .node circle,#mermaid-svg-wjx5JCa9bOuUtjHf .node ellipse,#mermaid-svg-wjx5JCa9bOuUtjHf .node polygon,#mermaid-svg-wjx5JCa9bOuUtjHf .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-wjx5JCa9bOuUtjHf .rough-node .label text,#mermaid-svg-wjx5JCa9bOuUtjHf .node .label text,#mermaid-svg-wjx5JCa9bOuUtjHf .image-shape .label,#mermaid-svg-wjx5JCa9bOuUtjHf .icon-shape .label{text-anchor:middle;}#mermaid-svg-wjx5JCa9bOuUtjHf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wjx5JCa9bOuUtjHf .rough-node .label,#mermaid-svg-wjx5JCa9bOuUtjHf .node .label,#mermaid-svg-wjx5JCa9bOuUtjHf .image-shape .label,#mermaid-svg-wjx5JCa9bOuUtjHf .icon-shape .label{text-align:center;}#mermaid-svg-wjx5JCa9bOuUtjHf .node.clickable{cursor:pointer;}#mermaid-svg-wjx5JCa9bOuUtjHf .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-wjx5JCa9bOuUtjHf .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-wjx5JCa9bOuUtjHf .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-wjx5JCa9bOuUtjHf .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-wjx5JCa9bOuUtjHf .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-wjx5JCa9bOuUtjHf .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-wjx5JCa9bOuUtjHf .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-wjx5JCa9bOuUtjHf .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-wjx5JCa9bOuUtjHf .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-wjx5JCa9bOuUtjHf .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-wjx5JCa9bOuUtjHf .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-wjx5JCa9bOuUtjHf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wjx5JCa9bOuUtjHf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wjx5JCa9bOuUtjHf rect.text{fill:none;stroke-width:0;}#mermaid-svg-wjx5JCa9bOuUtjHf .icon-shape,#mermaid-svg-wjx5JCa9bOuUtjHf .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-wjx5JCa9bOuUtjHf .icon-shape p,#mermaid-svg-wjx5JCa9bOuUtjHf .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-wjx5JCa9bOuUtjHf .icon-shape .label rect,#mermaid-svg-wjx5JCa9bOuUtjHf .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-wjx5JCa9bOuUtjHf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wjx5JCa9bOuUtjHf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wjx5JCa9bOuUtjHf :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Redis 部署选型。
数据量 < 10GB,
QPS < 10 万。
单机 + 主从,
简单可靠。
需要自动故障转移,
数据量 < 10GB。
哨兵模式,
3 哨兵 + 1 主 2 从。
数据量 > 10GB,
或 QPS > 10 万。
Cluster 模式,
3 主 3 从起步。
| 单机 | ❌ | ❌ 无扩展 | 开发 / 测试 |
| 主从复制 | 半自动 | 读扩展 | 读多写少 |
| 哨兵 | ✅ 自动故障转移 | 读扩展 | 中小规模 |
| Cluster | ✅ 自动故障转移 | 读写均可扩展 | 大规模 |
六、总结
#mermaid-svg-L9higWB7Ds6ItxVV{font-family:Microsoft YaHei;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-L9higWB7Ds6ItxVV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-L9higWB7Ds6ItxVV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-L9higWB7Ds6ItxVV .error-icon{fill:hsl(220.5882352941, 100%, 98.3333333333%);}#mermaid-svg-L9higWB7Ds6ItxVV .error-text{fill:rgb(8.5000000002, 5.7500000001, 0);stroke:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-L9higWB7Ds6ItxVV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-L9higWB7Ds6ItxVV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-L9higWB7Ds6ItxVV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-L9higWB7Ds6ItxVV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-L9higWB7Ds6ItxVV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-L9higWB7Ds6ItxVV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-L9higWB7Ds6ItxVV .marker{fill:#0b0b0b;stroke:#0b0b0b;}#mermaid-svg-L9higWB7Ds6ItxVV .marker.cross{stroke:#0b0b0b;}#mermaid-svg-L9higWB7Ds6ItxVV svg{font-family:Microsoft YaHei;font-size:16px;}#mermaid-svg-L9higWB7Ds6ItxVV p{margin:0;}#mermaid-svg-L9higWB7Ds6ItxVV .label{font-family:Microsoft YaHei;color:#333;}#mermaid-svg-L9higWB7Ds6ItxVV .cluster-label text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-L9higWB7Ds6ItxVV .cluster-label span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-L9higWB7Ds6ItxVV .cluster-label span p{background-color:transparent;}#mermaid-svg-L9higWB7Ds6ItxVV .label text,#mermaid-svg-L9higWB7Ds6ItxVV span{fill:#333;color:#333;}#mermaid-svg-L9higWB7Ds6ItxVV .node rect,#mermaid-svg-L9higWB7Ds6ItxVV .node circle,#mermaid-svg-L9higWB7Ds6ItxVV .node ellipse,#mermaid-svg-L9higWB7Ds6ItxVV .node polygon,#mermaid-svg-L9higWB7Ds6ItxVV .node path{fill:#fff4dd;stroke:hsl(40.5882352941, 60%, 83.3333333333%);stroke-width:1px;}#mermaid-svg-L9higWB7Ds6ItxVV .rough-node .label text,#mermaid-svg-L9higWB7Ds6ItxVV .node .label text,#mermaid-svg-L9higWB7Ds6ItxVV .image-shape .label,#mermaid-svg-L9higWB7Ds6ItxVV .icon-shape .label{text-anchor:middle;}#mermaid-svg-L9higWB7Ds6ItxVV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-L9higWB7Ds6ItxVV .rough-node .label,#mermaid-svg-L9higWB7Ds6ItxVV .node .label,#mermaid-svg-L9higWB7Ds6ItxVV .image-shape .label,#mermaid-svg-L9higWB7Ds6ItxVV .icon-shape .label{text-align:center;}#mermaid-svg-L9higWB7Ds6ItxVV .node.clickable{cursor:pointer;}#mermaid-svg-L9higWB7Ds6ItxVV .root .anchor path{fill:#0b0b0b!important;stroke-width:0;stroke:#0b0b0b;}#mermaid-svg-L9higWB7Ds6ItxVV .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-L9higWB7Ds6ItxVV .edgePath .path{stroke:#0b0b0b;stroke-width:2.0px;}#mermaid-svg-L9higWB7Ds6ItxVV .flowchart-link{stroke:#0b0b0b;fill:none;}#mermaid-svg-L9higWB7Ds6ItxVV .edgeLabel{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-L9higWB7Ds6ItxVV .edgeLabel p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-L9higWB7Ds6ItxVV .edgeLabel rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-L9higWB7Ds6ItxVV .labelBkg{background-color:rgba(243.9999999999, 220.9999999998, 255, 0.5);}#mermaid-svg-L9higWB7Ds6ItxVV .cluster rect{fill:hsl(220.5882352941, 100%, 98.3333333333%);stroke:hsl(220.5882352941, 60%, 88.3333333333%);stroke-width:1px;}#mermaid-svg-L9higWB7Ds6ItxVV .cluster text{fill:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-L9higWB7Ds6ItxVV .cluster span{color:rgb(8.5000000002, 5.7500000001, 0);}#mermaid-svg-L9higWB7Ds6ItxVV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:Microsoft YaHei;font-size:12px;background:hsl(220.5882352941, 100%, 98.3333333333%);border:1px solid hsl(220.5882352941, 60%, 88.3333333333%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-L9higWB7Ds6ItxVV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-L9higWB7Ds6ItxVV rect.text{fill:none;stroke-width:0;}#mermaid-svg-L9higWB7Ds6ItxVV .icon-shape,#mermaid-svg-L9higWB7Ds6ItxVV .image-shape{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);text-align:center;}#mermaid-svg-L9higWB7Ds6ItxVV .icon-shape p,#mermaid-svg-L9higWB7Ds6ItxVV .image-shape p{background-color:hsl(-79.4117647059, 100%, 93.3333333333%);padding:2px;}#mermaid-svg-L9higWB7Ds6ItxVV .icon-shape .label rect,#mermaid-svg-L9higWB7Ds6ItxVV .image-shape .label rect{opacity:0.5;background-color:hsl(-79.4117647059, 100%, 93.3333333333%);fill:hsl(-79.4117647059, 100%, 93.3333333333%);}#mermaid-svg-L9higWB7Ds6ItxVV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-L9higWB7Ds6ItxVV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-L9higWB7Ds6ItxVV :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
持久化与高可用核心结论。
持久化
生产用 RDB + AOF 混合
appendfsync everysec
主从
全量 + 增量同步
控制大 key 防延迟。
哨兵
3 哨兵自动故障转移
中小规模首选。
Cluster
16384 slot 分片
min-replicas 防脑裂。
📌 配套阅读: 上一篇:《04-01-A-Redis数据结构与底层实现详解.md》
下一篇:《04-03-A-Redis缓存架构设计与治理.md》
如果这篇文章对你有帮助,欢迎点赞、收藏、关注!🚀



