使用Docker搭建Redis分片集群

使用Docker和Docker Compose搭建Redis 3主3从分片集群及其扩缩容实践

1. 简介

Redis分片集群概念

Redis Cluster(Redis集群)是Redis官方提供的分布式解决方案,它能够在多个Redis节点之间自动分配数据,提供高可用性和横向扩展能力。分片集群通过一致性哈希算法将16384个哈希槽分配给不同的主节点,每个主节点负责一部分数据的存储和处理。

分片集群的优势

  1. 高可用性:当某个主节点故障时,其从节点可以自动提升为主节点,保证服务不中断
  2. 数据分片:数据自动分布在多个节点上,突破单机内存限制
  3. 读写分离:从节点可以分担读请求,提高系统并发能力
  4. 弹性扩容:可以动态添加或删除节点,实现集群的弹性伸缩
  5. 故障自愈:集群具备自动故障检测和恢复能力

为什么选择Docker容器化部署

  1. 环境一致性:容器化确保开发、测试、生产环境的一致性
  2. 快速部署:通过Docker Compose可以一键启动整个集群
  3. 资源隔离:每个Redis实例运行在独立的容器中,避免资源冲突
  4. 易于管理:容器化使得集群的启动、停止、监控更加便捷
  5. 可移植性:可以在任何支持Docker的环境中快速部署

2. 环境准备

环境要求

  • 操作系统:Windows 10/11 Pro、Linux、macOS
  • Docker版本:≥ 20.10.0
  • Docker Compose版本:≥ 2.0.0
  • 内存要求:至少4GB可用内存
  • 磁盘空间:至少2GB可用磁盘空间

验证环境

执行以下命令验证环境:

# 检查Docker版本
docker --version

# 检查Docker Compose版本
docker-compose --version

# 检查Docker服务状态
docker info

网络配置建议

为了确保Redis集群在Docker环境中的稳定运行,建议进行以下网络配置:

# 检查Docker网络配置
docker network ls

# 创建自定义网络(如果需要)
docker network create --driver bridge --subnet=172.20.0.0/16 redis-cluster-network

网络规划

  • 子网范围:172.20.0.0/16
  • 节点IP分配:172.20.0.2 - 172.20.0.7
  • 预留扩容IP:172.20.0.8 - 172.20.0.20

3. 集群架构设计

3主3从集群架构

我们将构建一个包含6个Redis实例的集群:

graph TB subgraph "Redis Cluster (3主3从)" subgraph "主节点层" M1["🔴 Master 1<br/>端口: 7001<br/>IP: 172.20.0.2<br/>哈希槽: 0-5460"] M2["🔴 Master 2<br/>端口: 7002<br/>IP: 172.20.0.3<br/>哈希槽: 5461-10922"] M3["🔴 Master 3<br/>端口: 7003<br/>IP: 172.20.0.4<br/>哈希槽: 10923-16383"] end subgraph "从节点层" S1["🔵 Slave 1<br/>端口: 7004<br/>IP: 172.20.0.5<br/>主节点: Master 1"] S2["🔵 Slave 2<br/>端口: 7005<br/>IP: 172.20.0.6<br/>主节点: Master 2"] S3["🔵 Slave 3<br/>端口: 7006<br/>IP: 172.20.0.7<br/>主节点: Master 3"] end M1 -->|数据复制| S1 M2 -->|数据复制| S2 M3 -->|数据复制| S3 M1 -.->|集群通信| M2 M2 -.->|集群通信| M3 M3 -.->|集群通信| M1 end subgraph "客户端访问" Client["客户端应用"] end Client -->|读写请求| M1 Client -->|读写请求| M2 Client -->|读写请求| M3 Client -->|读请求| S1 Client -->|读请求| S2 Client -->|读请求| S3 style M1 fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style M2 fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style M3 fill:#ff6b6b,stroke:#c92a2a,stroke-width:2px,color:#fff style S1 fill:#4ecdc4,stroke:#26a69a,stroke-width:2px,color:#fff style S2 fill:#4ecdc4,stroke:#26a69a,stroke-width:2px,color:#fff style S3 fill:#4ecdc4,stroke:#26a69a,stroke-width:2px,color:#fff style Client fill:#45b7d1,stroke:#2196f3,stroke-width:2px,color:#fff

集群网络拓扑

