Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接。
Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。lettuce主要利用netty实现与redis的同步和异步通信。
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
任何时候Spring IOC容器中同时管理StringRedisTemplate、RedisTemplate两个实例。
LettuceConnectionConfiguration、JedisConnectionConfiguration有个公共功能是分别创建Redis连接工厂接口RedisConnectionFactory的实现子类:LettuceConnectionFactory
、jedis连接工厂类之JedisConnectionFactory
。两个实现子类均在接口InitializingBean功能下初始化连接池相关功能。
由于先解析LettuceConnectionConfiguration类,所以两者配置类都满足的前提下,优先通过LettuceConnectionConfiguration创建JedisConnectionFactory。
1.JedisConnectionConfiguration
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration {
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
return createJedisConnectionFactory();
}
}
public class JedisConnectionFactory implements InitializingBean, RedisConnectionFactory {
public void afterPropertiesSet() {
clientConfig = createClientConfig(getDatabase(), getRedisUsername(), getRedisPassword());
...
if (getUsePool() && !isRedisClusterAware()) {
//初始化连接池 -> JedisPool -> JedisPool构造器中初始化GenericObjectPool
this.pool = createPool();
}
...
this.initialized = true;
}
}
JedisConnectionConfiguration发挥作用的前提条件之一是存在Jedis类,该类是通过以下依赖引入的:
<dependency>
<groupId>redis.clients</groupId>
// 该依赖中默认存在 commons-pool2, 实现连接池的管理
<artifactId>jedis</artifactId>
</dependency>
<!-- redis依赖commons-pool -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2.LettuceConnectionConfiguration
@Configuration
@ConditionalOnClass(RedisClient.class)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public LettuceConnectionFactory redisConnectionFactory(ClientResources clientResources)
throws UnknownHostException {
LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(clientResources,
this.properties.getLettuce().getPool());
return createLettuceConnectionFactory(clientConfig);
}
}
LettuceConnectionConfiguration配置类发挥作用的前提条件之一是存在Jedis类,该类是通过以下依赖引入的:
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.1.7.RELEASE</version>
<scope>compile</scope>
</dependency>
在SpringBoot项目中spring-boot-starter-data-redis
包下默认存在lettuce-core
。
3.获取客户端连接
class LettucePoolingConnectionProvider{
private final Map<Class<?>, GenericObjectPool<StatefulConnection<?, ?>>> pools = new ConcurrentHashMap<>(32);
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) {
GenericObjectPool<StatefulConnection<?, ?>> pool = pools.computeIfAbsent(connectionType, poolType -> {
return ConnectionPoolSupport.createGenericObjectPool(() ->
// connectionProvider:StandaloneConnectionProvider。工厂类PooledObjectFactory通过lambda表达式回调创建连接
connectionProvider.getConnection(connectionType),
poolConfig, false);
});
// pool:返回GenericObjectPool 调用ConnectionPoolSupport#createGenericObjectPool#borrowObject
StatefulConnection<?, ?> connection = pool.borrowObject();
poolRef.put(connection, pool);
return connectionType.cast(connection);
}
}
GenericObjectPool:管理连接核心类。也是实现池化技术常用手段。
public abstract class ConnectionPoolSupport {
public static <T extends StatefulConnection<?, ?>> GenericObjectPool<T> createGenericObjectPool(
Supplier<T> connectionSupplier, GenericObjectPoolConfig config, boolean wrapConnections) {
AtomicReference<Origin<T>> poolRef = new AtomicReference<>();
GenericObjectPool<T> pool = new GenericObjectPool<T>(new RedisPooledObjectFactory<T>(connectionSupplier),
config) {
@Override
public T borrowObject() throws Exception {
return wrapConnections ?
ConnectionWrapping.wrapConnection(super.borrowObject(), poolRef.get())
:super.borrowObject();//调用GenericObjectPool#borrowObject,最后是通过工厂类PooledObjectFactory的实现类RedisPooledObjectFactory创建连接
}
@Override
public void returnObject(T obj) {
if (wrapConnections && obj instanceof HasTargetConnection) {
super.returnObject((T) ((HasTargetConnection) obj).getTargetConnection());
return;
}
super.returnObject(obj);
}
};
poolRef.set(new ObjectPoolWrapper<>(pool));
return pool;
}
}
class StandaloneConnectionProvider implements LettuceConnectionProvider, TargetAware {
private final RedisClient client;
private final RedisCodec<?, ?> codec;
private final Optional<ReadFrom> readFrom;
private final Supplier<RedisURI> redisURISupplier;
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) {
if (StatefulConnection.class.isAssignableFrom(connectionType)) {
return connectionType.cast(readFrom.map(it -> this.masterReplicaConnection(redisURISupplier.get(), it))
.orElseGet(() -> client.connect(codec)));//RedisClient#connect 章节4内容
}
throw new UnsupportedOperationException("Connection type " + connectionType + " not supported!");
}
}
4.Netty建立连接的过程
public class RedisClient extends AbstractRedisClient {
public <K, V> StatefulRedisConnection<K, V> connect(RedisCodec<K, V> codec) {
return getConnection(connectStandaloneAsync(codec, this.redisURI, timeout));
}
private <K, V> ConnectionFuture<StatefulRedisConnection<K, V>> connectStandaloneAsync(RedisCodec<K, V> codec,
RedisURI redisURI, Duration timeout) {
// 该类是Redis通过通道channel写数据的核心类,内部notifyChannelActive方法是通过Netty建立连接后在ChannelActive方法中回调赋值channel的过程
DefaultEndpoint endpoint = new DefaultEndpoint(clientOptions, clientResources);
RedisChannelWriter writer = endpoint;
if (CommandExpiryWriter.isSupported(clientOptions)) {
//CommandExpiryWriter对DefaultEndpoint包装了一层
writer = new CommandExpiryWriter(writer, clientOptions, clientResources);
}
// 返回存在状态的连接 StatefulRedisConnectionImpl,其实是对DefaultEndpoint的抽象
StatefulRedisConnectionImpl<K, V> connection = newStatefulRedisConnection(writer, codec, timeout);
ConnectionFuture<StatefulRedisConnection<K, V>> future = connectStatefulAsync(connection, codec, endpoint,
// lambda表达式是为了初始化Netty涉及的ChannelHandler之CommandHandler,既是出栈也是入栈Handler。也就是在该Handler内部的ChannelActive方法负责对DefaultEndpoint赋值channel的操作
redisURI,() -> new CommandHandler(clientOptions, clientResources, endpoint));
future.whenComplete((channelHandler, throwable) -> {
if (throwable != null) {
connection.close();
}
});
return future;
}
private <K, V, S> ConnectionFuture<S> connectStatefulAsync(StatefulRedisConnectionImpl<K, V> connection,
RedisCodec<K, V> codec, Endpoint endpoint,RedisURI redisURI, Supplier<CommandHandler>
commandHandlerSupplier) {
ConnectionBuilder connectionBuilder = ConnectionBuilder.connectionBuilder();
connectionBuilder.connection(connection);//StatefulRedisConnectionImpl
connectionBuilder.clientOptions(clientOptions);
connectionBuilder.clientResources(clientResources);
connectionBuilder.commandHandler(commandHandlerSupplier).endpoint(endpoint);//DefaultEndpoint
// 核心创建Netty 客户端的启动器之Bootstrap
connectionBuilder(getSocketAddressSupplier(redisURI), connectionBuilder, redisURI);
channelType(connectionBuilder, redisURI);
if (clientOptions.isPingBeforeActivateConnection()) {
if (hasPassword(redisURI)) {
connectionBuilder.enableAuthPingBeforeConnect();
} else {
connectionBuilder.enablePingBeforeConnect();
}
}
ConnectionFuture<RedisChannelHandler<K, V>> future = initializeChannelAsync(connectionBuilder);//调用父类
ConnectionFuture<?> sync = future;
if (!clientOptions.isPingBeforeActivateConnection() && hasPassword(redisURI)) {
sync = sync.thenCompose(channelHandler -> {
CommandArgs<K, V> args = new CommandArgs<>(codec).add(redisURI.getPassword());
return connection.async().dispatch(CommandType.AUTH, new StatusOutput<>(codec), args);
});
}
if (LettuceStrings.isNotEmpty(redisURI.getClientName())) {
sync = sync.thenApply(channelHandler -> {
connection.setClientName(redisURI.getClientName());
return channelHandler;
});
}
if (redisURI.getDatabase() != 0) {
sync = sync.thenCompose(channelHandler -> {
CommandArgs<K, V> args = new CommandArgs<>(codec).add(redisURI.getDatabase());
return connection.async().dispatch(CommandType.SELECT, new StatusOutput<>(codec), args);
});
}
return sync.thenApply(channelHandler -> (S) connection);
}
}
public abstract class AbstractRedisClient {
protected <K, V, T extends RedisChannelHandler<K, V>> ConnectionFuture<T> initializeChannelAsync(
ConnectionBuilder connectionBuilder) {
Mono<SocketAddress> socketAddressSupplier = connectionBuilder.socketAddress();//Redis配置的url地址
CompletableFuture<SocketAddress> socketAddressFuture = new CompletableFuture<>();
CompletableFuture<Channel> channelReadyFuture = new CompletableFuture<>();
// 这种方式涉及 rtjava中响应式编程,
socketAddressSupplier.doOnError(socketAddressFuture::completeExceptionally)
.doOnNext(socketAddressFuture::complete)
.subscribe(redisAddress -> {// 启动发布者Mono的流式处理
if (channelReadyFuture.isCancelled()) {
return;
}
initializeChannelAsync0(connectionBuilder, channelReadyFuture, redisAddress);
}, channelReadyFuture::completeExceptionally);
// thenApply:当channelReadyFuture执行完complete方法触发。表明客户单连接正常建立。
// DefaultConnectionFuture也是RedisClient返回连接的最终形式。其中connection是对DefaultEndpoint抽象化的
//StatefulRedisConnectionImpl
return new DefaultConnectionFuture<>(socketAddressFuture, channelReadyFuture.thenApply(channel -> (T) connectionBuilder.connection()));
}
private void initializeChannelAsync0(ConnectionBuilder connectionBuilder, CompletableFuture<Channel>
channelReadyFuture,SocketAddress redisAddress) {
Bootstrap redisBootstrap = connectionBuilder.bootstrap();
//ChannelInitializer的子类 PlainChannelInitializer,包裹客户端全部的channelHandler,其中就包括上述CommandHandler
RedisChannelInitializer initializer = connectionBuilder.build();
//初始化ChannelInitializer,最终会将全部channelHandler绑定到NioSocketChannel的管道pipeline中
redisBootstrap.handler(initializer);
clientResources.nettyCustomizer().afterBootstrapInitialized(redisBootstrap);
// 异步控制Redis客户端连接创建的结果
CompletableFuture<Boolean> initFuture = initializer.channelInitialized();
// 真正驱动Netty组件开始创建客户端连接
ChannelFuture connectFuture = redisBootstrap.connect(redisAddress);
channelReadyFuture.whenComplete((c, t) -> {
if (t instanceof CancellationException) {
connectFuture.cancel(true);
initFuture.cancel(true);
}
});
// 此处的监听器涉及一个任务future。添加监听器的过程其实是将任务future添加到Netty普通队列中
connectFuture.addListener(future -> {
if (!future.isSuccess()) {
connectionBuilder.endpoint().initialState();
channelReadyFuture.completeExceptionally(future.cause());
return;
}
//当Netty逐步处理普通任务队列中所有任务时,会涉及当前任务的执行。
//RedisChannelInitializer中所有handler之channelInactive、userEventTriggered、channelActive、exceptionCaught方法执行都会涉及回调initFuture。执行完毕后继续执行initFuture回调方法whenComplete,根据handler执行结果判断连接建立是否顺利
initFuture.whenComplete((success, throwable) -> {
if (throwable == null) {// Netty建立连接过程中没有异常产生,说明连接建立顺利
// StatefulRedisConnectionImpl
RedisChannelHandler<?, ?> connection = connectionBuilder.connection();
connection.registerCloseables(closeableResources, connection);
channelReadyFuture.complete(connectFuture.channel());//此处触发异步执行任务channelReadyFuture的执行
return;
}
connectionBuilder.endpoint().initialState();
Throwable failure;
if (throwable instanceof RedisConnectionException) {
failure = throwable;
} else if (throwable instanceof TimeoutException) {
failure = new RedisConnectionException("Could not initialize channel within "
+ connectionBuilder.getTimeout(), throwable);
} else {
failure = throwable;
}
channelReadyFuture.completeExceptionally(failure);
});
});
}
}
返回的客户端连接实例为DefaultConnectionFuture,其实内部已经抽象化DefaultEndpoint。能获取到DefaultEndpoint就能获取到Netty从缓存区ByteBuf刷数据到通道channel对应的NioSocketChannel。