springboot集成redis实现原理分析

前言

  1. 我们知道,在 springboot 1.5.x版本的默认的Redis客户端是 Jedis实现的,需要导入jedis依赖,springboot 2.x版本中默认客户端是用 lettuce实现的,需要导入spring-boot-starter-data-redis依赖。这两种方式使用的都是 TCP协议.
  2. Jedis使用直连方式连接Redis Server,在多线程环境下存在线程安全问题, 因此需要增加连接池来解决线程安全的问题,同时可以限制redis客户端的数量, 但这种直连方式基于传统I/O模式,是阻塞式传输.
  3. 而 Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,底层基于netty通信,我们知道netty是基于NIO的非阻塞通信, 天生支持高并发,因此在在多线程环境下不存在线程安全问题,一个连接实例就可以满足多线程环境下的并发访问, 当然实例不够的情况下也可以按需增加实例,保证伸缩性.
  4. 下面我们通过源码的方式解析springboot是如何通过lettuce方式连接redis server的,以及springboot操作redis的底层原理.

Springboot操作Redis流程图如下:

springboot集成redis实现原理分析_第1张图片

1. 导入依赖

 	<parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.2.10.RELEASE</version>
 	</parent>

 	<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-redis</artifactId>
	</dependency>
  • 因为我们要研究的是lettuce方式, 因此我们需要保证我们的环境是springboot 2.0以上版本
  • 因为我们导入的是spring-boot-starter-data-redis,通过依赖关系,我们知道它有个子依赖lettuce-core, 看到这里我们也能想到是通过lettuce方式连接的,但我们需要通过源码来证实

2. 源码分析

  • 在进行源码分析的时候,说个题外话, 我之前写过一篇博客是关于springboot启动源码的,在那篇博客里我提了个问题,就是通过对springboot自动装配的源码分析,那么假如随便给我们一个springboot-start的依赖, 我们如何找到源码入口, 这次我们分析的是redis源码,又该如何找到源码入口呢, 这个问题留给读者思考, 这次我会直接进入源码入口.
  • 我通过对springboot集成redis的源码研究, 我认为整个源码可以分成3部分解析, 其中集成netty的源码我就不解析了,我曾经写过几篇关于netty原理的博客, 可以看我之前的博客. 下面我就分成3步, 一步一步为读者解析整个源码实现.

3. 第一部分: 集成redis是如何实现自动装配的

  • 我们知道springboot在集成redis的时候,只需要在yml文件中加入下面这些配置, 我们就可以手动注入操作redis的对象
 spring:
	 redis:
	    host: 123.60.33.114
	    password: xCzLlC
	    port: 6399
  • 这肯定是自动装配的功能, 才让我们只写配置就可以酸爽的操作redis, 因此我们先分析springboot集成redis是如何完成自动装配的, 以及究竟装配了什么.springboot集成redis的自动装配类是RedisAutoConfiguration.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

我们可以看到RedisAutoConfiguration上有四个注解, 下面对这4个注解进行逐个分析.

  • @Configuration(proxyBeanMethods = false)

proxyBeanMethods属性的默认值是true。

true

说明

如果为true,则是Full模式。被@Bean标识的方法会做如下处理:

  1. 都会被CGLIB进行代理
  2. 会走bean的生命周期中的一些行为(比如:@PostConstruct、@Destroy等 Spring中提供的生命周期)
  3. 无论调用几次,得到的都是同一个bean

优点

可以支持通过常规Java调用相同类的@Bean方法而保证是容器内的Bean,这有效规避了在“Lite模式”下操作时难以跟踪的细微错误。

缺点

  1. 启动时会给该配置类生成一个CGLIB子类放进容器(代理),会增加启动时间。 Spring
    Boot有很多配置类,这个开销是不容忽视的。这也是Spring 5.2新增了proxyBeanMethods属性的原因
  2. 因为被代理了,所以@Bean方法 不可以是private、不可以是final

false

说明