graph LR subgraph "Docker Network: 172.20.0.0/16" subgraph "Redis Cluster Nodes" N1["redis-node-1<br/>172.20.0.2:7001<br/>Master"] N2["redis-node-2<br/>172.20.0.3:7002<br/>Master"] N3["redis-node-3<br/>172.20.0.4:7003<br/>Master"] N4["redis-node-4<br/>172.20.0.5:7004<br/>Slave of N1"] N5["redis-node-5<br/>172.20.0.6:7005<br/>Slave of N2"] N6["redis-node-6<br/>172.20.0.7:7006<br/>Slave of N3"] end subgraph "Host Ports" H1["Host:7001"] H2["Host:7002"] H3["Host:7003"] H4["Host:7004"] H5["Host:7005"] H6["Host:7006"] end end H1 -.->|端口映射| N1 H2 -.->|端口映射| N2 H3 -.->|端口映射| N3 H4 -.->|端口映射| N4 H5 -.->|端口映射| N5 H6 -.->|端口映射| N6 N1 <-->|集群总线<br/>17001| N2 N2 <-->|集群总线<br/>17002| N3 N3 <-->|集群总线<br/>17003| N1 N1 <-->|主从复制| N4 N2 <-->|主从复制| N5 N3 <-->|主从复制| N6

架构特点

  • 主节点:负责处理读写请求,存储数据分片
  • 从节点:主节点的副本,提供数据备份和读请求处理
  • 哈希槽分配:16384个槽位平均分配给3个主节点
  • 自动故障转移:主节点故障时,对应从节点自动提升为主节点

分片策略和数据分布原理

Redis Cluster使用一致性哈希算法进行数据分片:

哈希槽分布图

pie title 哈希槽分配 (总计16384个槽) "Master 1 (0-5460)" : 5461 "Master 2 (5461-10922)" : 5462 "Master 3 (10923-16383)" : 5461

数据路由流程

flowchart TD A[客户端发送请求<br/>SET key value] --> B{计算key的哈希值} B --> C[CRC16算法计算哈希] C --> D[哈希值 % 16384<br/>确定槽位] D --> E{判断槽位归属} E -->|槽位 0-5460| F[转发到 Master 1<br/>172.20.0.2:7001] E -->|槽位 5461-10922| G[转发到 Master 2<br/>172.20.0.3:7002] E -->|槽位 10923-16383| H[转发到 Master 3<br/>172.20.0.4:7003] F --> I{当前节点是否<br/>负责此槽位?} G --> I H --> I I -->|是| J[直接处理请求] I -->|否| K[返回MOVED重定向<br/>指向正确节点] K --> L[客户端重新连接<br/>正确的节点] L --> J J --> M[操作完成<br/>返回结果] style A fill:#e1f5fe style J fill:#c8e6c9 style K fill:#ffcdd2 style M fill:#c8e6c9
  1. 哈希槽机制

    • 总共16384个哈希槽(0-16383)
    • 每个key通过CRC16算法计算哈希值
    • 哈希值对16384取模确定槽位
  2. 数据分布策略

    槽位分配:
    Master 1: 0-5460     (5461个槽)
    Master 2: 5461-10922 (5462个槽)
    Master 3: 10923-16383(5461个槽)
    
  3. 路由机制

    • 客户端直接计算key对应的槽位
    • 如果访问错误节点,会收到MOVED重定向
    • 支持批量操作的Hash Tag功能

故障转移机制

sequenceDiagram participant C as 客户端 participant M1 as Master 1 participant S1 as Slave 1 participant M2 as Master 2 participant M3 as Master 3 Note over M1,S1: 正常运行状态 C->>M1: 写入数据 M1->>S1: 数据复制 M1->>C: 确认写入 Note over M1: Master 1 故障 M1--xS1: 连接中断 M2->>S1: 心跳检测失败 M3->>S1: 心跳检测失败 Note over S1,M3: 故障转移开始 S1->>S1: 开始选举过程 S1->>M2: 请求投票 S1->>M3: 请求投票 M2->>S1: 投票确认 M3->>S1: 投票确认 Note over S1: Slave 1 提升为新Master S1->>M2: 通知角色变更 S1->>M3: 通知角色变更 C->>S1: 继续读写请求 S1->>C: 正常响应 Note over M1: Master 1 恢复 M1->>S1: 重新加入集群 S1->>M1: 通知当前状态 Note over M1: 变为Slave角色

4. Docker Compose配置文件

创建docker-compose.yml文件:

