一文詳解SpringBoot?Redis多數(shù)據(jù)源配置
問(wèn)題背景
在實(shí)際項(xiàng)目中,我們需要支持兩種 Redis 部署方式(集群和主從),但 Spring Boot 默認(rèn)只允許一種 Redis 連接池配置,且配置受限于 Lettuce 包,不夠靈活。為了解決這個(gè)問(wèn)題,我深入研究了 Spring Boot 的源碼,自定義了 Redis 配置方案,實(shí)現(xiàn)了多數(shù)據(jù)源支持。這一調(diào)整讓我們可以在同一項(xiàng)目中靈活適配不同的 Redis 部署方式,解決了配置的局限性,讓系統(tǒng)更具擴(kuò)展性和靈活性。
源碼分析
LettuceConnectionConfiguration 的核心配置
LettuceConnectionConfiguration
位于 org.springframework.boot.autoconfigure.data.redis
包內(nèi),使其作用域被限制,無(wú)法直接擴(kuò)展。為支持集群和主從 Redis 部署,我們需要通過(guò)自定義配置,繞過(guò)該限制。
LettuceConnectionFactory
是 Redis 連接的核心工廠(chǎng),依賴(lài)于 DefaultClientResources
,并通過(guò) LettuceClientConfiguration
設(shè)置諸如連接池、超時(shí)時(shí)間等基礎(chǔ)參數(shù)。配置如下:
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 連接工廠(chǎng)復(fù)用。因此,自定義 LettuceConnectionFactory
時(shí)可以直接使用這個(gè)共享的 ClientResources
。
客戶(hù)端配置與初始化解析
LettuceClientConfiguration 的獲取:getLettuceClientConfiguration
方法用以構(gòu)建 Lettuce 客戶(hù)端配置,應(yīng)用基本參數(shù)并支持連接池:
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(); }
創(chuàng)建 LettuceClientConfigurationBuilder:createBuilder
方法生成 LettuceClientConfigurationBuilder
,并判斷是否啟用連接池。若啟用,PoolBuilderFactory
會(huì)創(chuàng)建包含連接池的配置,該連接池通過(guò) GenericObjectPoolConfig
構(gòu)建。
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; } }
參數(shù)應(yīng)用與超時(shí)配置:applyProperties
方法用于配置 Redis 的基礎(chǔ)屬性,如 SSL、超時(shí)時(shí)間等。
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 多模式支持
在創(chuàng)建 LettuceConnectionFactory
時(shí),根據(jù)不同配置模式(哨兵、集群或單節(jié)點(diǎn))構(gòu)建連接工廠(chǎng):
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()
返回哨兵配置實(shí)例,實(shí)現(xiàn)高可用 Redis。 - 集群模式:若啟用集群,
getClusterConfiguration()
返回集群配置以支持分布式 Redis。 - 單節(jié)點(diǎn)模式:默認(rèn)單節(jié)點(diǎn)配置,返回
StandaloneConfig
。
getClusterConfiguration
的方法實(shí)現(xiàn):
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; }
- 集群節(jié)點(diǎn)與重定向:配置集群節(jié)點(diǎn)信息及最大重定向次數(shù)。
- 用戶(hù)名與密碼:集群連接的身份驗(yàn)證配置。
Redis 多數(shù)據(jù)源配置思路
通過(guò)以上的源碼分析,我梳理出了 LettuceConnectionFactory
構(gòu)建的流程:
1.LettuceClientConfiguration 初始化:
- 連接池初始化
- Redis 基礎(chǔ)屬性配置
- 設(shè)置
ClientResource
2.RedisConfiguration 初始化,官方支持以下配置:
RedisClusterConfiguration
RedisSentinelConfiguration
RedisStaticMasterReplicaConfiguration
RedisStandaloneConfiguration
Redis 多數(shù)據(jù)源配置實(shí)戰(zhàn)
復(fù)用 LettuceClientConfiguration 配置
/** * 這里與源碼有個(gè)不同的是返回了builder,因?yàn)楹罄m(xù)實(shí)現(xiàn)的主從模式的lettuce客戶(hù)端仍有自定義的配置,返回了builder則相對(duì)靈活一點(diǎn)。 */ private LettuceClientConfiguration.LettuceClientConfigurationBuilder getLettuceClientConfiguration(ClientResources clientResources, RedisProperties.Pool pool) { LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = createBuilder(pool); applyProperties(builder); builder.clientOptions(createClientOptions()); builder.clientResources(clientResources); return builder; } /** * 創(chuàng)建Lettuce客戶(hù)端配置構(gòu)建器 */ 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; } /** * 根據(jù)Redis屬性配置創(chuàng)建并返回一個(gè)通用對(duì)象池配置 */ 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; } /** * 根據(jù)Redis屬性配置構(gòu)建Lettuce客戶(hù)端配置 * * @param builder Lettuce客戶(hù)端配置的構(gòu)建器 * @return 返回配置完畢的Lettuce客戶(hù)端配置構(gòu)建器 */ 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; } /** * 創(chuàng)建客戶(hù)端配置選項(xiàng) */ 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構(gòu)建器 */ 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(); }
復(fù)用 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; }
注冊(cè) 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); }
應(yīng)用
@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 自動(dòng)配置源碼是實(shí)現(xiàn)靈活多數(shù)據(jù)源支持的關(guān)鍵。通過(guò)源碼分析,我們能夠在保持框架兼容性的同時(shí),精準(zhǔn)定制和擴(kuò)展功能,從而滿(mǎn)足復(fù)雜的業(yè)務(wù)需求。
以上就是一文詳解SpringBoot Redis多數(shù)據(jù)源配置的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Redis多數(shù)據(jù)源配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
idea上提交項(xiàng)目到gitee 最后出現(xiàn) Push rejected的問(wèn)題處理方法
這篇文章主要介紹了idea上面提交項(xiàng)目到gitee 最后出現(xiàn) Push rejected的問(wèn)題處理方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09詳解基于Spring Cloud幾行配置完成單點(diǎn)登錄開(kāi)發(fā)
這篇文章主要介紹了詳解基于Spring Cloud幾行配置完成單點(diǎn)登錄開(kāi)發(fā),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02Spring通過(guò)Java配置集成Tomcat的方法
這篇文章主要介紹了Spring通過(guò)Java配置集成Tomcat的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04spring mvc 使用kaptcha配置生成驗(yàn)證碼實(shí)例
本篇文章主要介紹了spring mvc 使用kaptcha生成驗(yàn)證碼實(shí)例,詳細(xì)的介紹了使用Kaptcha 生成驗(yàn)證碼的步驟,有興趣的可以了解一下2017-04-04MyBatis中mapper.java和mapper.xml的關(guān)系說(shuō)明
這篇文章主要介紹了MyBatis中mapper.java和mapper.xml的關(guān)系說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05spring cloud內(nèi)容匯總(各個(gè)功能模塊、啟動(dòng)、部署)的詳細(xì)過(guò)程
Spring Cloud 是一套基于 Spring Boot 的框架集合,用于構(gòu)建分布式微服務(wù)架構(gòu),文章介紹了Spring Cloud框架及其相關(guān)工具的使用,還詳細(xì)介紹了如何配置和使用這些功能,包括配置文件、依賴(lài)管理以及實(shí)際應(yīng)用示例,感興趣的朋友一起看看吧2024-11-11Spring Cloud Gateway 如何修改HTTP響應(yīng)信息
這篇文章主要介紹了Spring Cloud Gateway 修改HTTP響應(yīng)信息的方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07SpringSecurity拋出異常但AccessDeniedHandler不生效的解決
本文主要介紹了SpringSecurity拋出異常但AccessDeniedHandler不生效的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-01-01Springboot通過(guò)Scheduled實(shí)現(xiàn)定時(shí)任務(wù)代碼
這篇文章主要介紹了Springboot通過(guò)Scheduled實(shí)現(xiàn)定時(shí)任務(wù)代碼,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11