停止redis集群清除數據后重啟無法自動重連問題解決方法
問題重現步驟
1、停止redis集群中的每個節點
用命令停止逐個節點太麻煩了,寫了個shell腳本,shutdown.sh (-a 123456 是redis集群的連接密碼)
redis-cli -p 7001 -a 123456 shutdown redis-cli -p 7002 -a 123456 shutdown redis-cli -p 7003 -a 123456 shutdown redis-cli -p 7004 -a 123456 shutdown redis-cli -p 7005 -a 123456 shutdown redis-cli -p 7006 -a 123456 shutdown
執行命令 ./shutdown.sh 停止redis服務
2、清除每個節點產生的數據文件(nodes.conf appendonly.aof dump.rdb )
執行腳本./moveRedis.sh 清除數據文件
moveRedis.sh內容為
cd redis7001 rm -rf nodes.conf appendonly.aof dump.rdb cd .. cd redis7002 rm -rf nodes.conf appendonly.aof dump.rdb cd .. cd redis7003 rm -rf nodes.conf appendonly.aof dump.rdb cd .. cd redis7004 rm -rf nodes.conf appendonly.aof dump.rdb cd .. cd redis7005 rm -rf nodes.conf appendonly.aof dump.rdb cd .. cd redis7006 rm -rf nodes.conf appendonly.aof dump.rdb
3、啟動redis服務
執行 ./startall.sh 逐個啟動redis服務
startall.sh腳本內容為
cd redis7001 ./redis-server redis.conf cd .. cd redis7002 ./redis-server redis.conf cd .. cd redis7003 ./redis-server redis.conf cd .. cd redis7004 ./redis-server redis.conf cd .. cd redis7005 ./redis-server redis.conf cd .. cd redis7006 ./redis-server redis.conf
4、創建集群
進入redis-trib.rb所在目錄執行如下命令創建集群
./redis-trib.rb create --replicas 1 192.169.1.71:7001 192.169.1.71:7002 192.169.1.71:7003 192.169.1.71:7004 192.169.1.71:7005 192.169.1.71:7006
程序報錯
以上重現過程程序都是啟動着的,redis集群服務起來后redisson並沒有自動重連,查看日志發現有如下報錯
org.redisson.client.RedisException: MOVED redirection loop detected. Node //192.169.2.238:9511 has further redirect to //192.169.2.238:9511 at org.redisson.command.CommandAsyncService.checkAttemptFuture(CommandAsyncService.java:865) at org.redisson.command.CommandAsyncService$10.operationComplete(CommandAsyncService.java:673) at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:511) at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:504) at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:483) at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:424) at io.netty.util.concurrent.DefaultPromise.tryFailure(DefaultPromise.java:121) at org.redisson.misc.RedissonPromise.tryFailure(RedissonPromise.java:108) at org.redisson.client.protocol.CommandData.tryFailure(CommandData.java:78) at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:313) at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:128) at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:108) at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:647) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:582) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:499) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:461) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.lang.Thread.run(Thread.java:748)
解決方案
最開始懷疑是redisson版本的原因,原來是redisson-3.8.2,將版本提高至3.11.1依舊有這個問題
然后猜測是不是和redis版本有關,將redis版本由3.2.12升至4.0.14后,問題依然存在
既然和外部無關,那就只能從程序上優化了
在使用到RedissonClient的方法中捕獲異常,一旦出現異常,重新獲取一次RedissonClient
需要注意的是
手動調用加了@Bean注解的方法無效,需要再寫一個用於手動調用的獲取RedissonClient的方法
完整代碼如下
package com.xiaonian.middleware.redis; import com.xiaonian.util.StrUtil; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; /** * Redisson管理類 * 連接redis2(緩存redis) * @author xiaonian * @date 2019/6/26 10:41 * @version v2.0.0 */ @Configuration @Component public class RedissonManager { /**redis集群節點*/ @Value(value = "${spring.redis.cluster.nodes:#{null}}") private String cluster; /**redis密碼*/ @Value("${spring.redis.password:#{null}}") private String password; @Value("${spring.redis.host:#{null}}") /**redis單機節點主機*/ private String host; /**redis單機節點端口*/ @Value("${spring.redis.host:#{null}}") private String port; /**最大連接數*/ @Value("${redisson.pool.max.active:30}") private int MaxPoolSize; @Bean public RedissonClient getRedisson() { return loadRedisson(); } public RedissonClient loadRedisson(){ RedissonClient redisson = null; Config config = new Config(); //單節點 if(!StrUtil.isEmpty(host)){ config.useSingleServer(). setAddress("redis://"+host+":"+port) .setPassword(password) .setConnectionPoolSize(MaxPoolSize) //最小空閑連接 .setConnectionMinimumIdleSize(0); redisson = Redisson.create(config); }else{ //集群節點 String[] nodes = cluster.split(","); //redisson版本是3.5,集群的ip前面要加上“redis://”,不然會報錯,3.2版本可不加 for(int i=0;i<nodes.length;i++){ nodes[i] = "redis://"+nodes[i]; } //這是用的集群server config.useClusterServers() //設置集群狀態掃描時間2000 .setScanInterval(2000) .addNodeAddress(nodes) .setPassword(password) .setMasterConnectionPoolSize(MaxPoolSize) //最小空閑連接 .setMasterConnectionMinimumIdleSize(0); redisson = Redisson.create(config); // System.out.println(config.); //可通過打印redisson.getConfig().toJSON().toString()來檢測是否配置成功 } return redisson; } public RedissonClient retryGetRedisson() { return loadRedisson(); } }