Jedis Unexpected end of stream & java.net.SocketException: Broken pipe问题解决思路

笔者一直维护的稳定基础服务测试环境不稳定了,这能忍!盘他,虽然不必定能彻底盘的了。java

背景:

hrexternal 基础服务对外提供公司员工获取的多个接口,不少接口访问频率比较高,加了缓存,使用的是redis,可是redis最近2个月测试环境已经出问题了,时不时的报错,以前流程平台也报过错,只不过是随机的,不是必现的。当时也是没有具体缘由,只是将底层的redis实例换掉了。而后就行了,这个服务呢因为历史缘由还有不少其余服务是用的同一个redis实例,换的话须要好几个服务一块儿换,保障稳定性。
此次出现的问题更严重,由于每隔几分钟就会报错,get报错,put也会报错。因此就跟进排查了下。
Redis版本:3.0.7
Jedis版本:2.8.0
异常以下:
1573711317108_9EE07B61-20CC-48b1-908A-77D7D12866CB.png
1573711368303_86E3963F-15A1-4138-8101-85C9E14428F5.pnggit

这俩异常不常常遇到,可是一旦遇到确定是比较麻烦的。
笔者也是百度了不少,不少,从下面的连接中了解到一些信息:github

https://blog.csdn.net/aubdiy/article/details/53511410redis

也是按照上面的思路进行排查:apache

1.找DBA帮忙看redis是否有改动配置,没有缓存

2.看超时时间,客户端没有单独设置链接参数,默认超时时间应该是2秒。网络

3.多是网络问题。可是实际上不是。数据结构

4.根据jedis  github上面的issues讨论内容发现具体缘由也没有说出来,可是出现这个问题的人确实挺多的。解决的人基本上都加了Jedis的链接配置了,恰好咱们的没有加,还有可能解决。架构

这里就揭开了针对于Jedis配置的一场探索之路。
首先看这个hrexternal服务的jedis初始化代码:测试

/**
     *  初始化资源池
     */
    static {
        try {
            if (jedisSentinelPool ==null) {
              logger.info("init JedisSentinelPool is start....");
                logger.info("redis_ip1:"+RedisConfig.redis_ip1+",redis_port1:"+RedisConfig.redis_port1);
                logger.info("redis_ip2:"+RedisConfig.redis_ip2+",redis_ip2:"+RedisConfig.redis_port2);
                logger.info("redis_ip3:"+RedisConfig.redis_ip3+",redis_ip2:"+RedisConfig.redis_port3);
              Set<String> sentinels = new HashSet<String>();
              sentinels.add(new HostAndPort(RedisConfig.redis_ip1, Integer.parseInt(RedisConfig.redis_port1)).toString());
              sentinels.add(new HostAndPort(RedisConfig.redis_ip2, Integer.parseInt(RedisConfig.redis_port2)).toString());
              sentinels.add(new HostAndPort(RedisConfig.redis_ip3, Integer.parseInt(RedisConfig.redis_port3)).toString());
              jedisSentinelPool = new JedisSentinelPool(RedisConfig.master, sentinels);
              logger.info(" init JedisSentinelPool is end....");
            }
        }catch(Exception e){
              logger.error("---->init JedisSentinelPool was failed,the msg is " + e.getMessage(), e);
        }
    }
    

    /**
     * 获取资源
     * @return
     * @throws Exception
     */
    public static synchronized Jedis getJedis() throws Exception {
        try {
            if(jedisSentinelPool != null) {
                Jedis e = jedisSentinelPool.getResource();
                return e;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e);
            return null;
        }
    }

使用的是Jedis哨兵模式进行Jedis初始化,同时使用Jedis链接池。出现上面的异常不少缘由都跟链接池的链接有关。所以有必要分析一下Jedis的链接池和链接配置参数,以下图是Jedis链接配置参数和Jedis的链接池对象的类图:
Pool.png
其中只有GenericObjectPoolConfig,BaseObjectPoolConfig不是Jedis中的类,其余都是。这俩类是jedis依赖的另外一个jar包:

<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.2</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>

这个包是否是看着既熟悉又陌生。这个居然是java对象池池化技术的一个实现,相关文章以下:

http://www.javashuo.com/article/p-exmsbwzy-gd.html

固然本文的分析内容也包括这个,其中Jedis的一些配置参数也跟这个池化对象配置有关。

下面是我整理的一个配置参数介绍:

  1. maxTotal:程序容许建立资源的最大数量;默认值 -1,-1 表明无数量限制(int类型)
  2. blockWhenExhausted:当资源耗尽时,是否阻塞等待获取资源;默认值 true
  3. maxWaitMillis: 获取资源时的等待时间,单位毫秒。当blockWhenExhausted 配置为 true 时,此值有效。 -1 表明无时间限制,一直阻塞直到有可用的资源。(long类型)
  4. testOnBorrow: 否在从池中取出链接前进行检验,若是检验失败,则从池中去除链接并尝试取出另外一个;默认值 false ,当设置为true时,调用 factory.validateObject() 方法
  5. testOnCreate 建立连接的时候进行连接有效性检查; 默认值 false,当设置为true时,调用 factory.validateObject() 方法(备注:若是 testOnBorrow 或者 testOnCreate 中有一个 配置 为 true 时,就调用 factory.validateObject() )
  6. lifo 资源的存取数据结构,默认值 true,true 资源按照栈结构存取,false 资源按照队列结构存取
  7. fairness 当从池中获取资源或者将资源还回池中时 是否使用 java.util.concurrent.locks.ReentrantLock.ReentrantLock 的公平锁机制。 默认值 false, true 使用公平锁,false 不使用公平锁,
  8. timeBetweenEvictionRunsMillis 回收资源线程的执行周期,单位毫秒。默认值 -1 ,-1 表示不启用线程回收资源。(long类型)
  9. evictionPolicyClassName 资源回收策略, 默认值org.apache.commons.pool2.impl.DefaultEvictionPolicy(String类型)
  10. minEvictableIdleTimeMillis 链接在池中保持空闲而不被空闲链接回收器线程(若是有)回收的最小时间值; 默认值 1800000,单位 毫秒(long类型 )
  11. softMinEvictableIdleTimeMillis 软资源最小空闲时间, 默认值 -1 ,单位 毫秒,(long类型 )(备注,这个两个参数,在资源回收策略中,会使用到)
  12. maxIdle 最大空闲资源数,默认值 8 (int类型)
  13. minIdle 最小空闲资源数,默认值 0 (int类型 )
  14. testWhileIdle 指明链接是否被空闲链接回收器(若是有)进行检验.若是检测失败,则链接将被从池中去除;默认值 false; 设置为 true 时,当回收策略返回false时,则 调用 factory.activateObject()和factory.validateObject()
  15. testOnReturn 默认值 false; 设置为 true 时,当将资源返还个资源池时候,验证资源的有效性,调用 factory.validateObject()方法,若是无效,则调用 factory.destroyObject()方法
  16. numTestsPerEvictionRun 资源回收线程执行一次回收操做,回收资源的数量。默认值 3, (int类型)。
    备注:当 设置为0时,不回收资源。
    设置为 小于0时,回收资源的个数为 (int)Math.ceil( 池中空闲资源个数 / Math.abs(numTestsPerEvictionRun) );设置为 大于0时,回收资源的个数为 Math.min( numTestsPerEvictionRun,池中空闲的资源个数 );

因为上面代码的配置是使用默认的参数,也就是说当连接出现问题的时候你是不知道是客户端出的问题仍是服务端出的问题,跟DBA确认了一些服务端的参数:
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 512mb 128mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
timeout 60 配置的60s。
因为服务端没有动配置,客户端没有动配置,也没有动代码。封装Jedis操做的每一个API都检查了,最后都有finally代码块保证jedis用完会close.
不存在连接泄露问题。那为啥上面的错会发生?为啥稳定运行了很长时间最近才报错。
固然几个可能的方向

  1. 这个Redis实例被不少服务共享,致使数据错乱或者Redis连接有问题。
  2. Jedis配置问题
  3. 版本问题。
    当我设置了jedis连接池参数以后就不会出现上面的异常了,配置代码以下:
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
                jedisPoolConfig.setTestOnBorrow(true);
                jedisPoolConfig.setTestOnReturn(true);
                jedisPoolConfig.setTestOnCreate(true);
                jedisPoolConfig.setMaxTotal(50);
                jedisPoolConfig.setMaxIdle(10);
                jedisPoolConfig.setMinIdle(1);
                jedisPoolConfig.setMaxWaitMillis(3000);
jedisSentinelPool = new JedisSentinelPool(RedisConfig.master, sentinels,jedisPoolConfig);

部署完以后,发现异常再也不出现。
虽然具体缘由没有找到可是经过jedis开源代码和issues能够获得一些结论:
https://github.com/xetorthio/jedis/issues/932
https://blog.csdn.net/SakuraInLuoJia/article/details/89874287

也就是说有2点建议

  1. 不建议用Jedis默认的连接池配置,须要根据本身的须要在构造Jedis连接池的时候传入连接池配置。
  2. 将客户端版本与服务端版本尽可能保持一致。
    固然若是你遇到这种问题的话,经过上面的方式仍是搞不定,说明你没有找到正确的配置。即便有另外一份配置放在你面前,它可能也不能解决你的问题,但至少是多了一种尝试。

    本文由博客一文多发平台 OpenWrite 发布! 架构设计@工程设计@服务稳定性之路

相关文章
相关标签/搜索