解锁SpringBoot高级技能:Redis多数据源配置全攻略

问题背景

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

源码分析

LettuceConnectionConfiguration 的核心配置

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

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

 

java

代码解读

复制代码

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

客户端配置与初始化解析

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

java

代码解读

复制代码

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

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

java

代码解读

复制代码

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; } }

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

java

代码解读

复制代码

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 时,根据不同配置模式(哨兵、集群或单节点)构建连接工厂:

 

java

代码解读

复制代码

private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) { if (getSentinelConfig() != null) { return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration); } if (getClusterConfiguration() != null) { return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration); } return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration); }

  1. 哨兵模式getSentinelConfig() 返回哨兵配置实例,实现高可用 Redis。
  2. 集群模式:若启用集群,getClusterConfiguration() 返回集群配置以支持分布式 Redis。
  3. 单节点模式:默认单节点配置,返回 StandaloneConfig

getClusterConfiguration 的方法实现:

 

java

代码解读

复制代码

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 初始化:
    1. 连接池初始化
    2. Redis 基础属性配置
    3. 设置 ClientResource
  2. RedisConfiguration 初始化,官方支持以下配置:
    1. RedisClusterConfiguration
    2. RedisSentinelConfiguration
    3. RedisStaticMasterReplicaConfiguration
    4. RedisStandaloneConfiguration

Redis 多数据源配置实战

复用 LettuceClientConfiguration 配置

 

java

代码解读

复制代码

/** * 这里与源码有个不同的是返回了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(properties.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.Builder 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 集群初始化

 

java

代码解读

复制代码

/** * 获取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 主从配置

 

java

代码解读

复制代码

@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

 

java

代码解读

复制代码

@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); }

应用

 

java

代码解读

复制代码

@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 自动配置源码是实现灵活多数据源支持的关键。通过源码分析,我们能够在保持框架兼容性的同时,精准定制和扩展功能,从而满足复杂的业务需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值