RedisAutoConfiguration、LettuceConnectionConfiguration源码解析

一、pom.xml依赖,本文基于2.5.1版本进行源码分析

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>

给一个常用的maven依赖查找官网地址:
Maven Repository: Search/Browse/Explore (mvnrepository.com)

二、springboot启动自动装配:springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

/**
 * {@link EnableAutoConfiguration Auto-configuration} for Spring Data's Redis support.*/
@Configuration(proxyBeanMethods = false) 配置Lite轻量模式启动,多实例bean
@ConditionalOnClass(RedisOperations.class) 简单理解Spring工程中引用了该类的包 才会构建这个bean
@EnableConfigurationProperties(RedisProperties.class) redis配置文件加载
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) 2种客户端连接 在SpringBoot2.0之后默认Lettuce客户端连接Redis服务器
public class RedisAutoConfiguration {
   .......
}
/**
 * Configuration properties for Redis.
 *
 */
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {....}

其中类部分属性如下:redis配置类中还详细讲了集群、池、哨兵模式、客户端等内容的配置,这里不一一列举了

 

 

其他注解简单理解:

@ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
@ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean)
@ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
@ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
@ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
@ConditionalOnNotWebApplication(不是web应用)

 

Jedis和Lettuce的区别
jedis和Lettuce都是Redis的客户端,它们都可以连接Redis服务器,但是在SpringBoot2.0之后默认都是使用的Lettuce这个客户端连接Redis服务器。因为当使用Jedis客户端连接Redis服务器的时候,每个线程都要拿自己创建的Jedis实例去连接Redis客户端,当有很多个线程的时候,不仅开销大需要反复的创建关闭一个Jedis连接,而且也是线程不安全的,一个线程通过Jedis实例更改Redis服务器中的数据之后会影响另一个线程;

但是如果使用Lettuce这个客户端连接Redis服务器的时候,就不会出现上面的情况,Lettuce底层使用的是Netty,当有多个线程都需要连接Redis服务器的时候,可以保证只创建一个Lettuce连接,使所有的线程共享这一个Lettuce连接,这样可以减少创建关闭一个Lettuce连接时候的开销;而且这种方式也是线程安全的,不会出现一个线程通过Lettuce更改Redis服务器中的数据之后而影响另一个线程的情况;

springboot版本大于2.0,所以我们项目中看到jedis客户端连接是报红的,需要单独的引入jedis包才能使用jedis客户端连接redis服务器,这里我们就主要分析下lettuce方式连接服务器。

三、LettuceConnectionConfiguration源码解析

1、构造函数:


// 参数1来自于 application.properties 中以 spring.redis 为前缀的属性
// 参数2来自于我们注入Spring容器的RedisSentinelConfiguration实例
// 参数3来自于我们注入Spring容器的RedisClusterConfiguration实例
LettuceConnectionConfiguration(RedisProperties properties,
            ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
            ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
        super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
    }

针对objectprovider参数可以阅读下这边文章:

(37条消息) Spring ObjectProvider使用说明_OkidoGreen的博客-CSDN博客

什么时候使用ObjectProvider接口?

如果待注入参数的Bean为空或有多个时,便是ObjectProvider发挥作用的时候了。

  • 如果注入实例为空时,使用ObjectProvider则避免了强依赖导致的依赖对象不存在异常;
  • 如果有多个实例,ObjectProvider的方法会根据Bean实现的Ordered接口或@Order注解指定的先后顺序获取一个Bean。从而了提供了一个更加宽松的依赖注入方式。

2、LettuceConnectionFactory实例注入容器

    @Bean
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    LettuceConnectionFactory redisConnectionFactory(
            ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
            ClientResources clientResources) {
// 获取客户端client配置 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); }

这段源码可以很明显的知道当3种配置同时存在时候,实际中只会采用一种模式创建工厂连接实例

3、获取客户端配置

    private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
        ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
        if (connectionInfo.isUseSsl()) {
            builder.useSsl(); // 解析出来用户 密码基本在这里没有用到
        }
    }
// 合法的参数,形如字符串  redis://user:password@example.com: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) { // redis用户名不是必须传入的
        // 如果是 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);
  }
}

 

4-1 如果服务器为哨兵模式,客户端对应哨兵模式的配置:

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;
}

4-2 如果服务器为集群模式,客户端对应集群模式的配置:

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;
}

 

5、探究LettuceConnectionFactory

有参构造器:单机、哨兵、集群

 

posted @ 2022-05-14 18:14  chch213  阅读(1462)  评论(0编辑  收藏  举报