Redis连接池

194 阅读6分钟

1 与yaml文件对应的配置类

  • org.springframework.boot.autoconfigure.data.redis.RedisProperties

image.png

jedis/lettuce 都是对同一个对象Pool的封装

image.png

/**
 * Jedis client properties.
 */
public static class Jedis {

    /**
     * Jedis pool configuration.
     */
    private Pool pool;

    public Pool getPool() {
       return this.pool;
    }

    public void setPool(Pool pool) {
       this.pool = pool;
    }

}

/**
 * Lettuce client properties.
 */
public static class Lettuce {

    /**
     * Shutdown timeout.
     */
    private Duration shutdownTimeout = Duration.ofMillis(100);

    /**
     * Lettuce pool configuration.
     */
    private Pool pool;

    public Duration getShutdownTimeout() {
       return this.shutdownTimeout;
    }

    public void setShutdownTimeout(Duration shutdownTimeout) {
       this.shutdownTimeout = shutdownTimeout;
    }

    public Pool getPool() {
       return this.pool;
    }

    public void setPool(Pool pool) {
       this.pool = pool;
    }

}
  • 关于Pool的配置参数

    /**
     connection的最大空闲数量,负数时意味着对其没有限制
     */
    private int maxIdle = 8;

    /**
     connection的最小空闲数量,有效使用的前提条件--
     time between eviction runs(退出机制检查周期时间必须大于0)
     */
    private int minIdle = 0;

    /**
     最大的可用connection数量 ,负数时,表示没有限制
     */
    private int maxActive = 8;

    /**
     连接池被已经全部占用的时候,想要分配一个新的connection时,最长的等待时间。
     超过指定时间,抛出异常。负数的时候,意味着一直等待。
     */
    private Duration maxWait = Duration.ofMillis(-1);

    /**
     退出机制检查周期时间,大于0时,才执行
     */
    private Duration timeBetweenEvictionRuns;

2 注入到容器中的配置类

org.springframework.boot.autoconfigure.data.redis.JedisConnectionConfiguration

image.png

该配置类 引用了 RedisProperties

还有一个可拓展的自定义配置对象 JedisClientConfigurationBuilderCustomizer

关键的方法

创建一个JedisPoolConfig对象
private JedisPoolConfig jedisPoolConfig(RedisProperties.Pool pool) {
    JedisPoolConfig config = new JedisPoolConfig();
    config.setMaxTotal(pool.getMaxActive());
    config.setMaxIdle(pool.getMaxIdle());
    config.setMinIdle(pool.getMinIdle());
    if (pool.getTimeBetweenEvictionRuns() != null) {
       config.setTimeBetweenEvictionRunsMillis(pool.getTimeBetweenEvictionRuns().toMillis());
    }
    if (pool.getMaxWait() != null) {
       config.setMaxWaitMillis(pool.getMaxWait().toMillis());
    }
    return config;
}


public class JedisPoolConfig extends GenericObjectPoolConfig {
  // new JedisPoolConfig(); 实际上是自带一些属性配置的
  // 从父类BaseObjectPoolConfig继承而来 都涉及到Pool(池)的运行机制
  public JedisPoolConfig() {
    // testWhileIdle 连接池中空闲的对象 在退出机制进行扫描检查的时候 
    // 是否应该进行有效性检查 
    setTestWhileIdle(true);
    // 退出机制运行时 池中一个idle对象成为可以被清除的最低时间门槛 
    // 小于等于0时 对idle对象不会进行清除 
    setMinEvictableIdleTimeMillis(60000);
    setTimeBetweenEvictionRunsMillis(30000);
    // 退出机制运行的时候 需要检查池中idle对象的数量
    setNumTestsPerEvictionRun(-1);
  }
}

testWhileIdle的校验逻辑 取自于org.apache.commons.pool2.PooledObjectFactory#validateObject

image.png

直接查看JedisFactory

// 检查连接数据 host port 然后ping pong 响应 是否完成
@Override
public boolean validateObject(PooledObject<Jedis> pooledJedis) {
  final BinaryJedis jedis = pooledJedis.getObject();
  try {
    HostAndPort hostAndPort = this.hostAndPort.get();

    String connectionHost = jedis.getClient().getHost();
    int connectionPort = jedis.getClient().getPort();

    return hostAndPort.getHost().equals(connectionHost)
        && hostAndPort.getPort() == connectionPort && jedis.isConnected()
        && jedis.ping().equals("PONG");
  } catch (final Exception e) {
    return false;
  }
}

