容器化redis-cluster使用(二)java客戶端刷新cluster topology問題

          前一篇提到了容器ip變化後集羣自發現的問題,現在接着講這個問題引申出的另兩個問題:

1.雖然集羣ip問題解決了,但java client還是連接報錯,似乎連的是老的ip地址。

2.當key所在的master掛了,slave切換到master後,java客戶端卻一直嘗試重連,直到超時,並沒有隨着master的切換去連主拿key。以下代碼和例子爲環境模擬。

集羣信息:

192.168.27.128:7000> cluster nodes
ffd1619ad6b8f8db6d5764ab6739f4b03661cbe3 192.168.27.128:7001@17001 master - 0 1568640842000 70 connected 11404 11589-16383
8ca3773af76228ac6a864ef660a7e701d207aa3c 192.168.27.129:7002@17002 master - 0 1568640844271 35 connected 666-5460
a92e5ab3de251dd49a25c313380fb2ce8a81513c 192.168.27.128:7003@17003 slave a2d327c3be89c685524fe12e26b8e7227b500e20 0 1568640842000 37 connected
8c81e214847a2e0ed609a432f15f8ef048cff047 192.168.27.129:7001@17001 slave ffd1619ad6b8f8db6d5764ab6739f4b03661cbe3 0 1568640842355 70 connected
7e0686f3bec7c103a794aa6402c7b9ad0f6c632e 192.168.27.128:7000@17000 myself,slave 8ca3773af76228ac6a864ef660a7e701d207aa3c 0 1568640842000 1 connected
88ecdc0b150b3cbfdc4d791204bfd0d58cabd2ff 192.168.27.129:7003@17003 master - 0 1568640842264 8 connected 0-665 5461-6127 10923-11403 11405-11588
a2d327c3be89c685524fe12e26b8e7227b500e20 192.168.27.129:7000@17000 master - 0 1568640843260 37 connected 6128-10922

模擬客戶端程序,循環插入key和獲取key的值:

@Test
public void redisClusterTest() throws InterruptedException {
        ValueOperations<String,String> operations=redisTemplate.opsForValue();
        int i =0;
        while (true){         
                operations.set("mchf","mchf");        
                System.out.println(operations.get("mchf"));
                i++;
                System.out.println(i);
            }catch (Exception e){
                e.printStackTrace();
            }

            Thread.sleep(2000);
        }

獲取key所在的slot和節點:

192.168.27.128:7000> get mchf
(error) MOVED 11404 192.168.27.128:7001

我們kill掉192.168.27.128:7001這個redis實例,看看客戶端會發生什麼:

3
mchf
4
2019-09-16 21:37:42.393  INFO 3612 --- [xecutorLoop-1-3] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was /192.168.27.128:7001
2019-09-16 21:37:44.400  WARN 3612 --- [ioEventLoop-6-4] i.l.core.protocol.ConnectionWatchdog     : Cannot reconnect: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /192.168.27.128:7001
2019-09-16 21:37:48.693  INFO 3612 --- [xecutorLoop-1-5] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.168.27.128:7001
2019-09-16 21:37:50.698  WARN 3612 --- [ioEventLoop-6-1] i.l.core.protocol.ConnectionWatchdog     : Cannot reconnect: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /192.168.27.128:7001
2019-09-16 21:37:54.992  INFO 3612 --- [xecutorLoop-1-2] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.168.27.128:7001
2019-09-16 21:37:57.000  WARN 3612 --- [ioEventLoop-6-4] i.l.core.protocol.ConnectionWatchdog     : Cannot reconnect: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /192.168.27.128:7001
2019-09-16 21:38:02.091  INFO 3612 --- [xecutorLoop-1-5] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.168.27.128:7001
2019-09-16 21:38:04.096  WARN 3612 --- [ioEventLoop-6-1] i.l.core.protocol.ConnectionWatchdog     : Cannot reconnect: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /192.168.27.128:7001
2019-09-16 21:38:09.292  INFO 3612 --- [xecutorLoop-1-3] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.168.27.128:7001
2019-09-16 21:38:11.298  WARN 3612 --- [ioEventLoop-6-3] i.l.core.protocol.ConnectionWatchdog     : Cannot reconnect: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /192.168.27.128:7001
org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io.lettuce.core.RedisCommandTimeoutException: Command timed out after 30 second(s)

