首页 > 热点 > 正文

[Redis]Spring Boot中Redis Template集群配置

来源:腾讯云 2023-03-16 19:21:38

1、问题背景

在一个Spring boot项目中,需要使用redis作为缓存,于是将使用spring-boot-starter-data-redis,具体依赖如下:

org.springframework.bootspring-boot-starter-data-redis2.0.4.RELEASE

在测试环境中,功能测试,压力测试,都没有发现问题,原因是测试环境中redis自行搭建,没有设置密码,但是上线后,Redis使用的是A***的Pass服务的集群,并设置密码,使用过程中发现如下问题:


(资料图片仅供参考)

redis负载高;redis异常,错误信息: com.lambdaworks.redis.RedisException: java.lang.IllegalArgumentException: Connection to XXX.XX.XXX.XXX:15000 not allowed. This connection point is not known in the cluster viewjava.lang.IllegalArgumentException: Connection to XXX.XX.XXX.XXX:15000 not allowed. This connection point is not known in the cluster viewConnection to XXX.XX.XXX.XXX:15000 not allowed. This connection point is not known in the cluster view

2、问题分析+解决方法

2.1、redis负载过高问题

2.1.1、问题原因

原本打算看一下是否是代码逻辑问题导致redis负载过高,于是登录redis服务器使用monitor命令观察命令执行的频率,发现每执行一次命令都执行一次Auth password命令,说明连接池未正确使用导致执行一次命令创建一次连接,导致负载高 ,并且代码执行效率低 。

2.1.2、解决方法

然后对比了使用JedisCluster的项目没有此类问题,因此怀疑是spring-boot-starter-data-redis的RedisTemplate的问题,查看源码后发现spring-data-redis的驱动包在某个版本之后替换为 Lettuce,在启用集群后jedis的连接池无效。错误配置如下:

# 错误配置# Redis配置spring.redis.cluster.nodes=127.0.0.1:6379### 连接超时时间(毫秒)spring.redis.timeout=60000spring.redis.password=xxxxxxx# 连接池最大连接数(使用负值表示没有限制)spring.redis.jedis.pool.max-active=8##连接池最大阻塞等待时间,若使用负值表示没有限制spring.redis.jedis.pool.max-wait=-1##连接池中的最大空闲连接spring.redis.jedis.pool.max-idle=8# 连接池中的最小空闲连接spring.redis.jedis.pool.min-idle=0

需要改成正确的配置,修改之后无此现象,具体配置如下:

单机版:

# 单机版# Redis配置spring.redis.host=127.0.0.1spring.redis.port=6379### 连接超时时间(毫秒)spring.redis.timeout=60000spring.redis.password=xxxxxxx# 连接池最大连接数(使用负值表示没有限制)spring.redis.jedis.pool.max-active=8##连接池最大阻塞等待时间,若使用负值表示没有限制spring.redis.jedis.pool.max-wait=-1##连接池中的最大空闲连接spring.redis.jedis.pool.max-idle=8# 连接池中的最小空闲连接spring.redis.jedis.pool.min-idle=0

集群版:

#集群版 # Redis配置spring.redis.cluster.nodes=127.0.0.1:6379### 连接超时时间(毫秒)spring.redis.timeout=60000spring.redis.password=xxxxxxx# 连接池最大连接数(使用负值表示没有限制)spring.redis.lettuce.pool.max-active=8##连接池最大阻塞等待时间,若使用负值表示没有限制spring.redis.lettuce.pool.max-wait=-1##连接池中的最大空闲连接spring.redis.lettuce.pool.max-idle=8# 连接池中的最小空闲连接spring.redis.lettuce.pool.min-idle=0

注意:启用集群版,需要额外添加如下依赖

org.apache.commonscommons-pool22.8.0

2.2、redis异常 Connection to XXX.XX.XXX.XXX:15000 not allowed 问题

2.2.1、问题原因

网上搜索了一下,发现项目github上已有此问题的反馈以及解决办法github.com/lettuce-io/…,原因是由于Lettuce其中有个配置项validateClusterNodeMembership 默认是true导致;

