前言
- 我们知道,在 springboot 1.5.x版本的默认的Redis客户端是 Jedis实现的,需要导入jedis依赖,springboot 2.x版本中默认客户端是用 lettuce实现的,需要导入spring-boot-starter-data-redis依赖。这两种方式使用的都是 TCP协议.
- Jedis使用直连方式连接Redis Server,在多线程环境下存在线程安全问题, 因此需要增加连接池来解决线程安全的问题,同时可以限制redis客户端的数量, 但这种直连方式基于传统I/O模式,是阻塞式传输.
- 而 Lettuce 是 一种可伸缩,线程安全,完全非阻塞的Redis客户端,底层基于netty通信,我们知道netty是基于NIO的非阻塞通信, 天生支持高并发,因此在在多线程环境下不存在线程安全问题,一个连接实例就可以满足多线程环境下的并发访问, 当然实例不够的情况下也可以按需增加实例,保证伸缩性.
- 下面我们通过源码的方式解析springboot是如何通过lettuce方式连接redis server的,以及springboot操作redis的底层原理.
Springboot操作Redis流程图如下:
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标识的方法会做如下处理:
- 都会被CGLIB进行代理
- 会走bean的生命周期中的一些行为(比如:@PostConstruct、@Destroy等 Spring中提供的生命周期)
- 无论调用几次,得到的都是同一个bean
优点
可以支持通过常规Java调用相同类的@Bean方法而保证是容器内的Bean,这有效规避了在“Lite模式”下操作时难以跟踪的细微错误。
缺点
- 启动时会给该配置类生成一个CGLIB子类放进容器(代理),会增加启动时间。 Spring
Boot有很多配置类,这个开销是不容忽视的。这也是Spring 5.2新增了proxyBeanMethods属性的原因 - 因为被代理了,所以@Bean方法 不可以是private、不可以是final
false
说明
如果为true,则是Lite模式。被@Bean标识的方法会做如下处理:
- 不会被拦截进行CGLIB代理
- 不会走bean的生命周期中的一些行为(比如:@PostConstruct、@Destroy等 Spring中提供的生命周期)
- 如果同一个Configuration中调用@Bean标识的方法,就只是普通方法的执行而已,不会从容器中获取对象。
@Bean标识的返回值对象还是会放入到容器中的,从容器中获取bean还是可以是单例的,会走生命周期。
优点
- 启动时不需要给配置类生成CGLIB子类,减少了启动时间
- 可以将配置类当作一个普通类使用:也就是说@Bean方法 可以是private、可以是final
缺点
- 不能声明@Bean之间的依赖(也就是说不能通过方法调用来依赖其它Bean)
- 解决方案:把依赖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);
}
- 第一步默认用JdkSerializationRedisSerializer二进制流的方式序列化value,当我们用RedisTemplate如果没有指定序列化方式的话,默认的是采用JdkSerializationRedisSerializer二进制流的方式,这种方式兼容性好,速度快,存储空间小;缺点就是没有可读性, 而且你的对象需要先实现java.io.Serializable接口,因此我们都会在项目中配置序列化方式, 比如使用org.springframework.data.redis.serializer.StringRedisSerializer字符串序列化方式.或Jackson2JsonRedisSerializer的json序列化方式,这两种方式都有良好的可读性, 值得说明的是, StringRedisTemplate的key和value默认的就是字符串序列化方式.
- 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!");
}
- getConnection方法获取的是StatefulRedisConnectionImpl类型的连接,这里比较重要的点是只有连接不存在的时候,才去获取,所有整个过程只有一个connection对象.而这个StatefulRedisConnectionImpl是一个线程安全的对象,这就是为什么说lettuce模式是线程安全的
- 因为我们用的是单机版的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);
}
- 在 FutureSyncInvocationHandler的属性translator内部维护了一个map,
这个map的key是RedisAsyncCommandsImpl的命令方法,
value是AbstractRedisAsyncCommands的命令方法,
而RedisAsyncCommandsImpl又继承了AbstractRedisAsyncCommands,
因此所有操作redis的命令都能获取到对应的执行命令,
因此实际会执行AbstractRedisAsyncCommands中对应的命令方法. - 每个命令都会用Command包装, 实际发送命令会通过StatefulRedisConnectionImpl,
在第二部分我们已经说了,在整个项目中只有这一个实例, 它是线程安全的,在底层维护一个 tcp
连接,多个线程共享一个连接对象。同时会有一个ConnectionWatchdog[ChannelInboundHandlerAdapter继承netty]来维护连接,实现断连重连.
底层是通过netty通信发送命令.