如何在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"); // } } }
关键配置说明
- 网络别名:给主节点和哨兵设置
withNetworkAliases,容器间可通过别名互相访问,避免依赖动态IP。 - 哨兵启动命令:通过
--sentinel-monitor指定监控的主节点名称、地址(用主节点网络别名)、端口,以及法定人数(设为2,即至少2个哨兵同意才触发故障转移)。 - 就绪等待策略:使用
Wait.forLogMessage监听日志中的sentinel_master_status ok,确保哨兵成功关联主节点后再执行后续测试。 - 容器依赖:用
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
相关产品推荐
相关产品推荐

