天天看點

LettuceConnectionConfiguration源碼解析

通過閱讀 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 同時包含

  1. spring.redis.host 和 spring.redis.port;
  2. spring.redis.sentinel.master 和 spring.redis.sentinel.nodes;
  3. 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();
  }
}
           

七、總結

  1. 配置檔案:可以通過 application.properties 中的屬性來設定哨兵模式,叢集模式,單機模式;
  2. Java代碼:可以通過向 Spring 注入Bean的模式來設定哨兵模式(注入 RedisSentinelConfiguration)和叢集模式(注入 RedisClusterConfiguration);

繼續閱讀