version: '3.8'

services:
  # Redis集群节点1 - 主节点
  redis-node-1:
    image: redis:7.2-alpine
    container_name: redis-node-1
    ports:
      - "7001:7001"
      - "17001:17001"
    volumes:
      - ./redis-node-1.conf:/usr/local/etc/redis/redis.conf
      - redis-node-1-data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      - redis-cluster
    restart: unless-stopped

  # Redis集群节点2 - 主节点
  redis-node-2:
    image: redis:7.2-alpine
    container_name: redis-node-2
    ports:
      - "7002:7002"
      - "17002:17002"
    volumes:
      - ./redis-node-2.conf:/usr/local/etc/redis/redis.conf
      - redis-node-2-data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      - redis-cluster
    restart: unless-stopped

  # Redis集群节点3 - 主节点
  redis-node-3:
    image: redis:7.2-alpine
    container_name: redis-node-3
    ports:
      - "7003:7003"
      - "17003:17003"
    volumes:
      - ./redis-node-3.conf:/usr/local/etc/redis/redis.conf
      - redis-node-3-data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      - redis-cluster
    restart: unless-stopped

  # Redis集群节点4 - 从节点
  redis-node-4:
    image: redis:7.2-alpine
    container_name: redis-node-4
    ports:
      - "7004:7004"
      - "17004:17004"
    volumes:
      - ./redis-node-4.conf:/usr/local/etc/redis/redis.conf
      - redis-node-4-data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      - redis-cluster
    restart: unless-stopped

  # Redis集群节点5 - 从节点
  redis-node-5:
    image: redis:7.2-alpine
    container_name: redis-node-5
    ports:
      - "7005:7005"
      - "17005:17005"
    volumes:
      - ./redis-node-5.conf:/usr/local/etc/redis/redis.conf
      - redis-node-5-data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      - redis-cluster
    restart: unless-stopped

  # Redis集群节点6 - 从节点
  redis-node-6:
    image: redis:7.2-alpine
    container_name: redis-node-6
    ports:
      - "7006:7006"
      - "17006:17006"
    volumes:
      - ./redis-node-6.conf:/usr/local/etc/redis/redis.conf
      - redis-node-6-data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      - redis-cluster
    restart: unless-stopped

networks:
  redis-cluster:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

volumes:
  redis-node-1-data:
  redis-node-2-data:
  redis-node-3-data:
  redis-node-4-data:
  redis-node-5-data:
  redis-node-6-data:

关键配置项说明

  • image: 使用Redis 7.2官方Alpine镜像,体积小、安全性高
  • ports: 映射Redis服务端口和集群总线端口(服务端口+10000)
  • volumes: 挂载配置文件和数据目录,实现数据持久化
  • networks: 创建专用网络,确保容器间通信
  • restart: 设置容器重启策略,提高服务可用性

5. Redis配置文件

每个节点需要单独的配置文件。以redis-node-1.conf为例:

# 基础配置
port 7001
bind 0.0.0.0
protected-mode no
daemonize no

# 集群配置
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 15000
cluster-announce-ip 172.20.0.2
cluster-announce-port 7001
cluster-announce-bus-port 17001

# 持久化配置
appendonly yes
appendfilename "appendonly-7001.aof"
save 900 1
save 300 10
save 60 10000

# 内存管理
maxmemory 512mb
maxmemory-policy allkeys-lru

# 日志配置
loglevel notice
logfile ""

# 安全配置
# requirepass your_password_here
# masterauth your_password_here

其他节点配置文件

创建其余5个配置文件,主要修改以下参数:

redis-node-2.conf:

port 7002
cluster-config-file nodes-7002.conf
cluster-announce-ip 172.20.0.3
cluster-announce-port 7002
cluster-announce-bus-port 17002
appendfilename "appendonly-7002.aof"

redis-node-3.conf:

port 7003
cluster-config-file nodes-7003.conf
cluster-announce-ip 172.20.0.4
cluster-announce-port 7003
cluster-announce-bus-port 17003
appendfilename "appendonly-7003.aof"

redis-node-4.conf:

port 7004
cluster-config-file nodes-7004.conf
cluster-announce-ip 172.20.0.5
cluster-announce-port 7004
cluster-announce-bus-port 17004
appendfilename "appendonly-7004.aof"

redis-node-5.conf:

