1 与yaml文件对应的配置类
- org.springframework.boot.autoconfigure.data.redis.RedisProperties
jedis/lettuce 都是对同一个对象Pool的封装
/**
* 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
该配置类 引用了 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
直接查看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
这里的custom 方法在何时应用? 当然是容器加载的时候,通过config对象 @Bean注入
private void customize(JedisClientConfiguration.JedisClientConfigurationBuilder builder) {
this.builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
}
涉及到的是一个ConfigBuilder
org.springframework.data.redis.connection.jedis.JedisClientConfiguration.DefaultJedisClientConfigurationBuilder
默认的ConfigBuilder 如下图