主要围绕Redis高可用保障项目运行, 需要达到如下几点:
- Redis 集群模式:使用 Redis Cluster(至少 3 主 3 从),某个主节点挂了,从节点自动提升为主,秒级切换
- 哨兵模式:Redis Sentinel 监控节点状态,自动故障转移
- 数据持久化:RDB + AOF 双持久化,Redis 重启后数据可恢复
一、 集群架构原理
Redis Cluster 采用无中心 P2P 架构 ,将数据空间划分为 16384 个哈希槽(Hash Slot) ,通过
CRC16(key) % 16384计算每个 key 所属的槽位,再分配到不同主节点
- 3 主 3 从:每个主节点负责一部分连续槽位,从节点同步主节点数据并在主节点故障时自动选举晋升
- Gossip 协议:节点间通过集群总线(数据端口 +10000)进行心跳检测与状态同步,故障判定需半数以上主节点投票确认
- 故障转移:主节点宕机后,其从节点发起选举,获得多数主节点投票后晋升为新主,整个过程约 10-20 秒
二、 安装与节点配置
1. 环境准备
以 3 主 3 从(6 节点)为例,假设部署在三台服务器上,每台运行 2 个实例(1 主 + 1 从):
| 节点 | IP | 端口 | 角色 |
|---|---|---|---|
| node1 | 192.168.1.101 | 7000 | 主1 |
| node1 | 192.168.1.101 | 7003 | 从1(主1的从) |
| node2 | 192.168.1.102 | 7001 | 主2 |
| node2 | 192.168.1.102 | 7004 | 从2(主2的从) |
| node3 | 192.168.1.103 | 7002 | 主3 |
| node3 | 192.168.1.103 | 7005 | 从3(主3的从) |
2. 核心配置参数
每个节点创建独立的
redis.conf,关键配置如下:
bash
# 节点端口(每个节点不同)
port 7000
# 开启集群模式(必须)
cluster-enabled yes
# 集群节点配置文件(Redis 自动维护,无需手动编辑)
cluster-config-file nodes-7000.conf
# 节点超时时间(毫秒),生产环境建议 5000-15000
cluster-node-timeout 15000
# 开启 AOF 持久化
appendonly yes
# 后台运行
daemonize yes
# 绑定 IP(生产环境建议绑定内网 IP)
bind 0.0.0.0
# 关闭保护模式(允许远程连接)
protected-mode no
# 访问密码(所有节点必须一致)
requirepass your_password
# 集群间通信密码(与 requirepass 保持一致)
masterauth your_password
Docker 部署额外配置 :必须显式声明对外通信地址,否则节点间无法互通
bashcluster-announce-ip 192.168.1.101 cluster-announce-port 7000 cluster-announce-bus-port 17000
3. 端口开放
每个节点需开放两个 TCP 端口:
- 数据端口:如 7000-7005(客户端连接)
- 集群总线端口:数据端口 +10000,如 17000-17005(Gossip 通信)
bash
# 防火墙放行示例
firewall-cmd --permanent --add-port=7000-7005/tcp
firewall-cmd --permanent --add-port=17000-17005/tcp
firewall-cmd --reload
4. 启动所有节点
bash
redis-server /etc/redis/cluster/7000/redis.conf
redis-server /etc/redis/cluster/7001/redis.conf
redis-server /etc/redis/cluster/7002/redis.conf
redis-server /etc/redis/cluster/7003/redis.conf
redis-server /etc/redis/cluster/7004/redis.conf
redis-server /etc/redis/cluster/7005/redis.conf
三、 创建集群
Redis 5.0+ 使用
redis-cli --cluster create命令,只需在任意一台机器执行一次:
bash
redis-cli --cluster create \
192.168.1.101:7000 192.168.1.101:7003 \
192.168.1.102:7001 192.168.1.102:7004 \
192.168.1.103:7002 192.168.1.103:7005 \
--cluster-replicas 1 \
-a your_password
--cluster-replicas 1:每个主节点分配 1 个从节点- 执行后会提示槽位分配方案,输入
yes确认创建成功后会显示类似信息:
bash
[OK] All 16384 slots covered.
四、 验证集群状态
bash
# 连接集群(-c 启用集群模式,支持自动重定向)
redis-cli -c -h 192.168.1.101 -p 7000 -a your_password
# 查看集群整体状态
cluster info
# 关键指标:cluster_state:ok,cluster_slots_assigned:16384
# 查看节点拓扑(主从关系、槽位分布)
cluster nodes
# 健康检查
redis-cli --cluster check 192.168.1.101:7000 -a your_password
读写测试:
bash
# 在任意节点写入
set name redis
# 如果该 key 的槽不属于当前节点,会返回 MOVED 重定向
# 查看 key 所属槽位
cluster keyslot name
# 在从节点读取(验证主从同步)
get name
五、 客户端与服务交互流程
1. 客户端连接原理
客户端连接集群时,交互流程如下:
客户端 ──① 连接任意节点(主/从均可)
│
▼
节点 ──② 返回集群拓扑(各节点负责的槽位范围)
│
▼
客户端 ──③ 计算 key 的哈希槽 CRC16(key) % 16384
│
▼
┌─────┴─────┐
│ 槽在本节点? │
└─────┬─────┘
YES │ NO
│ │
▼ ▼
直接处理 返回 MOVED 重定向
(客户端更新本地槽映射缓存并重试)
2. 编程语言客户端示例
以 Go 为例(使用
redis/go-redis):
Go
import "github.com/redis/go-redis/v9"
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{
"192.168.1.101:7000",
"192.168.1.101:7003",
"192.168.1.102:7001",
"192.168.1.102:7004",
"192.168.1.103:7002",
"192.168.1.103:7005",
},
Password: "your_password",
})
// 正常使用,客户端自动处理槽位路由
rdb.Set(ctx, "name", "redis", 0)
val, _ := rdb.Get(ctx, "name").Result()
3. 重定向机制
- MOVED:槽位已永久迁移到另一节点,客户端更新本地缓存后重试
- ASK :槽位正在迁移中(resharding),客户端需先发送
ASKING命令再重试
六、 常见运维操作
扩容(添加新节点)
bash
# 1. 添加新主节点
redis-cli --cluster add-node 192.168.1.104:7006 192.168.1.101:7000 -a your_password
# 2. 重新均衡槽位(reshard)
redis-cli --cluster reshard 192.168.1.101:7000 -a your_password
# 3. 添加新从节点(绑定到指定主节点)
redis-cli --cluster add-node 192.168.1.104:7007 192.168.1.101:7000 \
--cluster-slave --cluster-master-id <master-node-id> -a your_password
缩容(删除节点)
bash
# 1. 先迁移该主节点的所有槽位到其他节点
redis-cli --cluster reshard 192.168.1.101:7000 -a your_password
# 2. 删除空槽节点
redis-cli --cluster del-node 192.168.1.101:7000 <node-id> -a your_password
故障转移模拟
Go
# 手动触发从节点晋升为主节点
redis-cli -h 192.168.1.101 -p 7003 -a your_password cluster failover
七、 关键注意事项
| 注意点 | 说明 |
|---|---|
| 多数据库不支持 | 集群模式下只能使用 db0,SELECT 命令不可用 |
| 批量操作限制 | MSET/MGET 的 key 必须在同一哈希槽,可用 Hash Tags {user1000}.name 强制同槽 |
| 主从异步复制 | 存在短暂数据丢失风险,与哨兵模式相同 |
| 槽位必须全覆盖 | 默认 cluster-require-full-coverage yes,有槽未分配时集群拒绝写入 |
| 清理旧数据 | 创建集群前需确保节点为空,否则需删除 dump.rdb、appendonly.aof、nodes.conf |
| 监控建议 | 监控 cluster_info、cluster_nodes、节点内存/CPU/网络等指标 |
八、 整体流程总结
Go
┌─────────────────────────────────────────────────────┐
│ 1. 准备 6 个 Redis 实例(3主3从),各自独立配置 │
│ 2. 启动所有节点 │
│ 3. 执行 redis-cli --cluster create 创建集群 │
│ 4. 验证 cluster_state:ok 且 16384 槽位全覆盖 │
│ 5. 客户端使用 ClusterClient 连接,自动处理槽位路由 │
│ 6. 主节点故障 → 从节点自动选举晋升 → 槽位重新映射 │
└─────────────────────────────────────────────────────┘
九、Docker Compose 部署
1. 目录结构
bash
redis-cluster/
├── docker-compose.yml
├── redis.conf # 配置模板
└── data/
├── node-7001/
├── node-7002/
├── node-7003/
├── node-7004/
├── node-7005/
└── node-7006/
2. 配置文件
(1). redis.conf(配置模板)
bash
# 端口(每个节点不同,启动时动态替换)
port ${PORT}
# 开启集群模式
cluster-enabled yes
# 集群元数据文件(Redis 自动维护)
cluster-config-file nodes.conf
# 节点超时时间(毫秒)
cluster-node-timeout 5000
# 开启 AOF 持久化
appendonly yes
# 关闭保护模式(集群环境必须)
protected-mode no
# 访问密码(所有节点必须一致)
requirepass your_password
masterauth your_password
# 声明对外地址(关键!容器部署必须配置)
cluster-announce-ip ${HOST_IP}
cluster-announce-port ${PORT}
cluster-announce-bus-port ${BUS_PORT}
cluster-announce-*是容器部署的核心:Gossip 协议会交换节点 IP,如果不显式声明,节点上报的是容器内网 IP,外部客户端无法连接
(2). docker-compose.yml
bash
version: '3.8'
services:
redis-7001:
image: redis:7.4-alpine
container_name: redis-7001
restart: always
ports:
- "7001:7001"
- "17001:17001"
volumes:
- ./data/node-7001:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: >
redis-server /usr/local/etc/redis/redis.conf
--port 7001
--cluster-announce-ip ${HOST_IP}
--cluster-announce-port 7001
--cluster-announce-bus-port 17001
networks:
- redis-cluster
redis-7002:
image: redis:7.4-alpine
container_name: redis-7002
restart: always
ports:
- "7002:7002"
- "17002:17002"
volumes:
- ./data/node-7002:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: >
redis-server /usr/local/etc/redis/redis.conf
--port 7002
--cluster-announce-ip ${HOST_IP}
--cluster-announce-port 7002
--cluster-announce-bus-port 17002
networks:
- redis-cluster
redis-7003:
image: redis:7.4-alpine
container_name: redis-7003
restart: always
ports:
- "7003:7003"
- "17003:17003"
volumes:
- ./data/node-7003:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: >
redis-server /usr/local/etc/redis/redis.conf
--port 7003
--cluster-announce-ip ${HOST_IP}
--cluster-announce-port 7003
--cluster-announce-bus-port 17003
networks:
- redis-cluster
redis-7004:
image: redis:7.4-alpine
container_name: redis-7004
restart: always
ports:
- "7004:7004"
- "17004:17004"
volumes:
- ./data/node-7004:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: >
redis-server /usr/local/etc/redis/redis.conf
--port 7004
--cluster-announce-ip ${HOST_IP}
--cluster-announce-port 7004
--cluster-announce-bus-port 17004
networks:
- redis-cluster
redis-7005:
image: redis:7.4-alpine
container_name: redis-7005
restart: always
ports:
- "7005:7005"
- "17005:17005"
volumes:
- ./data/node-7005:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: >
redis-server /usr/local/etc/redis/redis.conf
--port 7005
--cluster-announce-ip ${HOST_IP}
--cluster-announce-port 7005
--cluster-announce-bus-port 17005
networks:
- redis-cluster
redis-7006:
image: redis:7.4-alpine
container_name: redis-7006
restart: always
ports:
- "7006:7006"
- "17006:17006"
volumes:
- ./data/node-7006:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf
command: >
redis-server /usr/local/etc/redis/redis.conf
--port 7006
--cluster-announce-ip ${HOST_IP}
--cluster-announce-port 7006
--cluster-announce-bus-port 17006
networks:
- redis-cluster
# 初始化服务:所有节点启动后自动创建集群
redis-init:
image: redis:7.4-alpine
container_name: redis-init
depends_on:
- redis-7001
- redis-7002
- redis-7003
- redis-7004
- redis-7005
- redis-7006
command: >
sh -c "
sleep 10 &&
redis-cli -a your_password --cluster create
redis-7001:7001
redis-7002:7002
redis-7003:7003
redis-7004:7004
redis-7005:7005
redis-7006:7006
--cluster-replicas 1
--cluster-yes
"
networks:
- redis-cluster
networks:
redis-cluster:
driver: bridge
redis-init服务 :利用depends_on确保所有节点启动后再执行集群初始化,真正实现"一键部署"
3. 一键启动
(1). 创建目录
bash
mkdir -p redis-cluster/data/node-{7001,7002,7003,7004,7005,7006}
cd redis-cluster
(2). 设置宿主机 IP 并启动
bash
# 获取宿主机 IP(Linux)
export HOST_IP=$(hostname -I | awk '{print $1}')
# 或者手动指定
export HOST_IP=192.168.1.100
# 一键启动(8 个容器:6 节点 + 1 初始化)
docker compose up -d
(3). 查看启动日志
# 查看初始化进度
docker logs -f redis-init
# 看到以下输出表示集群创建成功
# [OK] All 16384 slots covered.
4. 验证集群状态
bash
# 连接任意节点查看集群信息
docker exec -it redis-7001 redis-cli -a your_password -c
# 查看集群状态
127.0.0.1:7001> CLUSTER INFO
# 期望输出:
# cluster_state:ok
# cluster_slots_assigned:16384
# cluster_slots_ok:16384
# cluster_known_nodes:6
# cluster_size:3
# 查看节点拓扑(主从关系、槽位分布)
127.0.0.1:7001> CLUSTER NODES
# 读写测试(-c 启用集群模式,自动处理 MOVED 重定向)
127.0.0.1:7001> SET user:1001 "Alice"
# -> Redirected to slot [5798] located at 172.x.x.x:7002
# OK
127.0.0.1:7002> GET user:1001
# "Alice"
5. 故障转移测试
bash
# 1. 查看当前主节点
docker exec -it redis-7001 redis-cli -a your_password CLUSTER NODES | grep master
# 2. 杀掉一个主节点(如 redis-7002)
docker rm -f redis-7002
# 3. 等待 10 秒,从节点自动晋升
sleep 10
# 4. 查看集群状态,确认新主节点已接替
docker exec -it redis-7001 redis-cli -a your_password CLUSTER NODES | grep master
# 5. 数据仍然可读写
docker exec -it redis-7001 redis-cli -a your_password -c GET user:1001
6. 停止与清理
bash
# 停止所有容器
docker compose down
# 彻底清理(包括数据卷)
docker compose down -v
rm -rf data/
7. 关键注意事项
| 注意点 | 说明 |
|---|---|
| 端口映射 | 每个节点需映射两个端口:数据端口(7001-7006)+ 总线端口(17001-17006) |
| cluster-announce- | 容器部署必须显式声明宿主机 IP,否则外部客户端无法连接 |
| 密码一致性 | requirepass 和 masterauth 必须所有节点一致 |
| 数据持久化 | 每个节点独立挂载 /data 目录,避免 AOF/RDB 互相覆盖 |
| 槽位全覆盖 | 初始化成功后 cluster_slots_assigned 必须等于 16384,否则集群不可用 |
| 客户端连接 | 应用代码需使用 Cluster 模式客户端(如 Go 的 redis.ClusterClient),并配置多个 seed 节点 |
9. Go 客户端连接示例
Go
import "github.com/redis/go-redis/v9"
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{
"192.168.1.100:7001",
"192.168.1.100:7002",
"192.168.1.100:7003",
},
Password: "your_password",
})
// 正常使用,客户端自动处理槽位路由和重定向
rdb.Set(ctx, "user:1001", "Alice", 0)
val, _ := rdb.Get(ctx, "user:1001").Result()