2.2.2、解决办法

由于spring boot未能直接通过配置文件直接修改此配置,因此需要自定义Redis配置,具体代码如下: MylettuceConnectionFactory.java

package com.quison.test.config;import io.lettuce.core.AbstractRedisClient;import io.lettuce.core.cluster.ClusterClientOptions;import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;import io.lettuce.core.cluster.RedisClusterClient;import org.springframework.beans.DirectFieldAccessor;import org.springframework.data.redis.connection.RedisClusterConfiguration;import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;import java.util.concurrent.TimeUnit;public class MyLettuceConnectionFactory extends LettuceConnectionFactory {    public MyLettuceConnectionFactory() { }    public MyLettuceConnectionFactory(RedisClusterConfiguration redisClusterConfiguration, LettuceClientConfiguration lettuceClientConfiguration) {        super(redisClusterConfiguration, lettuceClientConfiguration);    }    @Override    public void afterPropertiesSet() {        super.afterPropertiesSet();        DirectFieldAccessor accessor = new DirectFieldAccessor(this);        AbstractRedisClient client = (AbstractRedisClient) accessor.getPropertyValue("client");        if(client instanceof RedisClusterClient){            RedisClusterClient clusterClient = (RedisClusterClient) client;            ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()                    .enablePeriodicRefresh(10, TimeUnit.MINUTES)                    .enableAllAdaptiveRefreshTriggers()                    .build();            ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()                     // 注意此配置项设置为false                    .validateClusterNodeMembership(false)                    .topologyRefreshOptions(topologyRefreshOptions)                    .build();            clusterClient.setOptions(clusterClientOptions);        }    }}

由于配置后,连接池也需要自行设置,因此Redis的配置文件修改为如下设置 RedisConfig.java

package com.quison.test.config;import org.apache.commons.pool2.impl.GenericObjectPoolConfig;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.autoconfigure.data.redis.RedisProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.data.redis.connection.*;import org.springframework.data.redis.connection.lettuce.DefaultLettucePool;import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;import java.util.Arrays;import java.util.HashSet;import java.util.Set;@Configurationpublic class RedisConfig {    @Value("${spring.redis.cluster.nodes}")    private String clusterNodes;    @Value("${spring.redis.password}")    private String password;    @Value("${spring.redis.lettuce.pool.max-idle}")    private Integer maxIdle;    @Value("${spring.redis.lettuce.pool.max-active}")    private Integer maxActive;    @Value("${spring.redis.cluster.max-redirects}")    private Integer maxRedirects;    @Bean    public RedisConnectionFactory myRedisConnectionFactory() {        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();        String[] serverArray = clusterNodes.split(",");        Set nodes = new HashSet();        for (String ipPort : serverArray) {            String[] ipAndPort = ipPort.split(":");            nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.valueOf(ipAndPort[1])));        }        redisClusterConfiguration.setPassword(RedisPassword.of(password));        redisClusterConfiguration.setClusterNodes(nodes);        redisClusterConfiguration.setMaxRedirects(maxRedirects);        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();        genericObjectPoolConfig.setMaxIdle(maxIdle);        genericObjectPoolConfig.setMinIdle(8);        genericObjectPoolConfig.setMaxTotal(maxActive);        genericObjectPoolConfig.setMaxWaitMillis(10000);        LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()                .commandTimeout(Duration.ofMillis(10000))                .poolConfig(genericObjectPoolConfig)                .build();        return new MyLettuceConnectionFactory(redisClusterConfiguration, clientConfig);    }    /**     * redis模板,存储关键字是字符串,值是Jdk序列化     *     * @param myRedisConnectionFactory     * @return     * @Description:     */    @Bean    @ConditionalOnMissingBean(name = "redisTemplate")    @Primary    public RedisTemplate redisTemplate(RedisConnectionFactory myRedisConnectionFactory) {        RedisTemplate redisTemplate = new RedisTemplate<>();        redisTemplate.setConnectionFactory(myRedisConnectionFactory);        //key序列化方式;但是如果方法上有Long等非String类型的话,会报类型转换错误;        RedisSerializer redisSerializer = new StringRedisSerializer();        redisTemplate.setKeySerializer(redisSerializer);        redisTemplate.setHashKeySerializer(redisSerializer);        //默认使用JdkSerializationRedisSerializer序列化方式;会出现乱码,改成StringRedisSerializer        StringRedisSerializer stringSerializer = new StringRedisSerializer();        redisTemplate.setKeySerializer(stringSerializer);        redisTemplate.setValueSerializer(stringSerializer);        redisTemplate.setHashKeySerializer(stringSerializer);        redisTemplate.setHashValueSerializer(stringSerializer);        return redisTemplate;    }}

3、总结

吃一堑、长一智,总结如下:

开发+测试环境尽量与线上一致,可提前发现问题;使用新技术需要多多测试再投入生产使用;
x
推荐阅读

[Redis]Spring Boot中Redis Template集群配置

2023-03-16 19:21:38

全球要闻:2023年郑州市十八里河镇卫生院6老年人免费体检通知

2023-03-16 17:10:43

全球观点:奔赴“职场”的飞行之旅

2023-03-16 15:19:35

2023济宁各县市区供暖延长最新消息

2023-03-16 13:01:02

砂之塔全集解说_砂之塔

2023-03-16 11:08:19

持续拓展对口协作广度深度

2023-03-16 09:18:43

全球热推荐:赴一场“美丽中国”的人文之旅

2023-03-16 06:47:13

【世界热闻】鉴于银行业动荡 欧洲央行本周料仅加息25个基点

2023-03-16 01:53:25

2022年宝马在华交付79.19万辆,同比减少6.4%

2023-03-15 21:55:02

54届广州国际采购车展时间(2023)_世界时讯

2023-03-15 19:09:37
相关新闻

今日热门!国际能源署:随着俄罗斯开采更多原油,石油市场将过剩

2023-03-15 17:05:44

京剧猫第二季全集12宗宗主_京剧猫12宗宗主排名-焦点热讯

2023-03-15 14:52:43

【环球热闻】骑士卡是什么意思_骑士卡是什么

2023-03-15 12:59:28

焦点关注:今日20公司公布年报 5家业绩增幅翻倍

2023-03-15 10:19:12

嘴角烂是什么原因造成的_嘴角裂口是什么原因缺什么维生素

2023-03-15 08:41:24

全球新动态:十二的含义是什么意思_十二代表的含义

2023-03-15 05:00:26

槿汐是什么意思_金夕是什么意思

2023-03-15 01:01:35

环球观天下!微创介入治疗脑血栓_微创介入治疗

2023-03-14 20:53:36

北大方正人寿北京分公司:识别四种诈骗风险,拥抱清朗校园生活

2023-03-14 18:02:29

卓创资讯:库存连续四周下降 结构钢市场“金三”需求启动明显-天天观点

2023-03-14 15:52:09

世界短讯!保育员工作总结稿 保育员工作总结

2023-03-14 13:52:40

汇金股份3月14日盘中涨幅达5%|全球快播

2023-03-14 11:11:05

盖亚装甲-全球今日报

2023-03-14 09:48:14

大金帕蒂能空调遥控器说明书?

2023-03-14 07:04:56

环球百事通!安徽省弘伟建设项目管理有限公司

2023-03-14 00:54:51

粽子叶煮多长时间好用_粽子叶煮多久 世界热文

2023-03-13 21:01:42

公主肛裂是什么意思_肛裂是什么意思

2023-03-13 17:54:42

2023年涵江区开展“文明健康 绿色环保”主题垃圾分类宣传活动_世界今日讯

2023-03-13 15:57:57

世界微资讯!思考乐教育午盘涨超12% 暂现三连阳

2023-03-13 13:24:59

世界视讯!张家界市武陵源区:“妈妈工厂”有温度

2023-03-13 10:49:17