You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring Boot集成测试中用Testcontainers启动哨兵模式Redis?

如何用Testcontainers搭建Redis哨兵模式用于Spring Boot集成测试

当前配置的核心问题

  • 哨兵未关联主节点:启动的哨兵容器缺少监控Redis主节点的关键参数,哨兵不知道要监控哪个实例,无法正常工作。
  • 网络别名缺失:指定了--sentinel-announce-ip为redis-sentinel-1,但未给哨兵容器设置对应网络别名,导致容器在Testcontainers自定义网络中无法被解析。
  • 等待策略不完整:仅监听端口无法确保哨兵完成主节点的发现和初始化,此时操作哨兵会出现未就绪问题。
  • 手动管理生命周期:手动调用start()/stop()容易遗漏资源清理,Testcontainers本身支持自动管理容器启停。

正确实现方案

以下是修正后的完整代码,包含哨兵关联主节点、网络配置、就绪验证等关键步骤:

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;

public class RedisSentinelIntegrationTest {

    private static final String REDIS_MASTER_NAME = "mymaster";
    private static final int REDIS_MASTER_PORT = 6379;
    private static final int REDIS_SENTINEL_PORT = 26379;

    // 创建共享网络,让所有容器可互相访问
    private final Network redisNetwork = Network.newNetwork();

    // Redis主节点容器
    private final GenericContainer<?> redisMaster = new GenericContainer<>(DockerImageName.parse("redis:6.0.12"))
            .withExposedPorts(REDIS_MASTER_PORT)
            .withNetwork(redisNetwork)
            .withNetworkAliases("redis-master") // 设置网络别名,让哨兵可通过该名称访问
            .waitingFor(Wait.forListeningPort());

    // Redis哨兵容器1
    private final GenericContainer<?> sentinel1 = createSentinelContainer(redisNetwork, "sentinel-1");
    // Redis哨兵容器2
    private final GenericContainer<?> sentinel2 = createSentinelContainer(redisNetwork, "sentinel-2");

    // 封装哨兵容器创建逻辑
    private GenericContainer<?> createSentinelContainer(Network network, String alias) {
        return new GenericContainer<>(DockerImageName.parse("redis:6.0.12"))
                .withExposedPorts(REDIS_SENTINEL_PORT)
                .withNetwork(network)
                .withNetworkAliases(alias) // 设置网络别名
                // 启动哨兵并配置监控主节点的参数
                .withCommand(
                        "redis-server", "--sentinel",
                        "--sentinel-monitor", REDIS_MASTER_NAME, "redis-master", String.valueOf(REDIS_MASTER_PORT), "2",
                        "--sentinel-down-after-milliseconds", REDIS_MASTER_NAME, "5000",
                        "--sentinel-failover-timeout", REDIS_MASTER_NAME, "10000"
                )
                // 等待哨兵就绪:监听日志中的主节点状态正常标识
                .waitingFor(Wait.forLogMessage("*sentinel_master_status\\s+ok*", 1))
                .dependsOn(redisMaster); // 确保主节点先启动
    }

    @Test
    void testRedisSentinelSetup() {
        // 启动所有容器(Testcontainers会自动在测试结束后停止清理)
        redisMaster.start();
        sentinel1.start();
        sentinel2.start();

        // 获取宿主机可访问的哨兵地址
        String sentinel1Addr = sentinel1.getHost() + ":" + sentinel1.getMappedPort(REDIS_SENTINEL_PORT);
        String sentinel2Addr = sentinel2.getHost() + ":" + sentinel2.getMappedPort(REDIS_SENTINEL_PORT);

        // 此处可编写测试逻辑:比如用Redis客户端连接哨兵,验证主节点状态
        // 示例:
        // Set<String> sentinels = new HashSet<>(Arrays.asList(sentinel1Addr, sentinel2Addr));
        // JedisSentinelPool pool = new JedisSentinelPool(REDIS_MASTER_NAME, sentinels);
        // try (Jedis jedis = pool.getResource()) {
        //     jedis.set("test-key", "test-value");
        //     assert jedis.get("test-key").equals("test-value");
        // }
    }
}

关键配置说明

  1. 网络别名:给主节点和哨兵设置withNetworkAliases,容器间可通过别名互相访问,避免依赖动态IP。
  2. 哨兵启动命令:通过--sentinel-monitor指定监控的主节点名称、地址(用主节点网络别名)、端口,以及法定人数(设为2,即至少2个哨兵同意才触发故障转移)。
  3. 就绪等待策略:使用Wait.forLogMessage监听日志中的sentinel_master_status ok,确保哨兵成功关联主节点后再执行后续测试。
  4. 容器依赖:用dependsOn确保主节点先于哨兵启动,避免哨兵启动时找不到主节点。

结合Spring Boot的优化

如果是Spring Boot应用,可借助Testcontainers的Spring Boot支持,通过@Testcontainers和@Container自动管理容器,同时在application-test.yml中配置哨兵连接:

spring:
  data:
    redis:
      sentinel:
        master: mymaster
        nodes: ${sentinel1.host}:${sentinel1.port},${sentinel2.host}:${sentinel2.port}

在测试类中通过@DynamicPropertySource注入哨兵动态端口:

import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
// 其他必要注解

@Testcontainers
public class SpringBootRedisSentinelTest {

    // 容器定义同上

    @DynamicPropertySource
    static void registerRedisProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.redis.sentinel.nodes", () ->
                sentinel1.getHost() + ":" + sentinel1.getMappedPort(REDIS_SENTINEL_PORT) + "," +
                sentinel2.getHost() + ":" + sentinel2.getMappedPort(REDIS_SENTINEL_PORT)
        );
        registry.add("spring.data.redis.sentinel.master", () -> REDIS_MASTER_NAME);
    }

    // 测试方法
}

内容的提问来源于stack exchange,提问作者CuriousMind

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 18:50:53