赞
踩
使用 JedisPoll 多线程写入时,阶段性报错 broken pipe,重启后任务正常,一段时间后再次出现该报错。
JedisPool 配置如下:
- val config = new JedisPoolConfig
- config.setMaxIdle(20)
- config.setMinIdle(20)
- config.setNumTestsPerEvictionRun(-2)
- config.setTimeBetweenEvictionRunsMillis(30000)
- config.setSoftMinEvictableIdleTimeMillis(3600000)
- config.setMinEvictableIdleTimeMillis(-1)
- config.setTestOnBorrow(false)
- config.setTestOnReturn(false)
- config.setTestWhileIdle(false)
任务很简单,采取 JedisPool 在集群多线程写入数据,该报错会在任务运行一段时间后抛出,做了 Try catch 发现任务是偶发报错,并非全部写入失败;且重启任务后短时间内无该报错。初步分析就是 getResource 方法会存在获取无效连接的情况,从而导致偶发的写入失败,而重启后任务运行正常是因为任务刚启动获取的 redis 连接都有效,从而没有 Broken pipe。
通过配置可以看到下述三个参数设置均为 false:
- config.setTestOnBorrow(false)
- config.setTestOnReturn(false)
- config.setTestWhileIdle(false)
将 TestOnBorrow 设置为 true 或者把 testWhileIdle 设置为 true,如果还不行则都设置为 true。
· setTestOnBorrow
borrow 即从 JedisPool 连接池中获取连接,该参数控制在获取 redis 连接时检查该连接的有效性,如果检查到该链接已失效,则会清理掉并重新获取新连接,这里可以参考源码中 getResource 方法,该方法从 Pool 中执行 borrowObject 方法获取连接,该类所在位置为 redis.client.util.Pool,底层实现参考了 org.apache.commons.pool2.impl.GenericObjectPool。
- public T getResource() {
- try {
- return internalPool.borrowObject();
- } catch (Exception e) {
- throw new JedisConnectionException("Could not get a resource from the pool", e);
- }
- }
配置该参数后,borrow 调用获取新连接时,如果连接失效则会调用 activeObject 方法重置连接内部状态,相当于获取新连接:
- try {
- this.factory.activateObject(p);
- } catch (Exception var15) {
- try {
- this.destroy(p);
- } catch (Exception var14) {
- }
-
- p = null;
- if (create) {
- NoSuchElementException nsee = new NoSuchElementException("Unable to activate object");
- nsee.initCause(var15);
- throw nsee;
- }
- }
· setTestOnReturn
return 是在调用 returnResource 时配置的参数,该参数控制向连接池返回 resource 连接时,检查该连接的有效性,如果该链接已经失效则会清理该连接,该方法从 Pool 中执行 returnResourceObject 方法返还连接,该类所在位置为 redis.client.util.pool,底层实现同样参考 GenericObjectPool。
- public void returnResourceObject(final T resource) {
- if (resource == null) {
- return;
- }
- try {
- internalPool.returnObject(resource);
- } catch (Exception e) {
- throw new JedisException("Could not return the resource to the pool", e);
- }
- }
如果配置该参数后,在 returnObjectResource 时代码会对连接调用 ValidateObject 方法判断有效性,如果无效则 destory :
- if (this.getTestOnReturn() && !this.factory.validateObject(p)) {
- try {
- this.destroy(p);
- } catch (Exception var10) {
- this.swallowException(var10);
- }
-
- try {
- this.ensureIdle(1, false);
- } catch (Exception var9) {
- this.swallowException(var9);
- }
-
- this.updateStatsReturn(activeTime);
- }
· setTestWhileIdle
该参数控制对空闲的连接进行测试。该参数与 config.setTimeBetweenEvictionRunsMillis(30000) 相对应,这里空闲的判断依据即为该参数内设置的毫秒时间。这里检验空闲连接的有效性,真正执行清除的是 evict 方法 :
- public void evict() throws Exception {
- this.assertOpen();
- if (this.idleObjects.size() > 0) {
- PooledObject<T> underTest = null;
- EvictionPolicy<T> evictionPolicy = this.getEvictionPolicy();
- synchronized(this.evictionLock) {
- EvictionConfig evictionConfig = new EvictionConfig(this.getMinEvictableIdleTimeMillis(), this.getSoftMinEvictableIdleTimeMillis(), this.getMinIdle());
- boolean testWhileIdle = this.getTestWhileIdle();
- int i = 0;
- int m = this.getNumTests();
-
- while(true) ...
这里第 7 行还涉及到 MinEvictableIdleTimeMillis 和 SoftMinEvictableIdleTimeMillis 两个时间参数,这里涉及线程池 idleEvictTime 的选取,优先读取前者,如果前者为负数则默认为 maxLong、如果前者未配置则读取后者,所以线程池的一些配置参数会出现 -1,-2 的情况,实际上并不存在 -1 ms 的时间。
- public EvictionConfig(long poolIdleEvictTime, long poolIdleSoftEvictTime, int minIdle) {
- if (poolIdleEvictTime > 0L) {
- this.idleEvictTime = poolIdleEvictTime;
- } else {
- this.idleEvictTime = 9223372036854775807L;
- }
-
- if (poolIdleSoftEvictTime > 0L) {
- this.idleSoftEvictTime = poolIdleSoftEvictTime;
- } else {
- this.idleSoftEvictTime = 9223372036854775807L;
- }
-
- this.minIdle = minIdle;
- }
真正判断开始执行 evict 逻辑是在下述函数:
- public boolean evict(EvictionConfig config, PooledObject<T> underTest, int idleCount) {
- return config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() && config.getMinIdle() < idleCount || config.getIdleEvictTime() < underTest.getIdleTimeMillis();
- }
满足下述任意条件则执行逐出:
A. 连接空闲时间大于 softMinEvictableIdleTimeMillis 且 连接池的空闲数小于 minIdleNum 即 EvictionConfig 内的第三个参数
B. 连接空闲时间大于 minEvictableIdleTimeMillis
当然不会只进行逐出,逐出到一定程度,线程池也会进行 ensureIdle 去新建新的连接,保证线程池中有足够多可用的连接。
上述报错与场景设置 TestOnBorrow = true 和 TestWhileIdle = true 后完美解决,但是也会带来一定的性能损耗,可以根据自己的场景进行调整,一般情况可以尝试只开启 TestOnBorrow。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。