开发者

一文详解SpringBoot Redis多数据源配置

开发者 https://www.devze.com 2025-04-12 11:42 出处:网络 作者: Lvan
目录问题背景源码分析LettuceConnectionConfiguration 的核心配置客户端配置与初始化解析Redis 多模式支持Redis 多数据源配置思路Redis 多数据源配置实战复用 LettuceClientConfiguration 配置复用 Redis 集群初始化
目录
  • 问题背景
  • 源码分析
    • LettuceConnectionConfiguration 的核心配置
    • 客户端配置与初始化解析
    • Redis 多模式支持
  • Redis 多数据源配置思路
    • Redis 多数据源配置实战
      • 复用 LettuceClientConfiguration 配置
      • 复用 Redis 集群初始化
      • 自定义 Redis 主从配置
      • 注册 LettuceConnectionFactory
      • 应用
    • 最后

      问题背景

      在实际项目中,我们需要支持两种 Redis 部署方式(集群和主从),但 Spring Boot 默认只允许一种 Redis 连接池配置,且配置受限于 Lettuce 包,不够灵活。为了解决这个问题,我深入研究了 Spring Boot 的源码,自定义了 Redis 配置方案,实现了多数据源支持。这一调整让我们可以在同一项目中灵活适配不同的 Redis 部署方式,解决了配置的局限性,让系统更具扩展性和灵活性。

      源码分析

      LettuceConnectionConfiguration 的核心配置

      LettuceConnectionConfiguration 位于 org.springframework.boot.autoconfigure.data.redis 包内,使其作用域被限制,无法直接扩展。为支持集群和主从 Redis 部署,我们需要通过自定义配置,绕过该限制。

      LettuceConnectionFactory 是 Redis 连接的核心工厂,依赖于 DefaultClientResources,并通过 LettuceClientConfiguration 设置诸如连接池、超时时间等基础参数。配置如下:

      class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
      	@Bean(destroyMethod = "shutdown")
      	@ConditionalOnMissingBean(ClientResources.class)
      	DefaultClientResources lettuceClientResources(ObjectProvider<ClientResourcesBuilderCustomizer> customizers) {
      		DefaultClientResources.Builder builder = DefaultClientResources.builder();
      		customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
      		return builder.build();
      	}
      
      	@Bean
      	@ConditionalOnMissingBean(RedisConnectionFactory.class)
      	LettuceConnectionFactory redisConnectionFactory(
      			ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
      			ClientResources clientResources) {
      		LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
      				getProperties().getLettuce().getPool());
      		return createLettuceConnectionFactory(clientConfig);
      	}
      }
      

      lettuceClientResources 方法定义了 ClientResources,作为单例供所有 Redis 连接工厂复用。因此,自定义 LettuceConnectionFactory 时可以直接使用这个共享的 ClientResources

      客户端配置与初始化解析

      LettuceClientConfiguration 的获取getLettuceClientConfiguration 方法用以构建 Lettuce 客户端配置,应用基本参数并支持连接池:

      private LettuceClientConfiguration getLettuceClientConfiguration(
             ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,  
             ClientResources clientResources, Pool pool) {
          LettuceClientConfigurationBuilde编程客栈r builder = createBuilder(pool);
          applyProperties(builder);
          if (StringUtils.hasText(getProperties().getUrl())) {  
             customizjavascripteConfigurationFromUrl(builder);  
          }
          builder.clientOptions(createClientOptions());
          builder.clientResources(clientResources);
          builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
          return builder.build();
      }
      

      创建 LettuceClientConfigurationBuildercreateBuilder 方法生成 LettuceClientConfigurationBuilder,并判断是否启用连接池。若启用,PoolBuilderFactory 会创建包含连接池的配置,该连接池通过 GenericObjectPoolConfig 构建。

      private static final boolean COMMONS_POOL2_AVAILABLE = ClassUtils.isPresent("org.apache.commons.pool2.ObjectPool", 
             RedisConnectionConfiguration.class.getClassLoader());
             
      private LettuceClientConfigurationBuilder createBuilder(Pool pool) {  
          if (isPoolEnabled(pool)) {  
             return new PoolBuilderFactory().createBuilder(pool);  
          }  
          return LettuceClientConfiguration.builder();  
      }
      
      protected boolean isPoolEnabled(Pool pool) {  
          Boolean enabled = pool.getEnabled();  
          return (enabled != null) ? enabled : COMMONS_POOL2_AVAILABLE;  
      }
      
      private static class PoolBuilderFactory {  
        
          LettuceClientConfigurationBuilder createBuilder(Pool properties) {  
             return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(properties));  
          }  
        
          private GenericObjectPoolConfig<?> getPoolConfig(Pool properties) {  
             GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();  
             config.setMaxTotal(properties.getMaxActive());  
             config.setMaxIdle(properties.getMaxIdle());  
             config.setMinIdle(properties.getMinIdle());  
             if (properties.getTimeBetweenEvictionRuns() != null) {  
                config.setTimeBetweenEvictionRuns(properties.getTimeBetweenEvictionRuns());  
             }  
             if (properties.getMaxWait() != null) {  
                config.setMaxWait(properties.getMaxWait());  
             }  
             return config;  
          }   
      }
      

      参数应用与超时配置applyProperties 方法用于配置 Redis 的基础属性,如 SSL、超时时间等。

      private LettuceClientConfigurationBuilder applyProperties(
          LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
          if (getProperties().isSsl()) {
              builder.useSsl();
          }
          if (getProperties().getTimeout() != null) {
              builder.commandTimeout(getProperties().getTimeout());
          }
          if (getProperties().getLettuce() != null) {
              RedisProperties.Lettuce lettuce = getProperties().getLettuce();
              if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
                  builder.shutdownTimeout(getProperties().getLettuce().getShutdownTimeout());
              }
          }
          if (StringUtils.hasText(getProperties().getClientName())) {
              builder.clientName(getProperties().getClientName());
          }
          return builder;
      }
      

      Redis 多模式支持

      在创建 LettuceConnectionFactory 时,根据不同配置模式(哨兵、集群或单节点)构建连接工厂:

      private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
          if (getSentinelConfig() != null) {
              return new LettuceConnectionpythonFactory(getSentinelConfig(), clientConfiguration);
          }
          if (getClusterConfiguration() != null) {
              return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
          }
          return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
      }
      
      • 哨兵模式getSentinelConfig() 返回哨兵配置实例,实现高可用 Redis。
      • 集群模式:若启用集群,getClusterConfiguration() 返回集群配置以支持分布式 Redis。
      • 单节点模式:默认单节点配置,返回 StandaloneConfig

      getClusterConfiguration 的方法实现:

      protected final RedisClusterConfiguration getClusterConfiguration() {
          if (this.clusterConfiguration != null) {
              return this.clusterConfiguration;
          }
          if (this.properties.getCluster() == null) {
              return null;
          }
          RedisProperties.Cluster clusterProperties = this.properties.getCluster();
          RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes());
          if (clusterProperties.getMaxRedirects() != null) {
              config.setMaxRedirects(clusterProperties.getMaxRedirects());
          }
          config.setUsername(this.properties.getUsername());
          if (this.properties.getPassword() != null) {
              config.setPassword(RedisPassword.of(this.properties.getPassword()));
          }
          return config;
      }
      
      • 集群节点与重定向:配置集群节点信息及最大重定向次数。
      • 用户名与密码:集群连接的身份验证配置。

      Redis 多数据源配置思路

      通过以上的源码分析,我梳理出了 LettuceConnectionFactory 构建的流程:

      1.LettuceClientConfiguration 初始化:

      • 连接池初始化
      • Redis 基础属性配置
      • 设置 ClientResource

      2.RedisConfiguration 初始化,官方支持以下配置:

      • RedisClusterConfiguration
      • RedisSentinelConfiguration
      • RedisStaticMasterReplicaConfiguration
      • RedisStandaloneConfiguration

      Redis 多数据源配置实战

      复用 LettuceClientConfiguration 配置

      /**
       * 这里与源码有个不同的是返回了builder,因为后续实现的主从模式的lettuce客户端仍有自定义的配置,返回了builder则相对灵活一点。
       */
      private LettuceClientConfiguration.LettuceClientConfigurationBuilder getLettuceClientConfiguration(ClientResources clientResources, RedisProperties.Pool pool) {  
          LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = createBuilder(pool);  
          applyProperties(builder);  
          builder.clientOptions(createClientOptions());  
          builder.clientResources(clientResources);  
          return builder;  
      }  
        
      /**  
       * 创建Lettuce客户端配置构建器  
       */  
      private LettuceClientConfiguration.LettuceClientConfigurationBuilder createBuilder(RedisProperties.Pool pool) {  
          if (isPoolEnabled(pool)) {  
              return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(pool));  
          }  
          return LettuceClientConfiguration.builder();  
      }  
        
      /**  
       * 判断Redis连接池是否启用  
       */  
      private boolean isPoolEnabled(RedisProperties.Pool pool) {  
          Boolean enabled = pool.getEnabled();  
          return (enabled != null) ? enabled : COMMONS_POOL2_AVAILABLE;  
      }  
        
      /**  
       * 根据Redis属性配置创建并返回一个通用对象池配置  
       */  
      private GenericObjectPoolConfig<?> getPoolConfig(RedisProperties.Pool properties) {  
          GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();  
          config.setMaxTotal(properties.getMaxActive());  
          config.setMaxIdle(yZVZHGaproperties.getMaxIdle());  
          config.setMinIdle(properties.getMinIdle());  
          if (properties.getTimeBetweenEvictionRuns() != null) {  
              config.setTimeBetweenEvictionRuns(properties.getTimeBetweenEvictionRuns());  
          }  
          if (properties.getMaxWait() != null) {  
              config.setMaxWait(properties.getMaxWait());  
          }  
          return config;  
      }  
        
      /**  
       * 根据Redis属性配置构建Lettuce客户端配置  
       *  
       * @param builder Lettuce客户端配置的构建器  
       * @return 返回配置完毕的Lettuce客户端配置构建器  
       */  
      private LettuceClientConfiguration.LettuceClientConfigurationBuilder applyProperties(  
              LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {  
          if (redisProperties.isSsl()) {  
              builder.useSsl();  
          }  
          if (redisProperties.getTimeout() != null) {  
              builder.commandTimeout(redisProperties.getTimeout());  
          }  
          if (redisProperties.getLettuce() != null) {  
              RedisProperties.Lettuce lettuce = redisProperties.getLettuce();  
              if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {  
                  builder.shutdownTimeout(redisProperties.getLettuce().getShutdownTimeout());  
              }  
          }  
          if (StringUtils.hasText(redisProperties.getClientName())) {  
              builder.clientName(redisProperties.getClientName());  
          }  
          return builder;  
      }  
        
      /**  
       * 创建客户端配置选项  
       */  
      private ClientOptions createClientOptions() {  
          ClientOptions.Builder builder = initializeClientOptionsBuilder();  
          Duration connectTimeout = redisProperties.getConnectTimeout();  
          if (connectTimeout != null) {  
              builder.socketOptions(SocketOptions.builder().connectTimeout(connectTimeout).build());  
          }  
          return builder.timeoutOptions(TimeoutOptions.enabled()).build();  
      }  
        
      /**  
       * 初始化ClientOptions构建器  
       */  
      private ClientOptions.Buiwww.devze.comlder initializeClientOptionsBuilder() {  
          if (redisProperties.getCluster() != null) {  
              ClusterClientOptions.Builder builder = ClusterClientOptions.builder();  
              RedisProperties.Lettuce.Cluster.Refresh refreshProperties = redisProperties.getLettuce().getCluster().getRefresh();  
              ClusterTopologyRefreshOptions.Builder refreshBuilder = ClusterTopologyRefreshOptions.builder()  
                      .dynamicRefreshSources(refreshProperties.isDynamicRefreshSources());  
              if (refreshProperties.getPeriod() != null) {  
                  refreshBuilder.enablePeriodicRefresh(refreshProperties.getPeriod());  
              }  
              if (refreshProperties.isAdaptive()) {  
                  refreshBuilder.enableAllAdaptiveRefreshTriggers();  
              }  
              return builder.topologyRefreshOptions(refreshBuilder.build());  
          }  
          return ClientOptions.builder();  
      }
      

      复用 Redis 集群初始化

      /**  
       * 获取Redis集群配置  
       */  
      private RedisClusterConfiguration getClusterConfiguration() {  
          if (redisProperties.getCluster() == null) {  
              return null;  
          }  
          RedisProperties.Cluster clusterProperties = redisProperties.getCluster();  
          RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes());  
          if (clusterProperties.getMaxRedirects() != null) {  
              config.setMaxRedirects(clusterProperties.getMaxRedirects());  
          }  
          config.setUsername(redisProperties.getUsername());  
          if (redisProperties.getPassword() != null) {  
              config.setPassword(RedisPassword.of(redisProperties.getPassword()));  
          }  
          return config;  
      }
      

      自定义 Redis 主从配置

      @Getter  
      @Setter  
      @ConfigurationProperties(prefix = "spring.redis")  
      public class RedisPropertiesExtend {  
        
          private MasterReplica masterReplica;  
        
          @Getter  
          @Setter    public static class MasterReplica {  
        
              private String masterNodes;  
              private List<String> replicaNodes;  
          }  
      }
      
      /**  
       * 获取主从配置  
       */  
      private RedisStaticMasterReplicaConfiguration getStaticMasterReplicaConfiguration() {  
          if (redisPropertiesExtend.getMasterReplica() == null) {  
              return null;  
          }  
          RedisPropertiesExtend.MasterReplica masterReplica = redisPropertiesExtend.getMasterReplica();  
          List<String> masterNodes = CharSequenceUtil.split(masterReplica.getMasterNodes(), StrPool.COLON);  
          RedisStaticMasterReplicaConfiguration config = new RedisStaticMasterReplicaConfiguration(  
                  masterNodes.get(0), Integer.parseInt(masterNodes.get(1)));  
          for (String replicaNode : masterReplica.getReplicaNodes()) {  
              List<String> replicaNodes = CharSequenceUtil.split(replicaNode, StrPool.COLON);  
              config.addNode(replicaNodes.get(0), Integer.parseInt(replicaNodes.get(1)));  
          }  
          config.setUsername(redisProperties.getUsername());  
          if (redisProperties.getPassword() != null) {  
              config.setPassword(RedisPassword.of(redisProperties.getPassword()));  
          }  
          return config;  
      }
      

      注册 LettuceConnectionFactory

      @Primary  
      @Bean(name = "redisClusterConnectionFactory")  
      @ConditionalOnMissingBean(name = "redisClusterConnectionFactory")  
      public LettuceConnectionFactory redisClusterConnectionFactory(ClientResources clientResources) {  
          LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientResources,  
                  redisProperties.getLettuce().getPool())  
                  .build();  
          return new LettuceConnectionFactory(getClusterConfiguration(), clientConfig);  
      }  
        
      @Bean(name = "redisMasterReplicaConnectionFactory")  
      @ConditionalOnMissingBean(name = "redisMasterReplicaConnectionFactory")  
      public LettuceConnectionFactory redisMasterReplicaConnectionFactory(ClientResources clientResources) {  
          LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientResources,  
                  redisProperties.getLettuce().getPool())  
                  .readFrom(ReadFrom.REPLICA_PREFERRED)  
                  .build();  
          return new LettuceConnectionFactory(getStaticMasterReplicaConfiguration(), clientConfig);  
      }
      

      应用

      @Bean  
      @ConditionalOnMissingBean(name = "redisTemplate")  
      public RedisTemplate<Object, Object> redisTemplate(@Qualifier("redisClusterConnectionFactory") RedisConnectionFactory redisConnectionFactory) {  
          RedisTemplate<Object, Object> template = new RedisTemplate<>();  
          template.setConnectionFactory(redisConnectionFactory);  
          return template;  
      }
      
      @Bean  
      @ConditionalOnMissingBean(name = "masterReplicaRedisTemplate")  
      public RedisTemplate<Object, Object> masterReplicaRedisTemplate(@Qualifier("redisMasterReplicaConnectionFactory") RedisConnectionFactory redisConnectionFactory) {  
          RedisTemplate<Object, Object> template = new RedisTemplate<>();  
          template.setConnectionFactory(redisConnectionFactory);  
          return template;  
      }
      

      最后

      深入理解 Spring Boot Redis 自动配置源码是实现灵活多数据源支持的关键。通过源码分析,我们能够在保持框架兼容性的同时,精准定制和扩展功能,从而满足复杂的业务需求。

      以上就是一文详解SpringBoot Redis多数据源配置的详细内容,更多关于SpringBoot Redis多数据源配置的资料请关注编程客栈(www.devze.com)其它相关文章!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号