spring-bootdockerredislettuce

Spring Boot app cannot connect to Redis Replica in Docker


I am experiencing a weird issue with Redis connectivity in the Docker. I have a simple Spring Boot application with a Master-Replica configuration. As well as docker-compose config which I use to start Redis Master and Redis Replica.

If I start Redis via docker-compose and Spring Boot app as a simple java process outside of Docker, everything works fine. It can successfully connect to both Master and Replica via localhost.

If I launch Spring Boot app as a Docker container alongside Redis containers it can successfully connect to Master, but not Replica. Which means that I can write and read to Master node but when I try to read from Replica I get the following error:

redis-sample-app_1  | Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: redis-replica-a/172.31.0.2:7001
redis-sample-app_1  | Caused by: java.net.ConnectException: Connection refused
redis-sample-app_1  |   at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.8.0_302]

In the redis.conf I've changed the following to bind it to all network interfaces:

bind * -::*
protected-mode no

docker-compose.yml

version: "3"

services:
  redis-master:
    image: redis:alpine
    command: redis-server --include /usr/local/etc/redis/redis.conf
    volumes:
      - ./conf/redis-master.conf:/usr/local/etc/redis/redis.conf
    ports:
      - "6379:6379"

  redis-replica-a:
    image: redis:alpine
    command: redis-server --include /usr/local/etc/redis/redis.conf
    volumes:
      - ./conf/redis-replica.conf:/usr/local/etc/redis/redis.conf
    ports:
      - "7001:6379"

  redis-sample-app:
    image: docker.io/library/redis-sample:0.0.1-SNAPSHOT
    environment:
      - SPRING_REDIS_HOST=redis-master
      - SPRING_REDIS_PORT=6379
      - SPRING_REDIS_REPLICAS=redis-replica-a:7001
    ports:
      - "9080:8080"
    depends_on:
      - redis-master
      - redis-replica-a

application.yml

spring:
  redis:
    port: 6379
    host: localhost
    replicas: localhost:7001

RedisConfig.java

@Configuration
class RedisConfig {

    private static final Logger LOG = LoggerFactory.getLogger(RedisConfig.class);

    @Value("${spring.redis.replicas:}")
    private String replicasProperty;

    private final RedisProperties redisProperties;

    public RedisConfig(RedisProperties redisProperties) {
        this.redisProperties = redisProperties;
    }

    @Bean
    public StringRedisTemplate masterReplicaRedisTemplate(LettuceConnectionFactory connectionFactory) {
        return new StringRedisTemplate(connectionFactory);
    }

    @Bean
    public LettuceConnectionFactory masterReplicaLettuceConnectionFactory(LettuceClientConfiguration lettuceConfig) {
        LOG.info("Master: {}:{}", redisProperties.getHost(), redisProperties.getPort());
        LOG.info("Replica property: {}", replicasProperty);

        RedisStaticMasterReplicaConfiguration configuration = new RedisStaticMasterReplicaConfiguration(redisProperties.getHost(), redisProperties.getPort());

        if (StringUtils.hasText(replicasProperty)) {
            List<RedisURI> replicas = Arrays.stream(this.replicasProperty.split(",")).map(this::toRedisURI).collect(Collectors.toList());
            LOG.info("Replica nodes: {}", replicas);
            replicas.forEach(replica -> configuration.addNode(replica.getHost(), replica.getPort()));
        }

        return new LettuceConnectionFactory(configuration, lettuceConfig);
    }

    @Scope("prototype")
    @Bean(destroyMethod = "shutdown")
    ClientResources clientResources() {
        return DefaultClientResources.create();
    }

    @Scope("prototype")
    @Bean
    LettuceClientConfiguration lettuceConfig(ClientResources dcr) {
        ClientOptions options = ClientOptions.builder()
                .timeoutOptions(TimeoutOptions.builder().fixedTimeout(Duration.of(5, ChronoUnit.SECONDS)).build())
                .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
                .autoReconnect(true)
                .build();

        return LettuceClientConfiguration.builder()
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .clientOptions(options)
                .clientResources(dcr)
                .build();
    }

    @Bean
    StringRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }

    private RedisURI toRedisURI(String url) {
        String[] split = url.split(":");
        String host = split[0];
        int port;
        if (split.length > 1) {
            port = Integer.parseInt(split[1]);
        } else {
            port = 6379;
        }
        return RedisURI.create(host, port);
    }
}

Please advise how to continue with troubleshooting.


Solution

  • When running everything (redis, replica and spring) inside the docker network you should be using port 6379 instead of 7001

    7001 port can be used to connect from outside the container to it. But now you are trying to connect from container to container.

    So change your environment variable to

    SPRING_REDIS_REPLICAS=redis-replica-a:6379