port 7005
cluster-config-file nodes-7005.conf
cluster-announce-ip 172.20.0.6
cluster-announce-port 7005
cluster-announce-bus-port 17005
appendfilename "appendonly-7005.aof"

redis-node-6.conf:

port 7006
cluster-config-file nodes-7006.conf
cluster-announce-ip 172.20.0.7
cluster-announce-port 7006
cluster-announce-bus-port 17006
appendfilename "appendonly-7006.aof"

5. 集群部署步骤

步骤1:创建项目目录

# 创建项目目录
mkdir redis-cluster
cd redis-cluster

# 创建配置文件目录
mkdir configs

步骤2:创建配置文件

将上述配置文件分别保存为对应的文件名。

步骤3:启动Redis容器

# 启动所有Redis节点
docker-compose up -d

# 查看容器状态
docker-compose ps

# 查看容器日志
docker-compose logs redis-node-1

步骤4:验证容器启动

# 查看网络连接
docker network ls

# 查看容器IP地址
docker inspect redis-node-1 | findstr IPAddress

6. 集群初始化

方法一:使用redis-cli创建集群

# 进入任意一个Redis容器
docker exec -it redis-node-1 /bin/sh

# 创建集群(在容器内执行)
redis-cli --cluster create \
  172.20.0.2:7001 \
  172.20.0.3:7002 \
  172.20.0.4:7003 \
  172.20.0.5:7004 \
  172.20.0.6:7005 \
  172.20.0.7:7006 \
  --cluster-replicas 1

# 输入 yes 确认创建集群

方法二:使用宿主机redis-cli(如果已安装)

# 从宿主机直接创建集群
redis-cli --cluster create \
  127.0.0.1:7001 \
  127.0.0.1:7002 \
  127.0.0.1:7003 \
  127.0.0.1:7004 \
  127.0.0.1:7005 \
  127.0.0.1:7006 \
  --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 172.20.0.5:7004 to 172.20.0.2:7001
Adding replica 172.20.0.6:7005 to 172.20.0.3:7002
Adding replica 172.20.0.7:7006 to 172.20.0.4:7003
>>> 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
>>> Performing Cluster Check (using node 172.20.0.2:7001)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

7. 集群功能测试

查看集群状态

# 连接到集群任意节点
docker exec -it redis-node-1 redis-cli -p 7001

# 查看集群信息
CLUSTER INFO

# 查看集群节点
CLUSTER NODES

# 退出redis-cli
exit

测试数据读写

# 连接到集群(启用集群模式)
docker exec -it redis-node-1 redis-cli -c -p 7001

# 设置一些测试数据
SET key1 "Hello Redis Cluster"
SET key2 "This is node 2"
SET key3 "Distributed data"

# 读取数据
GET key1
GET key2
GET key3

# 查看key分布在哪个槽位
CLUSTER KEYSLOT key1
CLUSTER KEYSLOT key2
CLUSTER KEYSLOT key3

验证数据分片

# 查看每个节点的key数量
docker exec redis-node-1 redis-cli -p 7001 DBSIZE
docker exec redis-node-2 redis-cli -p 7002 DBSIZE
docker exec redis-node-3 redis-cli -p 7003 DBSIZE

性能测试

# 使用redis-benchmark进行性能测试
docker exec redis-node-1 redis-benchmark -h 127.0.0.1 -p 7001 -t set,get -n 10000 -c 50

8. 集群扩容操作

随着业务增长,我们可能需要扩展Redis集群以提高处理能力和存储容量。本节将详细说明如何安全地向现有3主3从集群添加新节点。

扩容方案设计

我们将演示两种扩容场景:

  1. 添加1主1从:扩展为4主4从集群
  2. 为现有主节点添加从节点:提高读取性能

扩容前的准备工作

# 1. 备份现有集群配置
docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES > cluster_backup.txt

# 2. 检查集群当前状态
docker exec redis-node-1 redis-cli -p 7001 CLUSTER INFO

# 3. 检查数据分布情况
docker exec redis-node-1 redis-cli -p 7001 --cluster check 127.0.0.1:7001

方案一:添加1主1从节点

步骤1:更新Docker Compose配置

创建新的docker-compose-expanded.yml文件,添加两个新节点:

version: '3.8'