退出机制运行时 idle池中需要检查的数量

private int getNumTests() {
    final int numTestsPerEvictionRun = getNumTestsPerEvictionRun();
    非负数的时候,只会检查其中的一部分
    if (numTestsPerEvictionRun >= 0) {
        return Math.min(numTestsPerEvictionRun, idleObjects.size());
    }
    -1的时候 会检查所有 
    return (int) (Math.ceil(idleObjects.size() /
            Math.abs((double) numTestsPerEvictionRun)));
}

3 退出机制

源自org.apache.commons.pool2.impl.BaseGenericObjectPool.Evictor

是一个后台定时任务

class Evictor implements Runnable {

    private ScheduledFuture<?> scheduledFuture;

    /**
     * Run pool maintenance.  Evict objects qualifying for eviction and then
     * ensure that the minimum number of idle instances are available.
     * Since the Timer that invokes Evictors is shared for all Pools but
     * pools may exist in different class loaders, the Evictor ensures that
     * any actions taken are under the class loader of the factory
     * associated with the pool.
     */
    @Override
    public void run() {
        final ClassLoader savedClassLoader =
                Thread.currentThread().getContextClassLoader();
        try {
            if (factoryClassLoader != null) {
                // Set the class loader for the factory
                final ClassLoader cl = factoryClassLoader.get();
                if (cl == null) {
                    // The pool has been dereferenced and the class loader
                    // GC'd. Cancel this timer so the pool can be GC'd as
                    // well.
                    cancel();
                    return;
                }
                Thread.currentThread().setContextClassLoader(cl);
            }

            // Evict from the pool
            try {
                // 这是主要的逻辑处理
                evict();
            } catch(final Exception e) {
                swallowException(e);
            } catch(final OutOfMemoryError oome) {
                // Log problem but give evictor thread a chance to continue
                // in case error is recoverable
                oome.printStackTrace(System.err);
            }
            // Re-create idle instances. 确保最小的空闲数 有可能清退的idle过多
            try {
                ensureMinIdle();
            } catch (final Exception e) {
                swallowException(e);
            }
        } finally {
            // Restore the previous CCL
            Thread.currentThread().setContextClassLoader(savedClassLoader);
        }
    }
}

退出逻辑

默认的退出策略

public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> {
    
    // config是退出机制的配置
    // underTest 是池中的ilde对象
    // idleCount 池中的idle数量
    @Override
    public boolean evict(final EvictionConfig config, final PooledObject<T> underTest, final int idleCount) {
         // 1 池中对象的空闲时间 > 退出机制设定sofe退出时间的门槛
         // 2 池中的空闲数量 > 退出机制中的设定
         // 3 池中对象的空闲时间 > 退出机制设定退出时间的门槛
         // 满足之一 就达到了退出条件
        if ((config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() &&
                config.getMinIdle() < idleCount) ||
                config.getIdleEvictTime() < underTest.getIdleTimeMillis()) {
            return true;
        }
        return false;
    }
}

public EvictionConfig(final long poolIdleEvictTime, final long poolIdleSoftEvictTime,
        final int minIdle) {
        
    if (poolIdleEvictTime > 0) {
        idleEvictTime = poolIdleEvictTime;
    } else {
        // 当poolIdleEvictTime<=0 时 池中idle对象将不会退出
        idleEvictTime = Long.MAX_VALUE;
    }
    if (poolIdleSoftEvictTime > 0) {
        idleSoftEvictTime = poolIdleSoftEvictTime;
    } else {
        // 当idleSoftEvictTime<=0 时 池中idle对象将不会退出
        idleSoftEvictTime  = Long.MAX_VALUE;
    }
    //欲想池中退出机制 只跟空闲数 相关 那么前两个只能配置成非正数
    this.minIdle = minIdle;
}

退出代码注解

