Windows 环境下使用 Docker 搭建 Redis 集群指南

在 Windows 环境下使用 Docker 搭建 Redis 集群,最核心的问题是解决 容器内部 IP 与宿主机映射 IP 不一致 导致的 MOVED 重定向失败。

1. 环境准备

  • 宿主机 IP: 192.168.0.110 (请确保你的 Java 程序能访问该 IP)
  • Docker Desktop for Windows
  • 端口规划:
    • 数据端口:6379 - 6384
    • 集群总线端口:16379 - 16384 (数据端口 + 10000)

2. 编写 docker-compose.yml

关键点在于使用 cluster-announce 系列参数,显式声明宿主机 IP 和对应的映射端口。

version: '3.9'

services:
  redis-master-1:
    image: redis:7.2
    container_name: redis-master-1
    command:
      - "redis-server"
      - "--port"
      - "6379"
      - "--cluster-enabled"
      - "yes"
      - "--cluster-config-file"
      - "nodes.conf"
      - "--cluster-node-timeout"
      - "5000"
      - "--appendonly"
      - "yes"
      - "--cluster-announce-ip"
      - "192.168.0.110"    # 宿主机局域网 IP
      - "--cluster-announce-port"
      - "6379"             # 外部访问的映射端口
      - "--cluster-announce-bus-port"
      - "16379"            # 集群总线端口
    ports:
      - "6379:6379"
      - "16379:16379"
    volumes:
      - ./data/redis-master-1:/data
    networks:
      - redis-cluster

  redis-master-2:
    image: redis:7.2
    container_name: redis-master-2
    command:
      - "redis-server"
      - "--port"
      - "6379"
      - "--cluster-enabled"
      - "yes"
      - "--cluster-config-file"
      - "nodes.conf"
      - "--cluster-node-timeout"
      - "5000"
      - "--appendonly"
      - "yes"
      - "--cluster-announce-ip"
      - "192.168.0.110"
      - "--cluster-announce-port"
      - "6380"             # 第二个节点映射到宿主机的 6380
      - "--cluster-announce-bus-port"
      - "16380"
    ports:
      - "6380:6379"
      - "16380:16379"
    volumes:
      - ./data/redis-master-2:/data
    networks:
      - redis-cluster

  redis-master-3:
    image: redis:7.2
    container_name: redis-master-3
    command:
      - "redis-server"
      - "--port"
      - "6379"
      - "--cluster-enabled"
      - "yes"
      - "--cluster-config-file"
      - "nodes.conf"
      - "--cluster-node-timeout"
      - "5000"
      - "--appendonly"
      - "yes"
      - "--cluster-announce-ip"
      - "192.168.0.110"
      - "--cluster-announce-port"
      - "6381"
      - "--cluster-announce-bus-port"
      - "16381"
    ports:
      - "6381:6379"
      - "16381:16379"
    volumes:
      - ./data/redis-master-3:/data
    networks:
      - redis-cluster

  redis-slave-1:
    image: redis:7.2
    container_name: redis-slave-1
    command: ["redis-server", "--port", "6379", "--cluster-enabled", "yes", "--cluster-config-file", "nodes.conf", "--cluster-announce-ip", "192.168.0.110", "--cluster-announce-port", "6382", "--cluster-announce-bus-port", "16382"]
    ports:
      - "6382:6379"
      - "16382:16379"
    volumes:
      - ./data/redis-slave-1:/data
    networks:
      - redis-cluster

  redis-slave-2:
    image: redis:7.2
    container_name: redis-slave-2
    command: ["redis-server", "--port", "6379", "--cluster-enabled", "yes", "--cluster-config-file", "nodes.conf", "--cluster-announce-ip", "192.168.0.110", "--cluster-announce-port", "6383", "--cluster-announce-bus-port", "16383"]
    ports:
      - "6383:6379"
      - "16383:16379"
    volumes:
      - ./data/redis-slave-2:/data
    networks:
      - redis-cluster

  redis-slave-3:
    image: redis:7.2
    container_name: redis-slave-3
    command: ["redis-server", "--port", "6379", "--cluster-enabled", "yes", "--cluster-config-file", "nodes.conf", "--cluster-announce-ip", "192.168.0.110", "--cluster-announce-port", "6384", "--cluster-announce-bus-port", "16384"]
    ports:
      - "6384:6379"
      - "16384:16379"
    volumes:
      - ./data/redis-slave-3:/data
    networks:
      - redis-cluster