這邊一直嘗試重連,超時拋出異常。這樣當slave提升到master後,客戶端還在嘗試連接原來的master,而原來的master已經被kill掉了,客戶端並沒有去連接新的master。

我們是基於springboot2.0做的開發,而springboot2.0中redis的連接池用的是lettuce,查文檔後發現,需要開啓自適應或定期刷新ClusterTopology,以下爲修改後的代碼:

  @Autowired
    private RedisProperties redisProperties;


    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {

        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(redisProperties.getCluster().getNodes());


        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        //支持自適應集羣拓撲刷新和靜態刷新源
        ClusterTopologyRefreshOptions clusterTopologyRefreshOptions =  ClusterTopologyRefreshOptions.builder()
               //.enablePeriodicRefresh(Duration.ofSeconds(5))
                .enableAllAdaptiveRefreshTriggers()
                .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(10))
                //.enablePeriodicRefresh(Duration.ofSeconds(10))
                .build();

        ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder().timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(30)))  超時修改爲30秒
                //.autoReconnect(false)  是否自動重連
                //.pingBeforeActivateConnection(Boolean.TRUE) 
                //.cancelCommandsOnReconnectFailure(Boolean.TRUE)
                //.disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
                .topologyRefreshOptions(clusterTopologyRefreshOptions).build();

        LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
                .poolConfig(genericObjectPoolConfig)
                //.readFrom(ReadFrom.NEAREST)
                .clientOptions(clusterClientOptions).build();

        return new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
    }


    @Bean
    public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory redisConnectionfactory){
        RedisTemplate<String,String> redisTemplate=new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionfactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

這邊需要注意兩點:

1.默認客戶端重連超時時間爲60秒,這邊修改到了30秒,redis集羣認爲master宕機slave切換到master的默認時間爲15秒,可以在redis.conf中修改

2.啓用adaptive cluster topology view當某個節點無法連接上之後,會生效,並且會和集羣中所有的節點每隔一段時間嘗試建立ESTABLISH連接,時間間隔取決於代碼中的刷新頻率,而啓用periodic cluster topology不管集羣有沒有發生改變,都會每隔一段時間和集羣中所有節點嘗試建立ESTABLISH連接。集羣使用客戶端比較多的時候需要注意這點。

代碼修改後可以從變化後的master拿到key:

前面發生了主從切換所以現在192.168.27.129:7001是主,我們kill掉,查看客戶端變化

192.168.27.128:7000> get mchf
(error) MOVED 11404 192.168.27.129:7001
Caused by: io.lettuce.core.RedisCommandTimeoutException: Command timed out after 30 second(s)
	at io.lettuce.core.ExceptionFactory.createTimeoutException(ExceptionFactory.java:51)
	at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:114)
	at io.lettuce.core.cluster.ClusterFutureSyncInvocationHandler.handleInvocation(ClusterFutureSyncInvocationHandler.java:123)
	at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:80)
	at com.sun.proxy.$Proxy85.set(Unknown Source)
	at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.set(LettuceStringCommands.java:146)
	... 39 more
mchf
5
2019-09-16 22:07:05.372  INFO 13520 --- [xecutorLoop-1-1] i.l.core.protocol.ConnectionWatchdog     : Reconnecting, last destination was 192.168.27.129:7001
mchf
6

當配置自適應或定期刷新cluster topology後,上面兩個問題得以解決。

參考:https://github.com/lettuce-io/lettuce-core/wiki/Redis-Cluster

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章