如果为true,则是Lite模式。被@Bean标识的方法会做如下处理:

  1. 不会被拦截进行CGLIB代理
  2. 不会走bean的生命周期中的一些行为(比如:@PostConstruct、@Destroy等 Spring中提供的生命周期)
  3. 如果同一个Configuration中调用@Bean标识的方法,就只是普通方法的执行而已,不会从容器中获取对象。
    @Bean标识的返回值对象还是会放入到容器中的,从容器中获取bean还是可以是单例的,会走生命周期。

优点

  1. 启动时不需要给配置类生成CGLIB子类,减少了启动时间
  2. 可以将配置类当作一个普通类使用:也就是说@Bean方法 可以是private、可以是final

缺点

  1. 不能声明@Bean之间的依赖(也就是说不能通过方法调用来依赖其它Bean)
  2. 解决方案:把依赖Bean放进方法入参里

如何选用true/false

如果配置类中的@Bean标识的方法之间不存在依赖调用的话,可以设置为false,可以避免拦截方法进行代理操作,提升性能。

  • @ConditionalOnClass(RedisOperations.class)

我们知道Springboot衍生了一系列的@Conditional注解, 而@ConditionalOnClass表示在RedisOperations类型存在的时候启用, 因为我们导入了spring-boot-starter-data-redis中包含了RedisOperations类型, 因此条件满足启用配置类.

  • @EnableConfigurationProperties(RedisProperties.class)

@EnableConfigurationProperties注解的作用是:让使用 @ConfigurationProperties 注解的类生效。 这里是让RedisProperties生效, 而RedisProperties类中属性就是接收yml文件里的配置

  • @Import({ LettuceConnectionConfiguration.class,
    JedisConnectionConfiguration.class })

@Import注解是向容器中注入bean, 首先注入的是LettuceConnectionConfiguration类型

protected RedisConnectionConfiguration(RedisProperties properties,
		ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
		ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
	this.properties = properties;
	this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
	this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
}

在构造器中将RedisProperties对象用properties属性接收.因为当前对象会被spring容器当做bean, 因此下面的源码将会执行.

@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
LettuceConnectionFactory redisConnectionFactory(
		ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
		ClientResources clientResources) throws UnknownHostException {
		//获取lettuce客户端配置
	LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
			getProperties().getLettuce().getPool());
			//创建connectionFactory对象
	return createLettuceConnectionFactory(clientConfig);
}

@ConditionalOnMissingBean(RedisConnectionFactory.class), 表示RedisConnectionFactory类型不存在的时候当前方法执行, 而在项目启动的时候这个类型确实不存在, 因此当前方法会执行.

private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
	if (getSentinelConfig() != null) {
		return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
	}
	if (getClusterConfiguration() != null) {
		return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
	}
	//单机redis
	return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
}

在实例化LettuceConnectionFactory对象的时候会先判断配置的redis模式, 分别是通过sentinel, cluster属性是否配置的了值来进行判断的, 说明一下,sentinel指的是哨兵模式, cluster指的是集群模式, 本公司用的是单机模式, 因此会走单机方式的实例化方法, 其实大部分公司用redis都是单机版的, 只有数据量大的才会采用其他方式.

protected final RedisStandaloneConfiguration getStandaloneConfig() {
	RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
	if (StringUtils.hasText(this.properties.getUrl())) {
		ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
		config.setHostName(connectionInfo.getHostName());
		config.setPort(connectionInfo.getPort());
		config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
	}
	else {
		config.setHostName(this.properties.getHost());
		config.setPort(this.properties.getPort());
		config.setPassword(RedisPassword.of(this.properties.getPassword()));
	}
	config.setDatabase(this.properties.getDatabase());
	return config;
}