public void evict() throws Exception {
    
    // 确保池处于正确的状态
    assertOpen();
    
    // 要有空闲对象存在
    if (idleObjects.size() > 0) {

        PooledObject<T> underTest = null;
        
        // 退出策略
        final EvictionPolicy<T> evictionPolicy = getEvictionPolicy();

        
        synchronized (evictionLock) {
            
            // 退出的三个条件 两个时间 一个数量
            final EvictionConfig evictionConfig = new EvictionConfig(
                    getMinEvictableIdleTimeMillis(),
                    getSoftMinEvictableIdleTimeMillis(),
                    getMinIdle());
            
            // 在ilde对象激活后 是否对其进行检查
            final boolean testWhileIdle = getTestWhileIdle();

            // 根据需要检查的数量 来进行循环
            for (int i = 0, m = getNumTests(); i < m; i++) {
            
                // 获取迭代器
                if (evictionIterator == null || !evictionIterator.hasNext()) {
                    evictionIterator = new EvictionIterator(idleObjects);
                }
                
                // 没有则退出
                if (!evictionIterator.hasNext()) {
                    // Pool exhausted, nothing to do here
                    return;
                }

                // 获取空闲对象
                try {
             
                    underTest = evictionIterator.next();
                } catch (final NoSuchElementException nsee) {
                    // Object was borrowed in another thread
                    // Don't count this as an eviction test so reduce i;
                    i--;
                    evictionIterator = null;
                    continue;
                }
                
                // idle对象的状态检查 是否处于idle 如果是 则需要先置为退出状态 EVICTION
                if (!underTest.startEvictionTest()) {
                    // Object was borrowed in another thread
                    // Don't count this as an eviction test so reduce i;
                    i--;
                    continue;
                }

                // User provided eviction policy could throw all sorts of
                // crazy exceptions. Protect against such an exception
                // killing the eviction thread.
                boolean evict;
                try {
                    // 判断是否应该退出
                    evict = evictionPolicy.evict(evictionConfig, underTest,
                            idleObjects.size());
                } catch (final Throwable t) {
                    // Slightly convoluted as SwallowedExceptionListener
                    // uses Exception rather than Throwable
                    PoolUtils.checkRethrow(t);
                    swallowException(new Exception(t));
                    // Don't evict on error conditions
                    evict = false;
                }

                if (evict) {
                    // 可以退出时 销毁对象 修正统计数量
                    destroy(underTest);
                    destroyedByEvictorCount.incrementAndGet();
                } else {
                    // 不能退出的时候 进行检查
                    if (testWhileIdle) {
                        boolean active = false;
                        try {
                            // 先激活
                            factory.activateObject(underTest);
                            active = true;
                        } catch (final Exception e) {
                            destroy(underTest);
                            destroyedByEvictorCount.incrementAndGet();
                        }
                        if (active) {
                            // 有效性检查 不合格 则销毁
                            if (!factory.validateObject(underTest)) {
                                destroy(underTest);
                                destroyedByEvictorCount.incrementAndGet();
                            } else {
                                try {
                                    // 返回池中 继续使用
                                    factory.passivateObject(underTest);
                                } catch (final Exception e) {
                                    destroy(underTest);
                                    destroyedByEvictorCount.incrementAndGet();
                                }
                            }
                        }
                    }
                    // 结束退出 还原状态成IDLE
                    if (!underTest.endEvictionTest(idleObjects)) {
                        // TODO - May need to add code here once additional
                        // states are used
                    }
                }
            }
        }
    }
    final AbandonedConfig ac = this.abandonedConfig;
    if (ac != null && ac.getRemoveAbandonedOnMaintenance()) {
        removeAbandoned(ac);
    }
}

确保最小ilde数量满足配置

private void ensureIdle(final int idleCount, final boolean always) throws Exception {
    
    if (idleCount < 1 || isClosed() || (!always && !idleObjects.hasTakeWaiters())) {
        return;
    }

    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;
        }
        // 这里涉及到了 后进先出(lifo) 还是 先进先出(fifo)的选择
        if (getLifo()) { 
        // 后进先出 --> 最近使用的对象(connection)-->
        // 缺点就是会出现薪资倒挂的现象 也即是后来居上 只闻新人笑 哪闻旧人哭 
        // 也就说会出现 旧的ilde对象 迟迟无法被重新使用 大概率会走向被销毁的命途
            idleObjects.addFirst(p);
        } else {
        // 先进先出 --> 最早使用的对象(connection)--> 相对公平
            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();
    }
}

JedisClientConfigurationBuilderCustomizer

image.png

这里的custom 方法在何时应用? 当然是容器加载的时候,通过config对象 @Bean注入

image.png

private void customize(JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
    this.builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
}

涉及到的是一个ConfigBuilder

org.springframework.data.redis.connection.jedis.JedisClientConfiguration.DefaultJedisClientConfigurationBuilder

默认的ConfigBuilder 如下图

image.png

真实应用样例

Provide more configuration properties for Jedis's connection pool · Issue #33814 · spring-projects/spring-boot · GitHub

参考链接

  1. mp.weixin.qq.com/s/trbGNYZPE…