services:
  # ...existing services...
  # 这里省略原有的6个节点配置

  # Redis集群节点7 - 新主节点
  redis-node-7:
    image: redis:7.2-alpine
    container_name: redis-node-7
    ports:
      - "7007:7007"
      - "17007:17007"
    volumes:
      - ./redis-node-7.conf:/usr/local/etc/redis/redis.conf
      - redis-node-7-data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      redis-cluster:
        ipv4_address: 172.20.0.8
    restart: unless-stopped

  # Redis集群节点8 - 新从节点
  redis-node-8:
    image: redis:7.2-alpine
    container_name: redis-node-8
    ports:
      - "7008:7008"
      - "17008:17008"
    volumes:
      - ./redis-node-8.conf:/usr/local/etc/redis/redis.conf
      - redis-node-8-data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      redis-cluster:
        ipv4_address: 172.20.0.9
    restart: unless-stopped

volumes:
  # ...existing volumes...
  redis-node-7-data:
  redis-node-8-data:

步骤2:创建新节点配置文件

redis-node-7.conf(新主节点):

# 基础配置
port 7007
bind 0.0.0.0
protected-mode no
daemonize no

# 集群配置
cluster-enabled yes
cluster-config-file nodes-7007.conf
cluster-node-timeout 15000
cluster-announce-ip 172.20.0.8
cluster-announce-port 7007
cluster-announce-bus-port 17007

# 持久化配置
appendonly yes
appendfilename "appendonly-7007.aof"
save 900 1
save 300 10
save 60 10000

# 内存管理
maxmemory 512mb
maxmemory-policy allkeys-lru

# 日志配置
loglevel notice
logfile ""

redis-node-8.conf(新从节点):

# 基础配置
port 7008
bind 0.0.0.0
protected-mode no
daemonize no

# 集群配置
cluster-enabled yes
cluster-config-file nodes-7008.conf
cluster-node-timeout 15000
cluster-announce-ip 172.20.0.9
cluster-announce-port 7008
cluster-announce-bus-port 17008

# 持久化配置
appendonly yes
appendfilename "appendonly-7008.aof"
save 900 1
save 300 10
save 60 10000

# 内存管理
maxmemory 512mb
maxmemory-policy allkeys-lru

# 日志配置
loglevel notice
logfile ""

步骤3:启动新节点

# 启动新节点
docker-compose -f docker-compose-expanded.yml up -d redis-node-7 redis-node-8

# 验证新节点启动成功
docker-compose -f docker-compose-expanded.yml ps

# 检查新节点日志
docker-compose -f docker-compose-expanded.yml logs redis-node-7
docker-compose -f docker-compose-expanded.yml logs redis-node-8

步骤4:将新节点加入集群

# 1. 将新主节点加入集群
docker exec -it redis-node-1 redis-cli --cluster add-node 172.20.0.8:7007 172.20.0.2:7001

# 2. 将新从节点加入集群
docker exec -it redis-node-1 redis-cli --cluster add-node 172.20.0.9:7008 172.20.0.2:7001

# 3. 获取新主节点的Node ID
docker exec redis-node-7 redis-cli -p 7007 CLUSTER NODES | findstr myself

# 4. 设置node-8为node-7的从节点(替换<node-7-id>为实际ID)
docker exec redis-node-8 redis-cli -p 7008 CLUSTER REPLICATE <node-7-id>

步骤5:重新分片数据

# 启动重新分片向导
docker exec -it redis-node-1 redis-cli --cluster reshard 172.20.0.2:7001

# 按提示输入:
# 1. 要移动多少槽位?建议输入:4096 (16384/4)
# 2. 目标节点ID?输入新主节点的ID
# 3. 源节点?输入 all (从所有现有主节点获取槽位)
# 4. 确认?输入 yes

步骤6:验证扩容结果

# 检查集群状态
docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES

# 检查槽位分布
docker exec redis-node-1 redis-cli --cluster check 172.20.0.2:7001

# 测试新节点功能
docker exec -it redis-node-7 redis-cli -c -p 7007
SET test-expansion "Data on new node"
GET test-expansion

方案二:为现有主节点添加从节点

如果只需要提高读取性能而不增加存储容量,可以为现有主节点添加额外的从节点。

添加额外从节点的步骤

# 1. 启动新的从节点容器
docker-compose -f docker-compose-expanded.yml up -d redis-node-9

# 2. 将新节点加入集群
docker exec -it redis-node-1 redis-cli --cluster add-node 172.20.0.10:7009 172.20.0.2:7001

# 3. 获取目标主节点ID(例如node-1)
docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES | findstr master