在yml文件中配置的redis参数其实都赋值给了RedisStandaloneConfiguration对象中的对应属性.这些配置信息最终都被包装在了LettuceConnectionFactory实例中.通过看LettuceConnectionFactory的源码我们会发现LettuceConnectionFactory实际上最终是实现了RedisConnectionFactory, 因此在RedisAutoConfiguration源码中的redisTemplate方法中注入的RedisConnectionFactory对象实际上是LettuceConnectionFactory

@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
		throws UnknownHostException {
	RedisTemplate<Object, Object> template = new RedisTemplate<>();
	template.setConnectionFactory(redisConnectionFactory);
	return template;
}

有些读者可能会有疑问了,不是还导入了JedisConnectionConfiguration了吗,为什么不会是JedisConnectionFactory, 仔细看redisTemplate()方法, 他里面将RedisTemplate交给了spring管理, 而方法上同时加了注解@ConditionalOnMissingBean(name = “redisTemplate”), 这就意味着当LettuceConnectionFactory注入后, RedisTemplate类型的bean也已经注入了容器中, redisTemplate方法不可能再次执行了, 因此我们说springboot集成redis默认用的是lettuce客户端方式

@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
		throws UnknownHostException {
	StringRedisTemplate template = new StringRedisTemplate();
	template.setConnectionFactory(redisConnectionFactory);
	return template;
}

当然springboot也默认将StringRedisTemplate 类型的bean交给了容器管理, 因此这两种bean都是自动装配的.

4. 第二部分: 操作redis的api实际上是通过创建代理对象来实现的

下面我们通过一个操作redis字符串类型的API来分析整个API的实现源码, 因为创建代理对象的源码调用链路很长, 所以关键的源码我会配上文字说明, 不是重点的源码我会贴出来,但不会进行文字说明了.

@RestController
@RequestMapping(value = "redis")
public class RedisController {

    @Resource(name = "redisTemplate")
    private ValueOperations valueOperations;

    @GetMapping("/test")
    public void test() throws IOException {
        valueOperations.set("testname", "zhangsn");
    }
}

这个图中我用@Resource(name = “redisTemplate”)将redisTemplate注入给了valueOperations, 这两个不是同一种类型,不会出现注入失败的情况吗, 这其实是spring集成redis的一种Editor机制, 有兴趣的可以自行研究, 这不是本次的重点.不深入为读者解析了.

@Override
public void set(K key, V value) {
	//这一步是将value进行序列化, 默认用JdkSerializationRedisSerializer二进制流的方式	
	byte[] rawValue = rawValue(value);
	execute(new ValueDeserializingRedisCallback(key) {

		@Override
		protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
			connection.set(rawKey, rawValue);
			return null;
		}
	}, true);
}
  1. 第一步默认用JdkSerializationRedisSerializer二进制流的方式序列化value,当我们用RedisTemplate如果没有指定序列化方式的话,默认的是采用JdkSerializationRedisSerializer二进制流的方式,这种方式兼容性好,速度快,存储空间小;缺点就是没有可读性, 而且你的对象需要先实现​​java.io.Serializable​​接口,因此我们都会在项目中配置序列化方式, 比如使用org.springframework.data.redis.serializer.StringRedisSerializer字符串序列化方式.或Jackson2JsonRedisSerializer的json序列化方式,这两种方式都有良好的可读性, 值得说明的是, StringRedisTemplate的key和value默认的就是字符串序列化方式.
  2. set方法中有个回调方法, 经常读源码的都知道,我们首先要知道回调对象是什么, 然后才知道回调方法究竟是在哪里回调的,所以我们再看execute()方法.
@Nullable
<T> T execute(RedisCallback<T> callback, boolean exposeConnection) {
	return template.execute(callback, exposeConnection);
}

@Nullable
public <T> T execute(RedisCallback<T> action, boolean exposeConnection) {
	return execute(action, exposeConnection, false);
}

