• Spring Boot集成redis集群拓扑动态刷新


    项目场景:

    Spring Boot集成Redis集群,使用lettuce连接Cluster集群实例。


    问题描述

    redis其中一个节点挂了之后,springboot集成redis集群配置信息没有及时刷新,出现读取操作报错。

    1. java.lang.IllegalArgumentException: Connection to 127.0.0.1:6379 not allowed. This connection point is not known in the cluster view
    2. exceptionStackTrace
    3. io.lettuce.core.cluster.PooledClusterConnectionProvider.getConnectionAsync(PooledClusterConnectionProvider.java:359)
    4. io.lettuce.core.cluster.ClusterDistributionChannelWriter.write(ClusterDistributionChannelWriter.java:93)
    5. io.lettuce.core.cluster.ClusterCommand.complete(ClusterCommand.java:56)
    6. io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:563)
    7. io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:516)

    原因分析:

    lettuce默认是没有开始拓扑更新及读写分离导致的


    解决方案:

    这里分为几种情况:

    1. springboot 1.x之前版本默认使用jedis,无需要手动开启刷新
    2. springboot 2.x默认为Lettuce,需要代码设置开启刷新节点拓扑策略
    3. springboot 2.3.0开始,支持集群拓扑刷新功能,属性配置开启即可

    第一种情况:springboot1.x版本环境

    springboot1.x之前版本默认使用jedis,无需手动开启动态刷新。

    第二种情况:springboot2.0~2.3版本环境

    springboot2.0-2.3版本默认使用lettuce,默认不支持属性配置集群拓扑刷新。使用lettuce,需要增加配置类,需要手动开启刷新。

     配置类如下:

    1. package com.test.config;
    2. import com.fasterxml.jackson.annotation.JsonAutoDetect;
    3. import com.fasterxml.jackson.annotation.PropertyAccessor;
    4. import com.fasterxml.jackson.databind.ObjectMapper;
    5. import io.lettuce.core.cluster.ClusterClientOptions;
    6. import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
    7. import lombok.extern.java.Log;
    8. import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
    9. import org.springframework.beans.factory.annotation.Autowired;
    10. import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
    11. import org.springframework.cache.annotation.EnableCaching;
    12. import org.springframework.cache.interceptor.KeyGenerator;
    13. import org.springframework.context.annotation.Bean;
    14. import org.springframework.context.annotation.Configuration;
    15. import org.springframework.data.redis.connection.RedisClusterConfiguration;
    16. import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
    17. import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
    18. import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
    19. import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
    20. import org.springframework.data.redis.core.RedisTemplate;
    21. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    22. import org.springframework.data.redis.serializer.RedisSerializer;
    23. import org.springframework.data.redis.serializer.StringRedisSerializer;
    24. import java.lang.reflect.Method;
    25. import java.time.Duration;
    26. @Log
    27. @Configuration
    28. @EnableCaching // 开启缓存支持
    29. public class RedisConfig {
    30. @Autowired
    31. private RedisProperties redisProperties;
    32. @Bean(destroyMethod = "destroy") //销毁这个bean之前调用这个destroy回调方法释放资源
    33. public LettuceConnectionFactory redisConnectionFactory() {
    34. // redis单节点
    35. if (null == redisProperties.getCluster() || null == redisProperties.getCluster().getNodes()) {
    36. RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(redisProperties.getHost(),
    37. redisProperties.getPort());
    38. configuration.setPassword(redisProperties.getPassword()); //RedisPassword.of(redisProperties.getPassword())
    39. configuration.setDatabase(redisProperties.getDatabase());
    40. return new LettuceConnectionFactory(configuration);
    41. }
    42. // redis集群
    43. RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(redisProperties.getCluster().getNodes());
    44. redisClusterConfiguration.setPassword(redisProperties.getPassword()); //RedisPassword.of(redisProperties.getPassword())
    45. redisClusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
    46. GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
    47. genericObjectPoolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive());
    48. genericObjectPoolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle());
    49. genericObjectPoolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle());
    50. genericObjectPoolConfig.setMaxWaitMillis(redisProperties.getLettuce().getPool().getMaxWait().getSeconds());
    51. // 支持自适应集群拓扑刷新和动态刷新源
    52. ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
    53. .enableAllAdaptiveRefreshTriggers()
    54. // 开启自适应刷新
    55. .enableAdaptiveRefreshTrigger()
    56. // 开启定时刷新
    57. .enablePeriodicRefresh(Duration.ofSeconds(5))
    58. .build();
    59. ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
    60. .topologyRefreshOptions(clusterTopologyRefreshOptions).build();
    61. LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
    62. .poolConfig(genericObjectPoolConfig) //如果使用默认配置可以注释genericObjectPoolConfig
    63. // .readFrom(ReadFrom.SLAVE_PREFERRED) //读写分离:主写从读模式配置
    64. .clientOptions(clusterClientOptions).build();
    65. LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
    66. lettuceConnectionFactory.setShareNativeConnection(false);// 是否允许多个线程操作共用同一个缓存连接,默认 true,false 时每个操作都将开辟新的连接
    67. lettuceConnectionFactory.resetConnection();// 重置底层共享连接, 在接下来的访问时初始化
    68. return lettuceConnectionFactory;
    69. }
    70. /**
    71. * RedisTemplate配置
    72. */
    73. @Bean
    74. public RedisTemplate redisTemplate(LettuceConnectionFactory factory) {
    75. RedisTemplate redisTemplate = new RedisTemplate<>();
    76. redisTemplate.setConnectionFactory(factory);
    77. // 使用注解@Bean返回RedisTemplate的时候,同时配置hashkey和hashValue的序列虎方式
    78. // key采用String的序列化方式
    79. redisTemplate.setKeySerializer(keySerializer());
    80. // value使用jackson序列化方式
    81. redisTemplate.setValueSerializer(valueSerializer());
    82. // hash的key采用String的序列化方式
    83. redisTemplate.setHashKeySerializer(keySerializer());
    84. // hash的value使用jackson序列化方式
    85. redisTemplate.setHashValueSerializer(valueSerializer());
    86. /**必须执行这个函数,初始化RedisTemplate*/
    87. // 需要先调用afterPropertiesSet方法,此方法是应该是初始化参数和初始化工作。
    88. redisTemplate.afterPropertiesSet();
    89. log.info("序列化完成!");
    90. return redisTemplate;
    91. }
    92. @Bean
    93. public KeyGenerator keyGenerator() {
    94. return new KeyGenerator() {
    95. @Override
    96. public Object generate(Object target, Method method, Object... params) {
    97. StringBuffer sb = new StringBuffer();
    98. sb.append(target.getClass().getName());
    99. sb.append(method.getName());
    100. for (Object obj : params) {
    101. sb.append(obj.toString());
    102. }
    103. return sb.toString();
    104. }
    105. };
    106. }
    107. /**
    108. * key键序列化方式
    109. *
    110. * @return RedisSerializer
    111. */
    112. private RedisSerializer keySerializer() {
    113. return new StringRedisSerializer();
    114. }
    115. /**
    116. * value值序列化方式
    117. *
    118. * @return
    119. */
    120. private Jackson2JsonRedisSerializer valueSerializer() {
    121. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    122. ObjectMapper om = new ObjectMapper();
    123. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    124. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    125. jackson2JsonRedisSerializer.setObjectMapper(om);
    126. return jackson2JsonRedisSerializer;
    127. }
    128. }

    配置文件:

    1. #Redis Configuration
    2. spring.redis.cluster.max-redirects=10
    3. spring.redis.cluster.nodes=127.0.0.1:8001,127.0.0.1:8002
    4. spring.redis.timeout=60000ms
    5. spring.redis.password=
    6. spring.redis.lettuce.pool.max-active=10
    7. spring.redis.lettuce.pool.max-idle=8
    8. spring.redis.lettuce.pool.min-idle=0
    9. spring.redis.lettuce.pool.max-wait=-1ms

     注意:注入LettuceConnectionFactory后,一定要记得注入RedisTemplate,并 redisTemplate.setConnectionFactory(factory);

    apache commons-pool2 包提供了一个通用的对象池技术的实现。可以很方便的基于它来实现自己的对象池,比如 DBCP 和 Jedis 他们的内部对象池的实现就是依赖于 commons-pool2 。

    1. org.apache.commons
    2. commons-pool2
    3. 2.8.0

    第三种情况:springboot2.3之后版本环境

    springboot2.3之后版本默认使用lettuce,默认支持属性配置开启集群拓扑刷新,其解决方案:属性配置开启即可。

    1. spring.redis.lettuce.cluster.refresh.adaptive= true
    2. spring.redis.lettuce.cluster.refresh.period=30000 # 30秒自动刷新一次

     

    关联文章:Spring Boot集成Redis集群报错UnsupportedOperationException-CSDN博客

  • 相关阅读:
    小程序源码:社群微群人脉系统-多玩法安装简单
    【Linux】升级GCC(版本9.3),补充:binutils
    mybatis的test坑(不等于‘‘ 且 不等于0)
    DevOps | 产研协同效能提升之评审、审批流、质量卡点
    CRM 报告:跟踪销售业绩的强大工具
    (32)测距仪(声纳、激光雷达、深度摄影机)
    【pytorch】1.6 tensor 计算
    基于华为云 IoT 物联网平台实现家居环境实时监控
    有什么好用的IT资产管理软件
    golang 多环境配置切换
  • 原文地址:https://blog.csdn.net/u011974797/article/details/133352525