networks:
  redis-cluster:
    driver: bridge

3. 部署步骤

3.1 清理数据(如果有旧集群)

如果是重新搭建,务必手动删除宿主机上的 ./data 文件夹,否则 Redis 会加载旧的节点信息。

3.2 启动容器

Bash

docker-compose up -d

3.3 创建集群

执行以下命令初始化集群(3主3从)。注意:此处 IP 必须为宿主机 IP,且使用外部映射端口。

Bash

docker exec -it redis-master-1 redis-cli --cluster create \
192.168.0.110:6379 \
192.168.0.110:6380 \
192.168.0.110:6381 \
192.168.0.110:6382 \
192.168.0.110:6383 \
192.168.0.110:6384 \
--cluster-replicas 1

5. Java 测试集群连接

在 Spring Boot 或 Jedis 配置中,连接节点应至少包含一个节点的宿主机 IP:

@SpringBootTest(classes = {WebApplication.class})
class RedisTest {

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    void testWriteToEveryMasterNode() {
        // 1. 获取集群中所有的 Master 节点信息
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        Iterable<RedisClusterNode> nodes = connectionFactory.getClusterConnection().clusterGetNodes();

        Set<String> masterNodeIds = new HashSet<>();
        for (RedisClusterNode node : nodes) {
            if (node.isMaster()) {
                masterNodeIds.add(node.getId());
                System.out.println("发现 Master 节点: " + node.getHost() + ":" + node.getPort() + " (ID: " + node.getId() + ")");
            }
        }

        System.out.println("--- 开始测试:尝试覆盖所有 " + masterNodeIds.size() + " 个主节点 ---");

        Set<String> coveredMasters = new HashSet<>();
        int attempts = 0;
        int maxAttempts = 500; // 防止陷入死循环

        while (coveredMasters.size() < masterNodeIds.size() && attempts < maxAttempts) {
            String testKey = "test_key_" + UUID.randomUUID().toString().substring(0, 8);
            String testValue = "value_" + attempts;

            // 写入数据
            redisUtils.set(testKey, testValue);

            // 获取该 Key 所在的节点信息 (通过 RedisTemplate 获取连接验证)
            // 注意:这里利用 RedisConnection 的 clusterGetNodeForKey 来定位 Key 所属节点
            RedisClusterNode nodeForKey = connectionFactory.getClusterConnection().clusterGetNodeForKey(testKey.getBytes());

            if (nodeForKey != null) {
                String nodeId = nodeForKey.getId();
                if (masterNodeIds.contains(nodeId) && !coveredMasters.contains(nodeId)) {
                    coveredMasters.add(nodeId);
                    System.out.println("成功写入节点 [" + nodeForKey.getHost() + ":" + nodeForKey.getPort() +
                            "] -> Key: " + testKey + " (已覆盖: " + coveredMasters.size() + "/" + masterNodeIds.size() + ")");
                }
            }

            // 验证读取
            Object result = redisUtils.get(testKey);
            assert testValue.equals(result);

            attempts++;
        }

        System.out.println("--- 测试完成 ---");
        System.out.println("总共尝试生成 Key 次数: " + attempts);
        if (coveredMasters.size() == masterNodeIds.size()) {
            System.out.println("结果: 成功覆盖所有物理 Master 节点!");
        } else {
            System.out.println("结果: 未能在限制次数内覆盖所有节点,请检查集群状态。");
        }
    }
}

配置好 cluster-announce-ip 后,当发生数据重定向时,Redis 会返回 192.168.0.110:638x,此时 Java 程序即可顺利找到对应节点,不再报错 MOVED