# 4. 设置新节点为指定主节点的从节点
docker exec redis-node-9 redis-cli -p 7009 CLUSTER REPLICATE <master-node-id>

9. 集群缩容操作

当系统负载降低或需要节约资源时,可以安全地移除部分节点。缩容操作需要格外小心,以确保数据完整性。

缩容前的检查

# 1. 检查集群健康状态
docker exec redis-node-1 redis-cli --cluster check 172.20.0.2:7001

# 2. 备份集群数据
docker exec redis-node-1 redis-cli -p 7001 BGSAVE

# 3. 检查要移除节点的角色和数据
docker exec redis-node-7 redis-cli -p 7007 CLUSTER NODES
docker exec redis-node-7 redis-cli -p 7007 DBSIZE

安全移除从节点

移除从节点相对简单,因为从节点不持有唯一数据:

# 1. 获取要移除的从节点ID
docker exec redis-node-8 redis-cli -p 7008 CLUSTER NODES | findstr myself

# 2. 从集群中移除从节点
docker exec -it redis-node-1 redis-cli --cluster del-node 172.20.0.2:7001 <slave-node-id>

# 3. 停止并移除容器
docker stop redis-node-8
docker rm redis-node-8

# 4. 验证移除结果
docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES

安全移除主节点

移除主节点需要先迁移其持有的槽位:

步骤1:迁移槽位数据

# 1. 检查要移除主节点的槽位分配
docker exec redis-node-7 redis-cli -p 7007 CLUSTER NODES | findstr master

# 2. 使用reshard命令迁移槽位
docker exec -it redis-node-1 redis-cli --cluster reshard 172.20.0.2:7001

# 按提示操作:
# - 要移动多少槽位?输入该节点的所有槽位数量
# - 目标节点ID?选择其他主节点的ID
# - 源节点?输入要移除的主节点ID
# - 确认迁移?输入 yes

步骤2:移除空主节点

# 1. 确认节点已无槽位分配
docker exec redis-node-7 redis-cli -p 7007 CLUSTER NODES

# 2. 移除节点
docker exec -it redis-node-1 redis-cli --cluster del-node 172.20.0.2:7001 <empty-master-node-id>

# 3. 停止并移除容器
docker stop redis-node-7
docker rm redis-node-7

步骤3:验证缩容结果

# 检查集群状态
docker exec redis-node-1 redis-cli --cluster check 172.20.0.2:7001

# 验证数据完整性
docker exec -it redis-node-1 redis-cli -c -p 7001
KEYS *
GET test-expansion  # 验证之前的测试数据

批量缩容操作

如果需要同时移除多个节点,建议按以下顺序:

# 1. 先移除所有从节点
for slave in node-4 node-5 node-6; do
    echo "Removing $slave..."
    # 执行移除从节点的命令
    docker exec -it redis-node-1 redis-cli --cluster del-node 172.20.0.2:7001 <slave-node-id>
    docker stop redis-node-$slave
    docker rm redis-node-$slave
    echo "$slave removed."
    # 验证移除结果
    docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES
    echo "Current cluster nodes:"
    docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES
    echo "-------------------------"
    sleep 2  # 等待集群稳定
done

# 2. 再移除主节点(逐个迁移槽位)
for master in node-2 node-3; do
    echo "Migrating slots from $master..."
    # 执行槽位迁移和节点移除
    docker exec -it redis-node-1 redis-cli --cluster reshard
    docker exec -it redis-node-1 redis-cli --cluster del-node 172.20.0.2:7001 <master-node-id>
    docker stop redis-node-$master
    docker rm redis-node-$master
    echo "$master removed."
    # 验证移除结果
    docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES
    echo "Current cluster nodes:"
    docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES
    echo "-------------------------"
    sleep 2  # 等待集群稳定
done

7. 集群功能测试

模拟主节点故障

# 停止一个主节点(例如node-1)
docker stop redis-node-1

# 查看集群状态
docker exec -it redis-node-2 redis-cli -c -p 7002 CLUSTER NODES

# 测试数据读写是否正常
docker exec -it redis-node-2 redis-cli -c -p 7002
SET test-failover "Failover test"
GET test-failover

观察故障转移过程

故障转移通常在几秒内完成:

  1. 集群检测到主节点不可达
  2. 该主节点的从节点开始选举
  3. 选举成功的从节点提升为新的主节点
  4. 集群重新分配槽位

