一文详解SpringBoot Redis多数据源配置
作者:Lvan
问题背景
在实际项目中,我们需要支持两种 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) { 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(); }
创建 LettuceClientConfigurationBuilder:createBuilder
方法生成 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 LettuceConnectionFactory(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(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 集群初始化
/** * 获取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多数据源配置的资料请关注脚本之家其它相关文章!