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

 更新时间:2024年11月17日 09:33:01   作者: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);
}
  • 哨兵模式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多数据源配置的资料请关注脚本之家其它相关文章!

相关文章

  • java显示目录文件列表和删除目录功能

    java显示目录文件列表和删除目录功能

    这篇文章主要介绍了java显示目录文件列表和删除目录功能,文章通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2017-12-12
  • SpringBoot整合Thymeleaf与FreeMarker视图层技术

    SpringBoot整合Thymeleaf与FreeMarker视图层技术

    在目前的企业级应用开发中,前后端分离是趋势,但是视图层技术还占有一席之地。Spring Boot 对视图层技术提供了很好的支持,福安防推荐使用的模板引擎是Thymeleaf,不过想FreeMarker也支持,JSP技术在这里并不推荐使用
    2022-08-08
  • Java中public关键字用法详细讲解

    Java中public关键字用法详细讲解

    这篇文章主要给大家介绍了关于Java中public关键字用法的相关资料,public关键字是和访问权限相关的,它所修饰的方法对所有类都是可以访问的,需要的朋友可以参考下
    2023-09-09
  • Java反射机制原理、Class获取方式以及应用场景详解

    Java反射机制原理、Class获取方式以及应用场景详解

    反射机制是JAVA的核心知识点之一,大多数框架的实现原理就是利用了反射机制,掌握反射机制会使你学习框架更加轻松高效,这篇文章主要给大家介绍了关于Java反射机制原理、Class获取方式以及应用场景的相关资料,需要的朋友可以参考下
    2022-04-04
  • java之TreeUtils生成一切对象树形结构案例

    java之TreeUtils生成一切对象树形结构案例

    这篇文章主要介绍了java之TreeUtils生成一切对象树形结构案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • SpringCloud Gateway使用详解

    SpringCloud Gateway使用详解

    Spring Cloud Gateway是一个基于Spring Boot 2.x和Spring WebFlux的API网关,可以帮助我们构建微服务架构中的统一入口。感兴趣的同学可以参考一下
    2023-04-04
  • java如何实现判断文件的真实类型

    java如何实现判断文件的真实类型

    本篇文章主要介绍了java如何实现判断文件的真实类型,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • SpringBoot配置数据库密码加密的方法

    SpringBoot配置数据库密码加密的方法

    由于系统安全的考虑,配置文件中不能出现明文密码的问题,本文就给大家详细介绍下springboot配置数据库密码加密的方法,下面话不多说了,来一起看看详细的介绍吧,需要的朋友可以参考下
    2023-08-08
  • spring boot 开发soap webservice的实现代码

    spring boot 开发soap webservice的实现代码

    这篇文章主要介绍了spring boot 开发soap webservice的实现代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-01-01
  • JAVA后端应该学什么技术

    JAVA后端应该学什么技术

    这篇文章主要介绍了JAVA后端应该学什么技术,对JAVA感兴趣的同学,可以规划一下
    2021-04-04

最新评论