SpringBoot 2.2.9连接Redis7.0.8集群报RedisConnectionException求助
问题现象
本地CentOS7虚拟机部署Redis 7.0.8集群,SpringBoot 2.2.9.RELEASE连接时持续报错;单机Redis可正常连接,虚拟机内集群操作正常,主机与虚拟机网络互通且防火墙未开启。
错误日志
2023-03-01 20:09:31.523 ERROR 32280 --- [nio-9913-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.RedisConnectionFailureException: Redis connection failed; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to [RedisURI [host='192.168.129.XXX', port=7010], RedisURI [host='192.168.129.XXX', port=7020], RedisURI [host='192.168.129.XXX', port=7030], RedisURI [host='192.168.129.XXX', port=7011], RedisURI [host='192.168.129.XXX', port=7021], RedisURI [host='192.168.129.XXX', port=7031]]] with root cause java.lang.UnsupportedOperationException: null at java.util.AbstractList.add(AbstractList.java:148) ~[na:1.8.0_151-1-redhat] at java.util.AbstractList.add(AbstractList.java:108) ~[na:1.8.0_151-1-redhat] at io.lettuce.core.output.ArrayOutput.set(ArrayOutput.java:54) ~[lettuce-core-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.lettuce.core.protocol.RedisStateMachine.safeSet(RedisStateMachine.java:358) ~[lettuce-core-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.lettuce.core.protocol.RedisStateMachine.decode(RedisStateMachine.java:139) ~[lettuce-core-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:716) ~[lettuce-core-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.lettuce.core.protocol.CommandHandler.decode0(CommandHandler.java:680) ~[lettuce-core-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:675) ~[lettuce-core-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:596) ~[lettuce-core-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:565) ~[lettuce-core-5.2.2.RELEASE.jar:5.2.2.RELEASE] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.51.Final.jar:4.1.51.Final] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_151-1-redhat]
环境验证
虚拟机内Redis集群可正常操作:
[root@localhost ~]# redis-cli -c -p 7010 127.0.0.1:7010> get boo -> Redirected to slot [13142] located at 192.168.129.XXX:7030 "for" 192.168.129.XXX:7030> set hello world -> Redirected to slot [866] located at 192.168.129.XXX:7010 OK 192.168.129.XXX:7010> get hello "world" 192.168.129.XXX:7010>
当前配置
SpringBoot配置类
@Component @ConfigurationProperties(prefix = "spring.redis.cluster") public class ClusterConfigurationProperties { List<String> nodes; public List<String> getNodes() { return nodes; } public void setNodes(List<String> nodes) { this.nodes = nodes; } } @Configuration public class RedisClusterConfig { @Autowired ClusterConfigurationProperties clusterProperties; @Bean public RedisConnectionFactory connectionFactory() { return new LettuceConnectionFactory( new RedisClusterConfiguration(clusterProperties.getNodes())); } @Bean public RedisTemplate<String, byte[]> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, byte[]> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } }
application.properties
spring.redis.cluster.nodes[0]=192.168.129.XXX:7010 spring.redis.cluster.nodes[1]=192.168.129.XXX:7020 spring.redis.cluster.nodes[2]=192.168.129.XXX:7030 spring.redis.cluster.nodes[3]=192.168.129.XXX:7011 spring.redis.cluster.nodes[4]=192.168.129.XXX:7021 spring.redis.cluster.nodes[5]=192.168.129.XXX:7031 spring.redis.password= spring.redis.cluster.max-redirects=3 spring.redis.timeout=60000ms spring.redis.lettuce.pool.max-active=32 spring.redis.lettuce.pool.max-wait=-1ms spring.redis.lettuce.pool.max-idle=16 spring.redis.lettuce.pool.min-idle=8
Redis集群节点配置(redis.conf)
port 7010 cluster-enabled yes cluster-config-file nodes-7010.conf cluster-node-timeout 5000 appendonly yes daemonize yes #requirepass redis-123456 loglevel warning logfile /root/Tools/redis-cluster/7010/redis_7010.log #bind 0.0.0.0 protected-mode no
解决方案
原因分析
报错栈显示java.lang.UnsupportedOperationException发生在AbstractList.add方法,核心问题是Lettuce版本与Redis 7.0.8不兼容:Redis 7.x新增了集群相关的响应格式,而当前使用的Lettuce 5.2.2.RELEASE在解析这些响应时,会尝试向不可修改的列表添加元素,导致抛出异常。
修复步骤
升级Lettuce版本
将Lettuce版本升级至5.3.7.RELEASE及以上(该版本已修复Redis 7.x集群的兼容性问题):- Maven项目在
pom.xml中添加依赖覆盖:<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.3.7.RELEASE</version> </dependency> - Gradle项目添加:
implementation 'io.lettuce:lettuce-core:5.3.7.RELEASE'
- Maven项目在
验证集群节点对外暴露IP
检查Redis集群节点生成的配置文件,确保节点对外暴露的是虚拟机IP而非127.0.0.1:cat /root/Tools/redis-cluster/7010/nodes-7010.conf | grep "myself"如果显示为127.0.0.1,需停止集群,修改每个节点的
redis.conf添加bind 192.168.129.XXX 127.0.0.1,然后重新创建集群:redis-cli --cluster create 192.168.129.XXX:7010 192.168.129.XXX:7020 192.168.129.XXX:7030 192.168.129.XXX:7011 192.168.129.XXX:7021 192.168.129.XXX:7031 --cluster-replicas 1简化配置(可选)
无需自定义配置类,直接使用SpringBoot自动配置:
修改application.properties节点配置为逗号分隔格式:spring.redis.cluster.nodes=192.168.129.XXX:7010,192.168.129.XXX:7020,192.168.129.XXX:7030,192.168.129.XXX:7011,192.168.129.XXX:7021,192.168.129.XXX:7031同时删除自定义的
ClusterConfigurationProperties和RedisClusterConfig,SpringBoot会自动创建适配的连接工厂。
验证
重启SpringBoot应用,测试Redis集群操作,确认连接正常。
内容的提问来源于stack exchange,提问作者Neil660