@Nullable
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {

	Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
	Assert.notNull(action, "Callback object must not be null");

	RedisConnectionFactory factory = getRequiredConnectionFactory();
	RedisConnection conn = null;
	try {
		//是否开启了事务, 默认关闭
		if (enableTransactionSupport) {
			// only bind resources in case of potential transaction synchronization
			conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
		} else {
			conn = RedisConnectionUtils.getConnection(factory);
		}

		boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);

		RedisConnection connToUse = preProcessConnection(conn, existingConnection);

		boolean pipelineStatus = connToUse.isPipelined();
		if (pipeline && !pipelineStatus) {
			connToUse.openPipeline();
		}

		RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
		T result = action.doInRedis(connToExpose);

		// close pipeline
		if (pipeline && !pipelineStatus) {
			connToUse.closePipeline();
		}

		// TODO: any other connection processing?
		return postProcessResult(result, connToUse, existingConnection);
	} finally {
		RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);
	}
}

最终会调用RedisTemplate中的execute()方法, 这里有个enableTransactionSupport属性,表示是否开启了事务, 默认关闭, 这里提到了redis事务, 但不是本次的重点,我不做解析了, 我们往下看else中获取RedisConnection对象的方法,这里获取的RedisConnection类型的对象就是一个代理对象, 这就是第二部分的核心源码, 创建代理对象.

public static RedisConnection getConnection(RedisConnectionFactory factory) {
	return getConnection(factory, false);
}

public static RedisConnection getConnection(RedisConnectionFactory factory, boolean transactionSupport) {
	return doGetConnection(factory, true, false, transactionSupport);
}

public static RedisConnection doGetConnection(RedisConnectionFactory factory, boolean allowCreate, boolean bind,
		boolean transactionSupport) {

	Assert.notNull(factory, "No RedisConnectionFactory specified");

	RedisConnectionHolder connHolder = (RedisConnectionHolder) TransactionSynchronizationManager.getResource(factory);

	if (connHolder != null) {
		if (transactionSupport) {
			potentiallyRegisterTransactionSynchronisation(connHolder, factory);
		}
		return connHolder.getConnection();
	}

	if (!allowCreate) {
		throw new IllegalArgumentException("No connection found and allowCreate = false");
	}

	if (log.isDebugEnabled()) {
		log.debug("Opening RedisConnection");
	}
	
	//核心方法, 获取连接对象, 这里的连接对象实际上是LettuceConnection类型
	RedisConnection conn = factory.getConnection();

	if (bind) {

		RedisConnection connectionToBind = conn;
		if (transactionSupport && isActualNonReadonlyTransactionActive()) {
			connectionToBind = createConnectionProxy(conn, factory);
		}

		connHolder = new RedisConnectionHolder(connectionToBind);

		TransactionSynchronizationManager.bindResource(factory, connHolder);
		if (transactionSupport) {
			potentiallyRegisterTransactionSynchronisation(connHolder, factory);
		}

		return connHolder.getConnection();
	}

	return conn;
}

这里的核心是通过LettuceConnectionFactory获取LettuceConnection对象, 这个对象就相当于我们连接数据库的Connection对象,我们可以通过spring.redis.lettuce配置连接信息.

if (isClusterAware()) {
		return getClusterConnection();
	}

	LettuceConnection connection;
	//核心方法,创建连接redis的对象
	connection = doCreateLettuceConnection(getSharedConnection(), connectionProvider, getTimeout(), getDatabase());
	connection.setConvertPipelineAndTxResults(convertPipelineAndTxResults);
	return connection;
}

因为在自动装配的时候实例化的是LettuceConnectionFactory, 因此这个factory就是LettuceConnectionFactory

@Nullable
protected StatefulRedisConnection<byte[], byte[]> getSharedConnection() {
	return shareNativeConnection ? (StatefulRedisConnection) getOrCreateSharedConnection().getConnection() : null;
}

@Nullable
StatefulConnection<E, E> getConnection() {

	synchronized (this.connectionMonitor) {
		//获取StatefulRedisConnectionImpl类型的连接,这里比较重要的点是只有连接不存在的时候,才去获取, 所有整个过程只有一个connection对象.
		if (this.connection == null) {
			this.connection = getNativeConnection();
		}

		if (getValidateConnection()) {
			validateConnection();
		}

		return this.connection;
	}
}