恢复故障节点

# 重新启动故障节点
docker start redis-node-1

# 检查节点状态(此时node-1会变成从节点)
docker exec -it redis-node-1 redis-cli -p 7001 CLUSTER NODES

故障转移测试

测试集群的高可用性是验证集群功能的重要环节:

模拟主节点故障

# 1. 记录当前集群状态
docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES > cluster_before_failure.txt

# 2. 停止一个主节点(例如node-1)
docker stop redis-node-1

# 3. 等待几秒钟,观察故障转移
Start-Sleep -Seconds 10

# 4. 检查集群状态变化
docker exec -it redis-node-2 redis-cli -c -p 7002 CLUSTER NODES

# 5. 测试集群是否仍可正常工作
docker exec -it redis-node-2 redis-cli -c -p 7002
SET failover-test "Cluster still works"
GET failover-test

观察故障恢复

# 1. 重新启动故障节点
docker start redis-node-1

# 2. 检查节点重新加入集群的状态
docker exec redis-node-1 redis-cli -p 7001 CLUSTER NODES

# 3. 验证数据一致性
docker exec -it redis-node-1 redis-cli -c -p 7001
GET failover-test

数据一致性验证

# 创建测试脚本验证数据一致性
$testScript = @"
#!/bin/bash
for i in {1..100}; do
    docker exec redis-node-1 redis-cli -c -p 7001 SET "test_key_$i" "value_$i"
done

echo "Data written, now checking consistency..."
for node in 2 3; do
    echo "Checking node $node:"
    for i in {1..100}; do
        result=`docker exec redis-node-$node redis-cli -c -p 700$node GET "test_key_$i"`
        if [ "$result" != "value_$i" ]; then
            echo "Inconsistency found for key test_key_$i"
        fi
    done
done
"@

$testScript | Out-File -FilePath "test_consistency.sh" -Encoding UTF8

10. 常见问题与解决方案

扩容相关问题

问题1:新节点无法加入集群

错误现象

[ERR] Sorry, can't connect to node 172.20.0.8:7007

原因分析

  • 网络配置错误
  • 端口映射问题
  • 防火墙阻拦

解决方案

# 1. 检查容器网络连通性
docker exec redis-node-1 ping 172.20.0.8

# 2. 检查端口映射
docker port redis-node-7

# 3. 验证Redis服务状态
docker exec redis-node-7 redis-cli -p 7007 ping

# 4. 检查集群配置
docker exec redis-node-7 redis-cli -p 7007 CLUSTER NODES

问题2:重新分片过程中断

错误现象

[ERR] Calling MIGRATE ERR syntax error

解决方案

# 1. 检查集群状态
docker exec redis-node-1 redis-cli --cluster check 172.20.0.2:7001

# 2. 手动修复不一致的槽位
docker exec redis-node-1 redis-cli --cluster fix 172.20.0.2:7001

# 3. 重新开始分片操作
docker exec -it redis-node-1 redis-cli --cluster reshard 172.20.0.2:7001

缩容相关问题

问题3:无法移除主节点

错误现象

[ERR] Node 172.20.0.8:7007 is not empty! Reshard data away and try again.

解决方案

# 1. 检查节点槽位分配
docker exec redis-node-7 redis-cli -p 7007 CLUSTER NODES | findstr 172.20.0.8

# 2. 迁移所有槽位到其他节点
docker exec -it redis-node-1 redis-cli --cluster reshard 172.20.0.2:7001

# 3. 确认槽位迁移完成后再次尝试移除
docker exec -it redis-node-1 redis-cli --cluster del-node 172.20.0.2:7001 <node-id>

问题4:数据迁移过程中性能下降

解决方案

# 1. 调整迁移并发度
docker exec redis-node-1 redis-cli CONFIG SET cluster-migration-barrier 2

# 2. 在业务低峰期进行迁移
# 3. 监控集群负载情况
docker exec redis-node-1 redis-cli INFO stats

性能相关问题

问题5:集群响应延迟增加

诊断步骤

# 1. 检查集群节点延迟
docker exec redis-node-1 redis-cli --latency -i 1

# 2. 检查网络延迟
docker exec redis-node-1 redis-cli --cluster check 172.20.0.2:7001

# 3. 分析慢查询日志
docker exec redis-node-1 redis-cli SLOWLOG GET 10

11. 性能优化建议

Docker资源优化

