java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot Redis多数据源配置

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

作者:Lvan

Spring Boot默认只允许一种 Redis 连接池配置,且配置受限于 Lettuce 包,不够灵活,所以本文将为大家介绍如何自定义Redis配置方案实现多数据源支持,需要的可以参考下

问题背景

在实际项目中,我们需要支持两种 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();
}

创建 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 LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
    }
    if (getClusterConfiguration() != null) {
        return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
    }
    return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}

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 初始化:

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

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多数据源配置的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文