private StatefulConnection<E, E> getNativeConnection() {
	return connectionProvider.getConnection(StatefulConnection.class);
}

@Override
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) {

	if (connectionType.equals(StatefulRedisSentinelConnection.class)) {
		return connectionType.cast(client.connectSentinel());
	}
	
	//通过RedisClient创建连接对象
	if (connectionType.equals(StatefulRedisPubSubConnection.class)) {
		return connectionType.cast(client.connectPubSub(codec));
	}

	if (StatefulConnection.class.isAssignableFrom(connectionType)) {

		return connectionType.cast(readFrom.map(it -> this.masterReplicaConnection(redisURISupplier.get(), it))
				.orElseGet(() -> client.connect(codec)));
	}

	throw new UnsupportedOperationException("Connection type " + connectionType + " not supported!");
}
  1. getConnection方法获取的是StatefulRedisConnectionImpl类型的连接,这里比较重要的点是只有连接不存在的时候,才去获取,所有整个过程只有一个connection对象.而这个StatefulRedisConnectionImpl是一个线程安全的对象,这就是为什么说lettuce模式是线程安全的
  2. 因为我们用的是单机版的redis, 因此最终进入StandaloneConnectionProvider类中的getConnection()方法,并通过RedisClient创建连接对象
public <K, V> StatefulRedisConnection<K, V> connect(RedisCodec<K, V> codec) {
    checkForRedisURI();
	//先创建连接对象, 通过ConnectionFuture的获取线程的返回值
    return getConnection(connectStandaloneAsync(codec, this.redisURI, timeout));
}

  private <K, V> ConnectionFuture<StatefulRedisConnection<K, V>> connectStandaloneAsync(RedisCodec<K, V> codec,
           RedisURI redisURI, Duration timeout) {

       assertNotNull(codec);
       checkValidRedisURI(redisURI);

       logger.debug("Trying to get a Redis connection for: " + redisURI);

       DefaultEndpoint endpoint = new DefaultEndpoint(clientOptions, clientResources);
       RedisChannelWriter writer = endpoint;

       if (CommandExpiryWriter.isSupported(clientOptions)) {
           writer = new CommandExpiryWriter(writer, clientOptions, clientResources);
       }
		
		//实例化StatefulRedisConnectionImpl对象, 这个对象包装了代理对象
       StatefulRedisConnectionImpl<K, V> connection = newStatefulRedisConnection(writer, codec, timeout);
       ConnectionFuture<StatefulRedisConnection<K, V>> future = connectStatefulAsync(connection, codec, endpoint, redisURI,
               () -> new CommandHandler(clientOptions, clientResources, endpoint));

       future.whenComplete((channelHandler, throwable) -> {

           if (throwable != null) {
               connection.close();
           }
       });

       return future;
   }

先创建连接对象, 通过ConnectionFuture的获取线程的返回值,实例化StatefulRedisConnectionImpl对象, 这个对象包装了代理对象.

   protected <K, V> StatefulRedisConnectionImpl<K, V> newStatefulRedisConnection(RedisChannelWriter channelWriter,
           RedisCodec<K, V> codec, Duration timeout) {
       return new StatefulRedisConnectionImpl<>(channelWriter, codec, timeout);
   }

   public StatefulRedisConnectionImpl(RedisChannelWriter writer, RedisCodec<K, V> codec, Duration timeout) {

       super(writer, timeout);

       this.codec = codec;
       this.async = newRedisAsyncCommandsImpl();
       this.sync = newRedisSyncCommandsImpl();
       this.reactive = newRedisReactiveCommandsImpl();
   }