容器资源限制

更新docker-compose.yml,添加资源限制:

services:
  redis-node-1:
    # ...existing configuration...
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M
    ulimits:
      memlock:
        soft: -1
        hard: -1

系统级优化

# 1. 调整Docker daemon配置
# 编辑 %USERPROFILE%\.docker\daemon.json
$daemonConfig = @{
    "storage-driver" = "overlay2"
    "log-driver" = "json-file"
    "log-opts" = @{
        "max-size" = "10m"
        "max-file" = "3"
    }
    "default-ulimits" = @{
        "memlock" = @{
            "Hard" = -1
            "Name" = "memlock"
            "Soft" = -1
        }
    }
} | ConvertTo-Json -Depth 3

$daemonConfig | Out-File -FilePath "$env:USERPROFILE\.docker\daemon.json" -Encoding UTF8

Redis集群优化

关键配置参数

为每个Redis节点添加性能优化配置:

# 网络优化
tcp-keepalive 300
tcp-backlog 511
timeout 0

# 内存优化
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

# 持久化优化
stop-writes-on-bgsave-error no
rdbcompression yes
rdbchecksum yes
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes

# 集群优化
cluster-require-full-coverage no
cluster-node-timeout 15000
cluster-slave-validity-factor 10
cluster-migration-barrier 1

监控和调优

# 1. 创建性能监控脚本
$monitorScript = @"
# Redis集群性能监控脚本
while ($true) {
    Write-Host "=== Redis Cluster Performance Monitor ===" -ForegroundColor Green
    Write-Host "Time: $(Get-Date)" -ForegroundColor Yellow
    
    # 检查每个节点的内存使用
    for ($i=1; $i -le 6; $i++) {
        $memory = docker exec redis-node-$i redis-cli -p 700$i INFO memory | Select-String "used_memory_human"
        Write-Host "Node $i Memory: $memory"
    }
    
    # 检查集群状态
    $clusterInfo = docker exec redis-node-1 redis-cli -p 7001 CLUSTER INFO | Select-String "cluster_state"
    Write-Host "Cluster State: $clusterInfo"
    
    # 检查连接数
    $connections = docker exec redis-node-1 redis-cli -p 7001 INFO clients | Select-String "connected_clients"
    Write-Host "Connections: $connections"
    
    Start-Sleep -Seconds 30
}
"@

$monitorScript | Out-File -FilePath "monitor_cluster.ps1" -Encoding UTF8

# 2. 运行监控脚本
# powershell -ExecutionPolicy Bypass -File monitor_cluster.ps1

生产环境建议

安全配置

# 启用密码认证
requirepass your_strong_password_here
masterauth your_strong_password_here

# 绑定特定接口
bind 127.0.0.1 172.20.0.2

# 启用保护模式
protected-mode yes

# 重命名危险命令
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG "CONFIG_b840fc02d524045429941cc15f59e41cb7be6c52"

备份策略

# 1. 创建自动备份脚本
$backupScript = @"
# Redis集群备份脚本
$backupDir = "C:\redis-backups\$(Get-Date -Format 'yyyy-MM-dd')"
New-Item -ItemType Directory -Path $backupDir -Force

# 备份每个主节点
foreach ($node in @(1,2,3)) {
    Write-Host "Backing up redis-node-$node..."
    docker exec redis-node-$node redis-cli -p 700$node BGSAVE
    
    # 等待备份完成
    do {
        Start-Sleep -Seconds 5
        $lastSave = docker exec redis-node-$node redis-cli -p 700$node LASTSAVE
    } while ($lastSave -eq $previousSave)
    
    # 复制备份文件
    docker cp redis-node-$node`:/data/dump.rdb $backupDir\node-$node-dump.rdb
    Write-Host "Node $node backup completed."
}

# 清理7天前的备份
Get-ChildItem "C:\redis-backups" | Where-Object {$_.CreationTime -lt (Get-Date).AddDays(-7)} | Remove-Item -Recurse -Force
"@

$backupScript | Out-File -FilePath "backup_cluster.ps1" -Encoding UTF8

# 2. 设置定时任务
# schtasks /create /tn "Redis Cluster Backup" /tr "powershell -ExecutionPolicy Bypass -File C:\path\to\backup_cluster.ps1" /sc daily /st 02:00
posted @ 2025-06-01 19:09  生命博爱  阅读(134)  评论(0)    收藏  举报