一文详解SpringBoot 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();
}
创建 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 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 初始化,官方支持以下配置:
RedisClusterConfigurationRedisSentinelConfigurationRedisStaticMasterReplicaConfigurationRedisStandaloneConfiguration
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)其它相关文章!
加载中,请稍侯......
精彩评论