在StatefulRedisConnectionImpl的构造器中会实例化3种类型的commands实例, RedisAsyncCommandsImpl是一个异步API, RedisSyncCommandsImpl是一个同步API, RedisTemplate默认用的就是这个api, 但底层其实还是用的异步, 这个等分析执行API的时候再说, RedisReactiveCommandsImpl是一个响应式API, 在使用ReactiveRedisTemplate时执行该API, 因为RedisTemplate用的是同步API, 因此我们特别分析RedisSyncCommandsImpl构造器.

protected RedisCommands<K, V> newRedisSyncCommandsImpl() {
    return syncHandler(async(), RedisCommands.class, RedisClusterCommands.class);
}

//这个方法在newRedisSyncCommandsImpl方法中被调用, 这里返回一个异步对象, 而这个对象就是上面的RedisAsyncCommandsImpl
@Override
public RedisAsyncCommands<K, V> async() {
    return async;
}

//创建RedisCommands,RedisClusterCommands接口的代理
protected <T> T syncHandler(Object asyncApi, Class<?>... interfaces) {
    FutureSyncInvocationHandler h = new FutureSyncInvocationHandler((StatefulConnection<?, ?>) this, asyncApi, interfaces);
    return (T) Proxy.newProxyInstance(AbstractRedisClient.class.getClassLoader(), interfaces, h);
}

async()方法在newRedisSyncCommandsImpl方法中被调用, 这里返回一个异步对象, 而这个对象就是上面的RedisAsyncCommandsImpl, 这点很重要, 这就是为什么说同步执行其实执行的是异步命令, 因为在执行同步命令的时候,会通过反射执行RedisAsyncCommandsImpl的method, syncHandler是创建RedisCommands,RedisClusterCommands接口的代理, 而对应的InvocationHandler是FutureSyncInvocationHandler, 所以他们的delegate最终都会执行FutureSyncInvocationHandler.到这里代理对象就被创建完成了.

5. 第三部分: 执行操作redis的API最终是转成了对应的Redis命令执行

我们回到 RedisConnection conn = factory.getConnection()方法, 这个RedisConnection实际上是LettuceConnection类型, 上面说到StatefulRedisConnectionImpl是单实例的, 但是lettuce每次创建的LettuceConnection是多实例的, 每个LettuceConnection包装同一个StatefulRedisConnectionImpl实例, 这点很重要,所以整个项目的代理也只创建了一个.

LettuceConnection(@Nullable StatefulConnection<byte[], byte[]> sharedConnection,
		LettuceConnectionProvider connectionProvider, long timeout, int defaultDbIndex) {

	Assert.notNull(connectionProvider, "LettuceConnectionProvider must not be null.");

	this.asyncSharedConn = sharedConnection;
	this.connectionProvider = connectionProvider;
	this.timeout = timeout;
	this.defaultDbIndex = defaultDbIndex;
	this.dbIndex = this.defaultDbIndex;
}

	public final V doInRedis(RedisConnection connection) {
		byte[] result = inRedis(rawKey(key), connection);
		return deserializeValue(result);
	}

获取到连接对象后, 会new 一个LettuceConnection实例,同时将StatefulRedisConnectionImpl通过构造器赋值给asyncSharedConn属性, 这点很重要. 之后会执行T result = action.doInRedis(connToExpose); 因为这个action其实是ValueDeserializingRedisCallback, 我们可以看到会执行inRedis(),根据函数式编程的特性,实际上就是执行下面这个方法里的inRedis()方法

@Override
	public void set(K key, V value) {

		byte[] rawValue = rawValue(value);
		execute(new ValueDeserializingRedisCallback(key) {

			@Override
			protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {	
			//实际上会执行这个方法,这里的connection就是LettuceConnection对象
				connection.set(rawKey, rawValue);
				return null;
			}
		}, true);
	}

