最近在看Luttuce的源码,简单记录一下
1.LuttuceConnectionConfiguration
LettuceConnectionConfiguration类是lettuce初始化的起始类,这个类是spring的管理的配置类,它初始化了lettuce连接工厂类,见如下代码
@Bean
@ConditionalOnMissingBean({RedisConnectionFactory.class})
LettuceConnectionFactory redisConnectionFactory(ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers, ClientResources clientResources) throws UnknownHostException {
LettuceClientConfiguration clientConfig = this.getLettuceClientConfiguration(builderCustomizers, clientResources, this.getProperties().getLettuce().getPool());
return this.createLettuceConnectionFactory(clientConfig);
}
下面会判断单点模式/集群模式/哨兵模式,来初始化连接工厂,本文以集群模式为例来讲解
private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
if (this.getSentinelConfig() != null) {
return new LettuceConnectionFactory(this.getSentinelConfig(), clientConfiguration);
} else {
return this.getClusterConfiguration() != null ? new LettuceConnectionFactory(this.getClusterConfiguration(), clientConfiguration) : new LettuceConnectionFactory(this.getStandaloneConfig(), clientConfiguration);
}
}
后面会进入LettuceConnectionFactory的构造方法:
public LettuceConnectionFactory(RedisClusterConfiguration clusterConfiguration,
LettuceClientConfiguration clientConfig) {
this(clientConfig);
Assert.notNull(clusterConfiguration, "RedisClusterConfiguration must not be null!");
this.configuration = clusterConfiguration;
}
2. LettuceConnectionFactory
这个是LettuceConnection工厂,看名字基本上知道是做什么的
由于它继承了InitializingBean,最终会走到afterPropertiesSet方法
public void afterPropertiesSet() {
this.client = createClient(); // todo redisClusterClient
this.connectionProvider = new ExceptionTranslatingConnectionProvider(createConnectionProvider(client, CODEC)); // todo LettuceConnectionFactory$ExceptionTranslatingConnectionProvider
this.reactiveConnectionProvider = new ExceptionTranslatingConnectionProvider(
createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC));
if (isClusterAware()) {
this.clusterCommandExecutor = new ClusterCommandExecutor(
new LettuceClusterTopologyProvider((RedisClusterClient) client),
new LettuceClusterConnection.LettuceClusterNodeResourceProvider(this.connectionProvider),
EXCEPTION_TRANSLATION);
}
if (getEagerInitialization() && getShareNativeConnection()) {
initConnection();
}
}
createConnectionProvider:
这里的ConnectionProvider是ClusterConnectionProvider,然后又包装了一层LettucePoolingConnectionProvider,也就是说最终connectionProvider和reactiveConnectionProvider都是LettucePoolingConnectionProvider外面再包一层ExceptionTranslatingConnectionProvider
3. ClusterConnectionProvider.getConnection
入口是:RedisConnectionUtils.getConnection -> LettuceConnectionFactory.getConnection ->ClusterConnectionProvider.getConnection
使用ExceptionTranslatingConnectionProvider.getConnection(这里的delegate就是LettucePoolingConnectionProvider)获取连接
这里的delegate就是LettucePoolingConnectionProvider,会执行到它的getConnection()
这里一共做了如下这些事:
- 查找或创建连接池:当需要一个新的连接时,首先在 pools 中查找是否已经存在对应类型的连接池。如果不存在,则创建一个新的连接池。
- 借用连接:从连接池中借用一个连接,连接池管理连接的生命周期。如果连接池为空且配置允许,连接池将创建一个新的连接并返回。
- 存储连接与池的映射:将借用到的连接和它所属的连接池记录下来,以便后续管理。
- 返回连接:将连接转换为请求的类型并返回给调用者。
在创建ConnectionPoolSupport.createGenericObjectPool传入一个表达式,在执行pool.borrowObject的时候会真正调用这里的connectionProvider.getConnection(connectionType),也就是最终会调用到ClusterConnectionProvider.getConnectionAsync方法
这里大致的逻辑是:
- 设置初始化
- 根据不同的连接类型创建,当 connectionType 是 StatefulRedisPubSubConnection 或 StatefulRedisClusterPubSubConnection 类型时,调用 client.connectPubSubAsync(codec) 获取发布订阅连接,并使用 thenApply 方法转换连接类型;当 connectionType 是 StatefulRedisClusterConnection 的子类或直接等于 StatefulConnection 时,调用 client.connectAsync(codec) 获取集群连接,并设置读取选项(如果存在
4. RedisClusterClient.connectAsync
- 创建ClusterDistributionChannelWriter: Redis 客户端中一个专门设计用于管理和分发集群命令的类。它的主要作用是协调将 Redis 命令发送到适当的 Redis 集群节点,并确保这些命令能够正确地路由到对应的分片(shard)上。
- 创建 PooledClusterConnectionProvider: 是 Redis 集群客户端中的一个关键组件,它的主要职责是管理 Redis 集群的连接池。它在集群环境中负责创建和管理连接实例,并根据需要将连接提供给客户端使用,以确保高效的资源管理和连接的复用。
- 创建 StatefulRedisClusterConnectionImpl:是 Redis 客户端库中的一个实现类,它提供了与 Redis 集群建立和管理状态连接的具体实现。这个类实现了 StatefulRedisClusterConnection<K, V> 接口,封装了与 Redis 集群的交互逻辑,包括命令的发送、连接的管理、拓扑的更新等。
- commandHandlerSupplier:命令处理handler,在后续构造netty客户端的时候会用到
这里的细节不用特别关注,大概知道这里是通过下面这段代码在创建连接:
5. RedisClusterClient.connectStatefulAsync
private <K, V, T extends StatefulRedisClusterConnectionImpl<K, V>, S> ConnectionFuture<S> connectStatefulAsync(T connection,
DefaultEndpoint endpoint, RedisURI connectionSettings, Mono<SocketAddress> socketAddressSupplier,
Supplier<CommandHandler> commandHandlerSupplier) {
// 创建连接构造器
ConnectionBuilder connectionBuilder = createConnectionBuilder(connection, connection.getConnectionState(), endpoint,
connectionSettings, socketAddressSupplier, commandHandlerSupplier);
// 初始化channel
ConnectionFuture<RedisChannelHandler<K, V>> future = initializeChannelAsync(connectionBuilder);
return future.thenApply(channelHandler -> (S) connection);
}
initializeChannelAsync方法最终调用io.lettuce.core.AbstractRedisClient#initializeChannelAsync0,关键代码如下:
private void initializeChannelAsync0(ConnectionBuilder connectionBuilder, CompletableFuture<Channel> channelReadyFuture,
SocketAddress redisAddress) {
// netty客户端启动引导,在前文createConnectionBuilder流程中构造
Bootstrap redisBootstrap = connectionBuilder.bootstrap();
// 连接初始化器
ChannelInitializer<Channel> initializer = connectionBuilder.build(redisAddress);
redisBootstrap.handler(initializer);
clientResources.nettyCustomizer().afterBootstrapInitialized(redisBootstrap);
// 建链
ChannelFuture connectFuture = redisBootstrap.connect(redisAddress);
channelReadyFuture.whenComplete((c, t) -> {
if (t instanceof CancellationException) {
connectFuture.cancel(true);
}
});
// 建链响应回调处理,连接初始化、握手结果处理
connectFuture.addListener(future -> {
// 省略。。。
});
}
channel初始化器构造:io.lettuce.core.ConnectionBuilder#build
public ChannelInitializer<Channel> build(SocketAddress socketAddress) {
return new PlainChannelInitializer(this::buildHandlers, clientResources);
}
protected List<ChannelHandler> buildHandlers() {
List<ChannelHandler> handlers = new ArrayList<>();
connection.setOptions(clientOptions);
// channel连接、断链事件监听handler(tcp级别事件)
handlers.add(new ChannelGroupListener(channelGroup, clientResources.eventBus()));
// resp协议编码handler
handlers.add(new CommandEncoder());
// 连接建立后握手handler,主要执行AUTH、PING命令
handlers.add(getHandshakeHandler());
// 命令处理handler
handlers.add(commandHandlerSupplier.get());
// 连接事件发布handler(握手完成后,应用层级别的事件)
handlers.add(new ConnectionEventTrigger(connectionEvents, connection, clientResources.eventBus()));
// 自动重连处理handler
if (clientOptions.isAutoReconnect()) {
handlers.add(createConnectionWatchdog());
}
return handlers;
}
其中初始化链接:
- 当饥饿式初始化链接来自于LettuceConnectionFactory.afterPropertiesSet 里面的initConnection
- 懒惰式建链由第一次命令执行时触发建链,建链调用触发点为org.springframework.data.redis.core.RedisTemplate#execute(org.springframework.data.redis.core.RedisCallback, boolean, boolean)
获取连接的核心逻辑如下:
释放连接的源码就不追了,大致过程如下: