当前位置:   article > 正文

JedisPool - Java.net.SocketException: Broken pipe (write failed)_jedisconnectionexception: java.net.socketexception

jedisconnectionexception: java.net.socketexception: broken pipe (write faile

一.引言

 

使用 JedisPoll 多线程写入时,阶段性报错 broken pipe,重启后任务正常,一段时间后再次出现该报错。

JedisPool 配置如下:

  1. val config = new JedisPoolConfig
  2. config.setMaxIdle(20)
  3. config.setMinIdle(20)
  4. config.setNumTestsPerEvictionRun(-2)
  5. config.setTimeBetweenEvictionRunsMillis(30000)
  6. config.setSoftMinEvictableIdleTimeMillis(3600000)
  7. config.setMinEvictableIdleTimeMillis(-1)
  8. config.setTestOnBorrow(false)
  9. config.setTestOnReturn(false)
  10. config.setTestWhileIdle(false)

二.问题分析与解决

1.问题场景

任务很简单,采取 JedisPool 在集群多线程写入数据,该报错会在任务运行一段时间后抛出,做了 Try catch 发现任务是偶发报错,并非全部写入失败;且重启任务后短时间内无该报错。初步分析就是 getResource 方法会存在获取无效连接的情况,从而导致偶发的写入失败,而重启后任务运行正常是因为任务刚启动获取的 redis 连接都有效,从而没有 Broken pipe。

2.问题解决

通过配置可以看到下述三个参数设置均为 false:

  1. config.setTestOnBorrow(false)
  2. config.setTestOnReturn(false)
  3. config.setTestWhileIdle(false)

将 TestOnBorrow 设置为 true 或者把 testWhileIdle 设置为 true,如果还不行则都设置为 true。

3.参数含义

· setTestOnBorrow

borrow 即从 JedisPool 连接池中获取连接,该参数控制在获取 redis 连接时检查该连接的有效性,如果检查到该链接已失效,则会清理掉并重新获取新连接,这里可以参考源码中 getResource 方法,该方法从 Pool 中执行 borrowObject 方法获取连接,该类所在位置为 redis.client.util.Pool,底层实现参考了 org.apache.commons.pool2.impl.GenericObjectPool。

  1. public T getResource() {
  2. try {
  3. return internalPool.borrowObject();
  4. } catch (Exception e) {
  5. throw new JedisConnectionException("Could not get a resource from the pool", e);
  6. }
  7. }

配置该参数后,borrow 调用获取新连接时,如果连接失效则会调用 activeObject 方法重置连接内部状态,相当于获取新连接:

  1. try {
  2. this.factory.activateObject(p);
  3. } catch (Exception var15) {
  4. try {
  5. this.destroy(p);
  6. } catch (Exception var14) {
  7. }
  8. p = null;
  9. if (create) {
  10. NoSuchElementException nsee = new NoSuchElementException("Unable to activate object");
  11. nsee.initCause(var15);
  12. throw nsee;
  13. }
  14. }

· setTestOnReturn

return 是在调用 returnResource 时配置的参数,该参数控制向连接池返回 resource 连接时,检查该连接的有效性,如果该链接已经失效则会清理该连接,该方法从 Pool 中执行 returnResourceObject 方法返还连接,该类所在位置为 redis.client.util.pool,底层实现同样参考 GenericObjectPool。

  1. public void returnResourceObject(final T resource) {
  2. if (resource == null) {
  3. return;
  4. }
  5. try {
  6. internalPool.returnObject(resource);
  7. } catch (Exception e) {
  8. throw new JedisException("Could not return the resource to the pool", e);
  9. }
  10. }

如果配置该参数后,在 returnObjectResource 时代码会对连接调用 ValidateObject 方法判断有效性,如果无效则 destory :

  1. if (this.getTestOnReturn() && !this.factory.validateObject(p)) {
  2. try {
  3. this.destroy(p);
  4. } catch (Exception var10) {
  5. this.swallowException(var10);
  6. }
  7. try {
  8. this.ensureIdle(1, false);
  9. } catch (Exception var9) {
  10. this.swallowException(var9);
  11. }
  12. this.updateStatsReturn(activeTime);
  13. }

· setTestWhileIdle

该参数控制对空闲的连接进行测试。该参数与 config.setTimeBetweenEvictionRunsMillis(30000) 相对应,这里空闲的判断依据即为该参数内设置的毫秒时间。这里检验空闲连接的有效性,真正执行清除的是 evict 方法 :

  1. public void evict() throws Exception {
  2. this.assertOpen();
  3. if (this.idleObjects.size() > 0) {
  4. PooledObject<T> underTest = null;
  5. EvictionPolicy<T> evictionPolicy = this.getEvictionPolicy();
  6. synchronized(this.evictionLock) {
  7. EvictionConfig evictionConfig = new EvictionConfig(this.getMinEvictableIdleTimeMillis(), this.getSoftMinEvictableIdleTimeMillis(), this.getMinIdle());
  8. boolean testWhileIdle = this.getTestWhileIdle();
  9. int i = 0;
  10. int m = this.getNumTests();
  11. while(true) ...

这里第 7 行还涉及到 MinEvictableIdleTimeMillis 和 SoftMinEvictableIdleTimeMillis 两个时间参数,这里涉及线程池 idleEvictTime 的选取,优先读取前者,如果前者为负数则默认为 maxLong、如果前者未配置则读取后者,所以线程池的一些配置参数会出现 -1,-2 的情况,实际上并不存在 -1 ms 的时间。

  1. public EvictionConfig(long poolIdleEvictTime, long poolIdleSoftEvictTime, int minIdle) {
  2. if (poolIdleEvictTime > 0L) {
  3. this.idleEvictTime = poolIdleEvictTime;
  4. } else {
  5. this.idleEvictTime = 9223372036854775807L;
  6. }
  7. if (poolIdleSoftEvictTime > 0L) {
  8. this.idleSoftEvictTime = poolIdleSoftEvictTime;
  9. } else {
  10. this.idleSoftEvictTime = 9223372036854775807L;
  11. }
  12. this.minIdle = minIdle;
  13. }

真正判断开始执行 evict 逻辑是在下述函数:

  1. public boolean evict(EvictionConfig config, PooledObject<T> underTest, int idleCount) {
  2. return config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() && config.getMinIdle() < idleCount || config.getIdleEvictTime() < underTest.getIdleTimeMillis();
  3. }

满足下述任意条件则执行逐出:

A. 连接空闲时间大于 softMinEvictableIdleTimeMillis 且 连接池的空闲数小于 minIdleNum 即 EvictionConfig 内的第三个参数

B. 连接空闲时间大于 minEvictableIdleTimeMillis 

当然不会只进行逐出,逐出到一定程度,线程池也会进行 ensureIdle 去新建新的连接,保证线程池中有足够多可用的连接。

三.总结

上述报错与场景设置 TestOnBorrow = true 和 TestWhileIdle = true 后完美解决,但是也会带来一定的性能损耗,可以根据自己的场景进行调整,一般情况可以尝试只开启 TestOnBorrow。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/160134
推荐阅读
相关标签
  

闽ICP备14008679号