实际会执行connection.set()方法,这里的connection就是LettuceConnection对象.
因为LettuceConnection继承了AbstractRedisConnection,而AbstractRedisConnection又实现了DefaultedRedisConnection, 因此这个set方法实现上执行的是DefaultedRedisConnection的set方法, 如果用的是StringRedisTemplate那么执行的就是DefaultedStringRedisConnection的set方法, 不管怎样,看到最后我们会发现,他们底层执行的方法是一样的, 这里我们以DefaultedRedisConnection为例分析

default Boolean set(byte[] key, byte[] value) {
	return stringCommands().set(key, value);
}
	//stringCommands方法调用的实际上是这个,获取LettuceStringCommands对象
	@Override
	public RedisStringCommands stringCommands() {
		return new LettuceStringCommands(this);
	}
public Boolean set(byte[] key, byte[] value) {

		Assert.notNull(key, "Key must not be null!");
		Assert.notNull(value, "Value must not be null!");

		try {
			if (isPipelined()) {
				pipeline(
						connection.newLettuceResult(getAsyncConnection().set(key, value), Converters.stringToBooleanConverter()));
				return null;
			}
			if (isQueueing()) {
				transaction(
						connection.newLettuceResult(getAsyncConnection().set(key, value), Converters.stringToBooleanConverter()));
				return null;
			}
			return Converters.stringToBoolean(getConnection().set(key, value));
		} catch (Exception ex) {
			throw convertLettuceAccessException(ex);
		}
	}
	
	//这里的connection就是LettuceConnetion
	public RedisClusterCommands<byte[], byte[]> getConnection() {
		return connection.getConnection();
	}

	protected RedisClusterCommands<byte[], byte[]> getConnection() {

		if (isQueueing()) {
			return getDedicatedConnection();
		}
		if (asyncSharedConn != null) {
			//在实例化LettuceConnection时已经将asyncSharedConn属性赋值StatefulRedisConnectionImpl,因此会执行当前方法, 默认获取的是同步对象,也就是代理对象
			if (asyncSharedConn instanceof StatefulRedisConnection) {
				return ((StatefulRedisConnection<byte[], byte[]>) asyncSharedConn).sync();
			}
			if (asyncSharedConn instanceof StatefulRedisClusterConnection) {
				return ((StatefulRedisClusterConnection<byte[], byte[]>) asyncSharedConn).sync();
			}
		}
		return getDedicatedConnection();
	}

这里是获取LettuceStringCommands对象, 最终调用LettuceStringCommands的getConnect(), 因为在返回LettuceCommands实例的时候,在构造器中将asyncSharedConn 属性赋值StatefulRedisConnectionImpl, 因为此通过sync()方法获取就是第二部分创建的代理对象,因为实际上会执行FutureSyncInvocationHandler的handleInvocation方法.

   FutureSyncInvocationHandler(StatefulConnection<?, ?> connection, Object asyncApi, Class<?>[] interfaces) {
        this.connection = connection;
        this.timeoutProvider = new TimeoutProvider(() -> connection.getOptions().getTimeoutOptions(), () -> connection
                .getTimeout().toNanos());
        this.asyncApi = asyncApi;
		//translator内部维护了一个map, 这个map的key是RedisAsyncCommandsImpl的命令方法, value是AbstractRedisAsyncCommands的命令方法, 而RedisAsyncCommandsImpl又继承了AbstractRedisAsyncCommands, 因此所有操作redis的命令都能获取到对应的执行命令
        this.translator = MethodTranslator.of(asyncApi.getClass(), interfaces);
    }
protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable {

        try {
			
			//实际会执行AbstractRedisAsyncCommands中的命令方法
            Method targetMethod = this.translator.get(method);
            Object result = targetMethod.invoke(asyncApi, args);

            if (result instanceof RedisFuture<?>) {

                RedisFuture<?> command = (RedisFuture<?>) result;

                if (isNonTxControlMethod(method.getName()) && isTransactionActive(connection)) {
                    return null;
                }

                long timeout = getTimeoutNs(command);

                return LettuceFutures.awaitOrCancel(command, timeout, TimeUnit.NANOSECONDS);
            }

            return result;
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }

//AbstractRedisAsyncCommands中的set()方法
    @Override
    public RedisFuture<String> set(K key, V value) {
        return dispatch(commandBuilder.set(key, value));
    }

    Command<K, V, String> set(K key, V value) {
        notNullKey(key);

        return createCommand(SET, new StatusOutput<>(codec), key, value);
    }

    protected <T> Command<K, V, T> createCommand(CommandType type, CommandOutput<K, V, T> output, K key, V value) {
        CommandArgs<K, V> args = new CommandArgs<K, V>(codec).addKey(key).addValue(value);
        return createCommand(type, output, args);
    }
    
    protected <T> Command<K, V, T> createCommand(CommandType type, CommandOutput<K, V, T> output, CommandArgs<K, V> args) {
        return new Command<K, V, T>(type, output, args);
    }
	
	//命令每次都实例化一个
    public Command(ProtocolKeyword type, CommandOutput<K, V, T> output, CommandArgs<K, V> args) {
        LettuceAssert.notNull(type, "Command type must not be null");
        this.type = type;
        this.output = output;
        this.args = args;
    }

//这里的connection就是StatefulRedisConnectionImpl, 在底层维护一个 tcp 连接,多个线程共享一个连接对象。同时会有一个ConnectionWatchdog[ChannelInboundHandlerAdapter继承netty]来维护连接,实现断连重连。因此是一个线程安全的对象
public <T> AsyncCommand<K, V, T> dispatch(RedisCommand<K, V, T> cmd) {
        AsyncCommand<K, V, T> asyncCommand = new AsyncCommand<>(cmd);
        RedisCommand<K, V, T> dispatched = connection.dispatch(asyncCommand);
        if (dispatched instanceof AsyncCommand) {
            return (AsyncCommand<K, V, T>) dispatched;
        }
        return asyncCommand;
    }
    @Override
    public <T> RedisCommand<K, V, T> dispatch(RedisCommand<K, V, T> command) {

        RedisCommand<K, V, T> toSend = preProcessCommand(command);

        try {
            return super.dispatch(toSend);
        } finally {
            if (command.getType().name().equals(MULTI.name())) {
                multi = (multi == null ? new MultiOutput<>(codec) : multi);
            }
        }
    }

//底层是通过netty通信发送命令
protected <T> RedisCommand<K, V, T> dispatch(RedisCommand<K, V, T> cmd) {

        if (debugEnabled) {
            logger.debug("dispatching command {}", cmd);
        }

        if (tracingEnabled) {

            RedisCommand<K, V, T> commandToSend = cmd;
            TraceContextProvider provider = CommandWrapper.unwrap(cmd, TraceContextProvider.class);

            if (provider == null) {
                commandToSend = new TracedCommand<>(cmd, clientResources.tracing()
                        .initialTraceContextProvider().getTraceContext());
            }

            return channelWriter.write(commandToSend);
        }

        return channelWriter.write(cmd);
    }
  1. 在 FutureSyncInvocationHandler的属性translator内部维护了一个map,
    这个map的key是RedisAsyncCommandsImpl的命令方法,
    value是AbstractRedisAsyncCommands的命令方法,
    而RedisAsyncCommandsImpl又继承了AbstractRedisAsyncCommands,
    因此所有操作redis的命令都能获取到对应的执行命令,
    因此实际会执行AbstractRedisAsyncCommands中对应的命令方法.
  2. 每个命令都会用Command包装, 实际发送命令会通过StatefulRedisConnectionImpl,
    在第二部分我们已经说了,在整个项目中只有这一个实例, 它是线程安全的,在底层维护一个 tcp
    连接,多个线程共享一个连接对象。同时会有一个ConnectionWatchdog[ChannelInboundHandlerAdapter继承netty]来维护连接,实现断连重连.
    底层是通过netty通信发送命令.

至此整个实现原理已分析完毕.

你可能感兴趣的:(spring,boot,redis,java)