Skip to content
清晨的一缕阳光
返回

Redis Cluster 实战部署与运维

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 硬件要求

配置推荐值说明
CPU4 核 +主频 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 从,跨机房
配置合理超时,持久化
扩容先加节点,再分片
故障自动转移,手动修复
监控集群状态,节点健康

最佳实践

  1. 至少 3 主 3 从部署
  2. 配置合理的超时时间
  3. 定期备份和恢复演练
  4. 监控集群状态和性能
  5. 制定故障处理预案

掌握 Cluster 实战,构建高可用 Redis 架构!

参考资料


分享这篇文章到:

上一篇文章
Kafka 安全加固与权限管理实战
下一篇文章
Redis 缓存一致性方案