GenericObjectPool中borrowObject
public T borrowObject(final long borrowMaxWaitMillis) throws Exception { //省略部分代码 PooledObject<T> p = null; // Get local copy of current config so it is consistent for entire // method execution final boolean blockWhenExhausted = getBlockWhenExhausted(); boolean create; final long waitTime = System.currentTimeMillis(); while (p == null) { create = false; p = idleObjects.pollFirst(); if (p == null) { p = create(); if (p != null) { create = true; } } //省略部分代码 } updateStatsBorrow(p, System.currentTimeMillis() - waitTime); return p.getObject(); }
释放连接的流程图如下:
看下关键代码
GenericObjectPool中释放连接代码
public void returnObject(final T obj) { //省略部分代码 final int maxIdleSave = getMaxIdle(); if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) { try { destroy(p); } catch (final Exception e) { swallowException(e); } } else { if (getLifo()) { idleObjects.addFirst(p); } else { idleObjects.addLast(p); } if (isClosed()) { // Pool closed while object was being added to idle objects. // Make sure the returned object is destroyed rather than left // in the idle object pool (which would effectively be a leak) clear(); } } updateStatsReturn(activeTime); }
RedisChannalHandler中的close方法
public void close() { //省略部分代码 closeAsync().join(); } public CompletableFuture<Void> closeAsync() { //省略部分代码 if (CLOSED.compareAndSet(this, ST_OPEN, ST_CLOSED)) { active = false; CompletableFuture<Void> future = channelWriter.closeAsync(); //省略部分代码 } return closeFuture; }
DefaultEndpoint类的closeAsync
public CompletableFuture<Void> closeAsync() { //省略部分代码 if (STATUS.compareAndSet(this, ST_OPEN, ST_CLOSED)) { Channel channel = getOpenChannel(); if (channel != null) { Futures.adapt(channel.close(), closeFuture); } else { closeFuture.complete(null); } } return closeFuture; }
actuator健康检查获取连接
我们知道,springboot的actuator健康检查是实现了ReactiveHealthIndicator接口,如果springboot工程启用了actuator,在lettuce初始化时,会创建一个reactive的连接,UML类图如下:
RedisReactiveHealthIndicator类会调用RedisConnectionFactory来创建一个reactive连接,代码如下:
protected Mono<Health> doHealthCheck(Health.Builder builder) { //getConnection()创建一个连接 return getConnection().flatMap((connection) -> doHealthCheck(builder, connection)); } public LettuceReactiveRedisConnection getReactiveConnection() { //下面的构造函数会创建交互式连接 return getShareNativeConnection() ? new LettuceReactiveRedisConnection(getSharedReactiveConnection(), reactiveConnectionProvider) : new LettuceReactiveRedisConnection(reactiveConnectionProvider); } LettuceReactiveRedisConnection(StatefulConnection<ByteBuffer, ByteBuffer> sharedConnection, LettuceConnectionProvider connectionProvider) { Assert.notNull(sharedConnection, "Shared StatefulConnection must not be null!"); Assert.notNull(connectionProvider, "LettuceConnectionProvider must not be null!"); //调用AsyncConnect构造函数创建连接方法 this.dedicatedConnection = new AsyncConnect(connectionProvider, StatefulConnection.class); this.pubSubConnection = new AsyncConnect(connectionProvider, StatefulRedisPubSubConnection.class); this.sharedConnection = Mono.just(sharedConnection); } AsyncConnect(LettuceConnectionProvider connectionProvider, Class<? extends T> connectionType) { Assert.notNull(connectionProvider, "LettuceConnectionProvider must not be null!"); this.connectionProvider = connectionProvider; //回到了之前讲的使用connectionProvider创建连接 Mono<T> defer = Mono.defer(() -> Mono.<T> just(connectionProvider.getConnection(connectionType))); this.connectionPublisher = defer.subscribeOn(Schedulers.elastic()); }
释放不掉的连接
有时候我们为了节省创建连接花费的时间,会设置min-idle,但其实lettuce初始化时并不会创建这个数量的连接,除非我们设置一个参数spring.redis.lettuce.pool.time-between-eviction-runs=1,
而这样lettuce在初始化的时候因为使用了actuator做健康检查而创建${min-idle} + 1个reactive连接,并不会创建普通连接,只有在第一次请求的时候才会创建${min-idle} + 1个普通连接。
如果没有交互式场景,这些交互式连接不会被释放,造成资源浪费。所以如果使用了actuator监控检查,而又想初始化时创建一定数量的连接,只能造成连接资源浪费了。
为什么要这么设计,有点不明白,可能是bug?没顾上看后面的版本有没有处理这个问题。看下UML类图,从这个流程图看到,time-between-eviction-runs这个参数决定了是否初始化的时候创建${min-idle} + 1个连接池
上面关键代码就是GenericObjectPool类中的ensureMinIdle方法,在释放连接的时候也会调用这个方法,代码如下:
private void ensureIdle(final int idleCount, final boolean always) throws Exception { //省略部分代码 while (idleObjects.size() < idleCount) { final PooledObject<T> p = create(); if (p == null) { // Can't create objects, no reason to think another call to // create will work. Give up. break; } if (getLifo()) { idleObjects.addFirst(p); } else { idleObjects.addLast(p); } } if (isClosed()) { // Pool closed while object was being added to idle objects. // Make sure the returned object is destroyed rather than left // in the idle object pool (which would effectively be a leak) clear(); } }
那为什么会比min-idle多创建一个连接呢?问题还在于上面的一个方法。初始化的流程如下:
1.健康检查需要创建一个reactive连接
protected Mono<Health> doHealthCheck(Health.Builder builder) { return getConnection().flatMap((connection) -> doHealthCheck(builder, connection)); }
2.之前介绍过,创建连接实际是用LettucePoolConnectionProvider的getConnection方法
public <T extends StatefulConnection<?, ?>> T getConnection(Class<T> connectionType) { GenericObjectPool<StatefulConnection<?, ?>> pool = pools.computeIfAbsent(connectionType, poolType -> { return ConnectionPoolSupport.createGenericObjectPool(() -> connectionProvider.getConnection(connectionType), poolConfig, false); }); //省略部分代码 }}
3.调用了ConnectionPoolSupport.createGenericObjectPool
public static <T extends StatefulConnection<?, ?>> GenericObjectPool<T> createGenericObjectPool( Supplier<T> connectionSupplier, GenericObjectPoolConfig config, boolean wrapConnections) { //省略部分代码 GenericObjectPool<T> pool = new GenericObjectPool<T>(new RedisPooledObjectFactory<T>(connectionSupplier), config) { //省略部分代码 }; poolRef.set(new ObjectPoolWrapper<>(pool)); return pool; }
4.ConnectionPoolSupport.createGenericObjectPool方法创建GenericObjectPool对象,构造函数里面用到了前面讲的setConfig
public GenericObjectPool(final PooledObjectFactory<T> factory, final GenericObjectPoolConfig<T> config) { //省略部分代码 setConfig(config); }
5.setConfig最终调用了上面讲的ensureIdle,而健康检查的那个连接还没有返还给线程池,线程池的数量已经是min-idle了,最终多了一个
同理,普通连接也是一样,首次创建的时候会比min-idle多一个
共享连接
第一部分介绍springboot整合lettuce时讲到RedisConfig的配置,如下方法里面第一行代码就是设置时是否共享Native连接。
@Bean RedisTemplate redisTemplate(LettuceConnectionFactory factory){ factory.setShareNativeConnection(false); RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(factory); return redisTemplate; }
这个主要用于获取集群中的连接或者是获取Reactive连接时,可以用LettuceConnectionFactory中直接获取。我对这个地方的设计并不是特别理解,只是为了省去了从连接池获取和释放的的时间?
总结
lettuce的确很香,不过从设计中也可以看出一些瑕疵
如果应用使用了springboot的actuator,建议min-idle设置为0