Redis Cluster 实战部署与运维
Redis Cluster 是 Redis 的分布式解决方案。本文将深入 Cluster 的部署、配置、扩容、故障处理等实战经验。
一、集群规划
1.1 节点规划
生产环境推荐(6 节点,3 主 3 从):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Master 1 │ │ Master 2 │ │ Master 3 │
│ 192.168.1.1 │ │ 192.168.1.2 │ │ 192.168.1.3 │
│ port 7000 │ │ port 7001 │ │ port 7002 │
│ 0-5460 │ │ 5461-10922 │ │ 10923-16383 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐
│ Slave 1 │ │ Slave 2 │ │ Slave 3 │
│ 192.168.1.4 │ │ 192.168.1.5 │ │ 192.168.1.6 │
│ port 7003 │ │ port 7004 │ │ port 7005 │
└─────────────┘ └─────────────┘ └─────────────┘
1.2 硬件要求
| 配置 | 推荐值 | 说明 |
|---|---|---|
| CPU | 4 核 + | 主频 2.5GHz+ |
| 内存 | 8GB+ | 根据数据量调整 |
| 磁盘 | SSD | 持久化需要 |
| 网络 | 千兆 | 内网通信 |
二、部署配置
2.1 节点配置
# /etc/redis/7000.conf
# 基础配置
port 7000
bind 0.0.0.0
daemonize yes
pidfile /var/run/redis/redis-7000.pid
logfile /var/log/redis/redis-7000.log
# 集群配置
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
# 持久化
appendonly yes
appendfilename "appendonly-7000.aof"
appendfsync everysec
# 内存配置
maxmemory 4gb
maxmemory-policy allkeys-lru
# 安全配置
requirepass your_password
masterauth your_password
2.2 启动脚本
#!/bin/bash
# start_cluster.sh
REDIS_BIN="/usr/local/bin/redis-server"
REDIS_CLI="/usr/local/bin/redis-cli"
CONFIG_DIR="/etc/redis"
# 启动主节点
for port in 7000 7001 7002; do
$REDIS_BIN $CONFIG_DIR/$port.conf
echo "Started Redis on port $port"
done
# 启动从节点
for port in 7003 7004 7005; do
$REDIS_BIN $CONFIG_DIR/$port.conf
echo "Started Redis on port $port"
done
# 等待启动完成
sleep 5
# 创建集群
$REDIS_CLI --cluster create \
192.168.1.1:7000 192.168.1.2:7001 192.168.1.3:7002 \
192.168.1.4:7003 192.168.1.5:7004 192.168.1.6:7005 \
--cluster-replicas 1 \
--cluster-yes
echo "Cluster created successfully"
三、Java 客户端配置
3.1 Jedis Cluster
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import java.util.HashSet;
import java.util.Set;
public class RedisClusterExample {
private static JedisCluster cluster;
static {
// 集群节点
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.1.1", 7000));
nodes.add(new HostAndPort("192.168.1.2", 7001));
nodes.add(new HostAndPort("192.168.1.3", 7002));
// 连接池配置
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(20);
poolConfig.setMinIdle(5);
// 创建集群连接
cluster = new JedisCluster(nodes, poolConfig);
}
public static void main(String[] args) {
// 自动路由到正确节点
cluster.set("user:1001:name", "Alice");
String name = cluster.get("user:1001:name");
System.out.println("Name: " + name);
// 关闭连接
// cluster.close();
}
}
3.2 Lettuce Cluster
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
import java.util.Arrays;
public class LettuceClusterExample {
public static void main(String[] args) {
// 集群 URI
RedisURI uri1 = RedisURI.create("redis://192.168.1.1:7000");
RedisURI uri2 = RedisURI.create("redis://192.168.1.2:7001");
RedisURI uri3 = RedisURI.create("redis://192.168.1.3:7002");
// 创建集群客户端
RedisClusterClient clusterClient = RedisClusterClient.create(
Arrays.asList(uri1, uri2, uri3)
);
// 获取连接
StatefulRedisClusterConnection<String, String> connection =
clusterClient.connect();
// 同步命令
RedisClusterCommands<String, String> commands = connection.sync();
commands.set("user:1001:name", "Alice");
String name = commands.get("user:1001:name");
System.out.println("Name: " + name);
// 关闭连接
connection.close();
clusterClient.shutdown();
}
}
四、Golang 客户端配置
4.1 go-redis Cluster
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
func main() {
ctx := context.Background()
// 创建集群客户端
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{
"192.168.1.1:7000",
"192.168.1.2:7001",
"192.168.1.3:7002",
},
Password: "your_password",
})
// 自动路由
err := rdb.Set(ctx, "user:1001:name", "Alice", 0).Err()
if err != nil {
panic(err)
}
name, err := rdb.Get(ctx, "user:1001:name").Result()
if err != nil {
panic(err)
}
fmt.Println("Name:", name)
// 关闭连接
rdb.Close()
}
4.2 集群监控
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
)
type ClusterMonitor struct {
rdb *redis.ClusterClient
}
func NewClusterMonitor(rdb *redis.ClusterClient) *ClusterMonitor {
return &ClusterMonitor{rdb: rdb}
}
// 获取集群状态
func (cm *ClusterMonitor) GetClusterInfo() {
ctx := context.Background()
// 集群信息
info, _ := cm.rdb.ClusterInfo(ctx).Result()
fmt.Println("Cluster Info:", info)
// 节点状态
nodes, _ := cm.rdb.ClusterNodes(ctx).Result()
fmt.Println("Cluster Nodes:", nodes)
// 槽位状态
slots, _ := cm.rdb.ClusterSlots(ctx).Result()
for _, slot := range slots {
fmt.Printf("Slot %d-%d: %v\n", slot.Start, slot.End, slot.Nodes)
}
}
// 检查节点健康
func (cm *ClusterMonitor) CheckNodeHealth() {
ctx := context.Background()
cm.rdb.ForEachMaster(ctx, func(ctx context.Context, master *redis.Client) error {
info, _ := master.Ping(ctx).Result()
fmt.Printf("Master %s: %s\n", master.String(), info)
return nil
})
}
func main() {
rdb := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{
"192.168.1.1:7000",
"192.168.1.2:7001",
"192.168.1.3:7002",
},
})
monitor := NewClusterMonitor(rdb)
monitor.GetClusterInfo()
monitor.CheckNodeHealth()
}
五、扩容缩容
5.1 添加主节点
# 1. 启动新节点
redis-server /etc/redis/7006.conf
# 2. 添加到集群
redis-cli --cluster add-node 192.168.1.7:7006 192.168.1.1:7000
# 3. 重新分片
redis-cli --cluster reshard 192.168.1.1:7000
# 交互提示:
# How many slots do you want to move? 4096
# What is the receiving node ID? <new_node_id>
# Source node 1: all
# Source node 2: done
5.2 添加从节点
# 1. 启动新节点
redis-server /etc/redis/7007.conf
# 2. 添加为从节点
redis-cli --cluster add-node \
192.168.1.8:7007 \
192.168.1.1:7000 \
--cluster-slave \
--cluster-master-id <master_node_id>
5.3 删除节点
# 1. 迁移槽位
redis-cli --cluster reshard 192.168.1.1:7000
# 2. 检查节点是否为空
redis-cli --cluster check 192.168.1.1:7000
# 3. 删除节点
redis-cli --cluster del-node \
192.168.1.1:7000 \
<node_id_to_remove>
六、故障处理
6.1 节点故障
# 1. 检查集群状态
redis-cli --cluster check 192.168.1.1:7000
# 2. 查看故障节点
redis-cli -c -p 7000 CLUSTER NODES
# 3. 修复故障
redis-cli --cluster fix 192.168.1.1:7000
# 4. 如果需要,手动故障转移
redis-cli -c -p 7003 CLUSTER FAILOVER
6.2 槽位迁移中断
# 1. 查看迁移状态
redis-cli -c -p 7000 CLUSTER NODES
# 2. 继续迁移
redis-cli --cluster reshard 192.168.1.1:7000
# 3. 如果迁移卡住,重置槽位
redis-cli -c -p 7000 CLUSTER SETSLOT <slot> STABLE
6.3 脑裂问题
# 1. 检查集群状态
redis-cli CLUSTER INFO
# 2. 如果 cluster_state=fail,需要人工介入
# 3. 选择多数派分区
# 4. 重置少数派节点
redis-cli CLUSTER RESET
# 5. 重新加入集群
redis-cli --cluster add-node ...
七、性能优化
7.1 配置优化
# 集群配置优化
cluster-node-timeout 15000 # 节点超时(15 秒)
cluster-replica-validity-factor 10 # 从节点有效性因子
cluster-migration-barrier 1 # 迁移屏障
cluster-require-full-coverage yes # 要求全槽位覆盖
7.2 网络优化
# 1. 使用内网通信
# 2. 增加网络带宽
# 3. 调整 TCP 参数
sysctl -w net.ipv4.tcp_keepalive_time=60
sysctl -w net.ipv4.tcp_keepalive_intvl=10
sysctl -w net.ipv4.tcp_keepalive_probes=3
7.3 内存优化
# 内存配置
maxmemory 4gb
maxmemory-policy allkeys-lru
# 禁用持久化(纯缓存场景)
save ""
appendonly no
八、监控告警
8.1 关键指标
# Prometheus 监控指标
# 集群状态
redis_cluster_state # ok/fail
redis_cluster_slots_assigned # 已分配槽位
redis_cluster_slots_ok # 正常槽位
redis_cluster_slots_pfail # 疑似故障槽位
redis_cluster_slots_fail # 故障槽位
redis_cluster_known_nodes # 已知节点数
redis_cluster_size # 集群大小
8.2 Grafana 告警
# 告警规则
- alert: RedisClusterStateFail
expr: redis_cluster_state{state="fail"} == 1
for: 1m
labels:
severity: critical
annotations:
summary: "Redis Cluster state is fail"
- alert: RedisClusterSlotsFail
expr: redis_cluster_slots_fail > 0
for: 5m
labels:
severity: warning
annotations:
summary: "Redis Cluster has failed slots"
- alert: RedisClusterNodeDown
expr: redis_cluster_known_nodes < 6
for: 5m
labels:
severity: warning
annotations:
summary: "Redis Cluster node is down"
九、备份恢复
9.1 备份策略
#!/bin/bash
# backup_cluster.sh
BACKUP_DIR="/backup/redis"
DATE=$(date +%Y%m%d_%H%M%S)
REDIS_CLI="redis-cli"
# 备份每个节点
for port in 7000 7001 7002 7003 7004 7005; do
# 触发 BGSAVE
$REDIS_CLI -p $port BGSAVE
# 等待完成
while [ "$($REDIS_CLI -p $port LASTSAVE)" == "$LAST_SAVE" ]; do
sleep 1
done
# 复制 RDB 文件
cp /var/lib/redis/dump-$port.rdb $BACKUP_DIR/dump-$port-$DATE.rdb
# 压缩
gzip $BACKUP_DIR/dump-$port-$DATE.rdb
done
# 清理 7 天前备份
find $BACKUP_DIR -name "*.rdb.gz" -mtime +7 -delete
9.2 恢复流程
# 1. 停止集群
for port in 7000 7001 7002 7003 7004 7005; do
redis-cli -p $port SHUTDOWN
done
# 2. 恢复 RDB 文件
for port in 7000 7001 7002 7003 7004 7005; do
cp /backup/redis/dump-$port-20240101.rdb.gz /var/lib/redis/
gunzip /var/lib/redis/dump-$port-20240101.rdb.gz
mv /var/lib/redis/dump-$port-20240101.rdb /var/lib/redis/dump-$port.rdb
done
# 3. 启动集群
for port in 7000 7001 7002 7003 7004 7005; do
redis-server /etc/redis/$port.conf
done
# 4. 检查集群状态
redis-cli --cluster check 192.168.1.1:7000
十、最佳实践
10.1 部署建议
✅ 推荐:
- 至少 3 主 3 从
- 节点部署在不同机器
- 跨机房部署
- 使用内网通信
- 配置合理的超时时间
❌ 避免:
- 单点部署
- 主从同机
- 超时时间过短
- 全槽位覆盖要求过严
10.2 运维检查清单
每日检查:
- [ ] 集群状态
- [ ] 节点健康
- [ ] 内存使用
- [ ] 慢查询
每周检查:
- [ ] 备份验证
- [ ] 性能指标
- [ ] 日志分析
每月检查:
- [ ] 故障演练
- [ ] 容量规划
- [ ] 配置优化
10.3 故障演练
# 1. 模拟主节点故障
redis-cli -p 7000 DEBUG SLEEP 10
# 2. 观察故障转移
watch -n 1 "redis-cli -p 7001 CLUSTER NODES"
# 3. 验证数据完整性
redis-cli -c -p 7001 GET user:1001:name
# 4. 恢复节点
redis-cli -p 7000 SHUTDOWN
redis-server /etc/redis/7000.conf
总结
Redis Cluster 实战要点:
| 方面 | 关键点 |
|---|---|
| 部署 | 3 主 3 从,跨机房 |
| 配置 | 合理超时,持久化 |
| 扩容 | 先加节点,再分片 |
| 故障 | 自动转移,手动修复 |
| 监控 | 集群状态,节点健康 |
最佳实践:
- 至少 3 主 3 从部署
- 配置合理的超时时间
- 定期备份和恢复演练
- 监控集群状态和性能
- 制定故障处理预案
掌握 Cluster 实战,构建高可用 Redis 架构!