通過閱讀 LettuceConnectionConfiguration 的源碼,我們知道兩種在用戶端代碼中配置 redis-server 模式的方法:
1.配置檔案:可以通過 application.properties 中的屬性來設定哨兵模式,叢集模式,單機模式;
2.Java代碼:可以通過向 Spring 注入Bean的模式來設定哨兵模式(注入 RedisSentinelConfiguration)和叢集模式(注入 RedisClusterConfiguration);
Java代碼優先級 > 配置檔案;
另外,我們還知道如果同時存在多種模式的配置時,最終隻會選取一種模式,此時就要根據優先級來判斷具體選擇哪一種:
哨兵模式 > 叢集模式 > 單機模式
最後,就是 spring.redis.url 這個屬性,對于配置 Lettuce 作為 Redis 用戶端時,沒啥卵用。
一、簡介
當你使用 SpringBoot 架構時,自動裝配的功能很友善,比如你引用 redis 的依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
當你不考慮整合 jedis 時,預設使用的是 lettuce。
簡單說明一下 LettuceConnectionConfiguration 是什麼時候自動裝配的:
在 spring-boot-autoconfigure 有一個 RedisAutoConfiguration:
...
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
...
}
之後會去建立 @Import 指明的 Bean,首先就是 LettuceConnectionConfiguration。
二、構造函數
// 第一次參數來自于 application.properties 中以 spring.redis 為字首的屬性
// 第二個參數來自于我們注入Spring容器的RedisSentinelConfiguration執行個體
// 第三個參數同理,來自于我們注入Spring容器的RedisClusterConfiguration執行個體
LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
}
比較特别的就是參數 ObjectProvider,可以閱讀這篇 Spring ObjectProvider使用說明 了解。
- 如果注入執行個體為空時,使用ObjectProvider則避免了強依賴導緻的依賴對象不存在異常;
- 如果有多個執行個體,ObjectProvider的方法可以根據Bean實作的Ordered接口或@Order注解指定的先後順序擷取一個Bean。
ObjectProvider 為Spring使用者提供了一個更加寬松的依賴注入方式。
三、注入RedisConnectionFactory
// 如果Spring容器中還沒有 RedisConnectionFactory 的執行個體,則向容器中注入LettuceConnectionFactory
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
LettuceConnectionFactory redisConnectionFactory(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
ClientResources clientResources) {
// 擷取 Lettuce 用戶端配置
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources, getProperties().getLettuce().getPool());
return createLettuceConnectionFactory(clientConfig);
}
連接配接工廠執行個體的優先級:
// 從加載優先級來看,代碼設計的邏輯是 哨兵模式 > 叢集模式 > 單機模式
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);
}
比方說,你在 application.properties 同時包含
- spring.redis.host 和 spring.redis.port;
- spring.redis.sentinel.master 和 spring.redis.sentinel.nodes;
- spring.redis.cluster.nodes 和 spring.redis.cluster.maxRedirects;
這樣三組配置同時存在時,最終會采用第二種哨兵模式,而忽略第一種單機模式以及第三種叢集模式對應的配置。
四、getSentinelConfig
如果伺服器為哨兵模式,用戶端對應哨兵模式的配置:
protected final RedisSentinelConfiguration getSentinelConfig() {
// 如果這個不為空,則說明Spring容器中有RedisSentinelConfiguration類型的Bean
// 同時說明,從優先級來看,Java代碼注入的RedisSentinelConfiguration類型的Bean > application.properties 中以 spring.redis.sentinel 為字首的配置
if (this.sentinelConfiguration != null) {
return this.sentinelConfiguration;
}
RedisProperties.Sentinel sentinelProperties = this.properties.getSentinel();
if (sentinelProperties != null) {
RedisSentinelConfiguration config = new RedisSentinelConfiguration();
// 哨兵伺服器可以監控多組 master-slave,這裡指定連接配接其中某組 master-slave 的名字
// 例如,sentinel.conf 中的配置 sentinel monitor mymaster 172.22.0.3 6379 2
// mymaster就是我們需要的值
config.master(sentinelProperties.getMaster());
// 哨兵伺服器的 ip:port 解析成 RedisNode
config.setSentinels(createSentinels(sentinelProperties));
config.setUsername(this.properties.getUsername());
// 如果 redis-server 配置了 requirepass 屬性,則用戶端需要提供密碼
if (this.properties.getPassword() != null) {
config.setPassword(RedisPassword.of(this.properties.getPassword()));
}
// 如果 redis-sentinel 配置了 requirepass 屬性,則用戶端需要提供密碼
if (sentinelProperties.getPassword() != null) {
config.setSentinelPassword(RedisPassword.of(sentinelProperties.getPassword()));
}
config.setDatabase(this.properties.getDatabase());
return config;
}
return null;
}
五、getClusterConfiguration
如果伺服器為叢集模式,用戶端對應叢集模式的配置:
protected final RedisClusterConfiguration getClusterConfiguration() {
// 如果這個不為空,則說明Spring容器中有RedisClusterConfiguration類型的Bean
// 同時說明,從優先級來看,Java代碼注入的RedisClusterConfiguration類型的Bean > application.properties 中以 spring.redis.cluster 字首的配置
if (this.clusterConfiguration != null) {
return this.clusterConfiguration;
}
if (this.properties.getCluster() == null) {
return null;
}
RedisProperties.Cluster clusterProperties = this.properties.getCluster();
// Redis 叢集節點配置,形式為 ip:port,多個節點之間用逗号分隔
RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes());
if (clusterProperties.getMaxRedirects() != null) {
config.setMaxRedirects(clusterProperties.getMaxRedirects());
}
config.setUsername(this.properties.getUsername());
// Redis 叢集節點設定了密碼,則用戶端需要提供密碼
if (this.properties.getPassword() != null) {
config.setPassword(RedisPassword.of(this.properties.getPassword()));
}
return config;
}
六、parseUrl
這個 parseUrl 其實是屬于 LettuceConnectionConfiguration 的父類 RedisConnectionConfiguration:
// 合法的參數,形如字元串 redis://user:[email protected]:6379
protected ConnectionInfo parseUrl(String url) {
try {
URI uri = new URI(url);
// 協定名,以 redis:// 或者 rediss:// 開頭
String scheme = uri.getScheme();
if (!"redis".equals(scheme) && !"rediss".equals(scheme)) {
throw new RedisUrlSyntaxException(url);
}
如果是 rediss ,則表示使用 SSL 安全協定
boolean useSsl = ("rediss".equals(scheme));
String username = null;
String password = null;
// 指的是url中雙斜杠之後,@之前的内容
if (uri.getUserInfo() != null) {
String candidate = uri.getUserInfo();
int index = candidate.indexOf(':');
if (index >= 0) {
// 如果是 username:pwd 的形式
username = candidate.substring(0, index);
password = candidate.substring(index + 1);
}
else {
// 如果是 username 的形式
password = candidate;
}
}
return new ConnectionInfo(uri, useSsl, username, password);
}
catch (URISyntaxException ex) {
throw new RedisUrlSyntaxException(url, ex);
}
}
我不過,因為接下來的這段代碼,是以我個人判定 spring.redis.url 用處并不大,隻能影響 useSsl 這一個屬性:
private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
// 解析出來的 username,password 完全沒用上
if (connectionInfo.isUseSsl()) {
builder.useSsl();
}
}
七、總結
- 配置檔案:可以通過 application.properties 中的屬性來設定哨兵模式,叢集模式,單機模式;
- Java代碼:可以通過向 Spring 注入Bean的模式來設定哨兵模式(注入 RedisSentinelConfiguration)和叢集模式(注入 RedisClusterConfiguration);