Search code examples
spring-bootdockerredisredis-cluster

Can not connect SpringBoot to redis-cluster on docker


I want to make local redis-cluster on docker.

  • I tried make redis-cluster on local docker.
  • I have 3 dockers.
    1. docker redis:5.0.1-alpine 172.18.1.1
    2. docker redis:5.0.1-alpine 172.18.1.2
    3. docker redis:5.0.1-alpine 172.18.1.3
  • They are connected via mynet network. Docker config
  • I made redis cluster.
docker 172.18.1.1 $> redis-cli --cluster create --cluster-replicas 0 172.18.1.1:6379 172.18.1.2:6379 172.18.1.3:6379

They are works well!!

$> redis-cli -c
127.0.0.1:6379> set hello redis 
OK
127.0.0.1:6379> get hello 
"redis"
127.0.0.1:6379> 

The problem is can not connect to redis-cluster from Spring Boot Application.

  • Springboot version: 1.5.7.RELEASE
  • REDIS LIB version: redis.clients:jedis:2.9.0
  • here are some codes
spring.redis.cluster.nodes=127.0.0.1:36379,127.0.0.1:36380,127.0.0.1:36381
spring.redis.cluster.max-redirects=6


@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class RedisClusterConfigurationProperties {
    List<String> nodes;

    public List<String> getNodes() {
        return nodes;
    }

    public void setNodes(List<String> nodes) {
        this.nodes = nodes;
    }
}


@Configuration
@Service
public class SomesService {
    @Autowired
    private RedisClusterConfigurationProperties clusterProperties;

    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        final JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

        return jedisPoolConfig;
    }

    @Bean(name = "redisTemplate")
    public RedisTemplate redisTemplate() {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        redisTemplate.setConnectionFactory(connectionFactory());
        redisTemplate.setExposeConnection(true);
        return redisTemplate;
    }

    private RedisConnectionFactory connectionFactory() {

        final RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(clusterProperties.getNodes());
        final JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(
            clusterConfig, jedisPoolConfig());
        jedisConnectionFactory.setUsePool(true);
        return jedisConnectionFactory;
    }

}

result

org.springframework.data.redis.ClusterStateFailureException: Could not retrieve cluster information. CLUSTER NODES returned with error.
    - 172.18.1.1:6379 failed: Could not get a resource from the pool
    - 172.18.1.2:6379 failed: Could not get a resource from the pool
    - 172.18.1.3:6379 failed: Could not get a resource from the pool

    at org.springframework.data.redis.connection.jedis.JedisClusterConnection$JedisClusterTopologyProvider.getTopology(JedisClusterConnection.java:4237)
    at org.springframework.data.redis.connection.ClusterCommandExecutor.getClusterTopology(ClusterCommandExecutor.java:349)
    at org.springframework.data.redis.connection.ClusterCommandExecutor.executeCommandOnAllNodes(ClusterCommandExecutor.java:188)
    at org.springframework.data.redis.connection.jedis.JedisClusterConnection.info(JedisClusterConnection.java:3128)
--- removed my package sorry ;-)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
--- removed my package sorry ;-)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

2019-07-23 15:48:01.579  INFO 13741 --- [       Thread-5] o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework.web.context.support.GenericWebApplicationContext@305ffe9e: startup date [Tue Jul 23 15:47:42 KST 2019]; root of context hierarchy
2019:07:23 15:48:01.579 INFO  --- [Thread-5] o.s.w.c.s.GenericWebApplicationContext : - 984 Closing org.springframework.web.context.support.GenericWebApplicationContext@305ffe9e: startup date [Tue Jul 23 15:47:42 KST 2019]; root of context hierarchy

Process finished with exit code 255
  • Error messages

Could not retrieve cluster information. CLUSTER NODES returned with error. - 172.18.1.1:6379 failed: Could not get a resource from the pool * '172.18.1.1' is docker host ip. * '127.0.0.1:36379' is the value of what I put in application.properties file.

It's working on docker

  • But!!! If I put a war the result of SpringBoot build in a docker which connected via mynet, it's working!!
  • Build the project > copy the war in another docker > run the war. It's working!! Very Well.
    • I think redis-cluster do not allow proxy pass or something...
  • Anybody knows how to make local redis-cluster in local docker?

Solution

  • The problem is that in Redis Cluster, clients gets the URLs of all Redis nodes from Redis node itself.

    So in your case, the Spring Boot application sends "cluster nodes" request to one of the configured nodes (127.0.0.1:36379,127.0.0.1:36380,127.0.0.1:36381).

    As a response, it receives the URL of all Redis nodes and in your case its 172.18.1.1:6379 172.18.1.2:6379 172.18.1.3:6379 and tries to communicate with them. Since this is internal Docker network you get connection failure error.

    What you want is to configure each Redis node to have a different "advertised" host and port. This way the clients will receive URL that they can access instead of the internal host and port.

    This is the configuration parameters you should add:

    cluster-announce-ip: The IP address to announce.
    cluster-announce-port: The data port to announce.
    cluster-announce-bus-port: The cluster bus port to announce.
    

    See this blog post that explain this in more details: https://get-reddie.com/blog/redis4-cluster-docker-compose/