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。