JedisPool连接池相关配置


最近有些其他业务部门的同学在线上环境redis有出现以下错误Unexpected end of stream,这个错误大致是因为,redis服务器端已经关闭了客户端的连接,而客户端不知道依然拿着原来的连接去访问redis服务器,结果就会报出这个exception。既然我们知道原因是服务器端主动关闭与客户端连接,那么我们下面看下有哪些情况会导致服务器端主动关闭连接。主要在redis.conf中有以下两个参数client-output-buffer-limit、timeout,下面我们分别来介绍下这两个参数的作用。

client-output-buffer-limit

对于Redis服务器的输出(也就是命令的返回值)来说,其大小通常是不可控制的。有可能一个简单的命令,能够产生体积庞大的返回数据。另外也有可能因为执行了太多命令,导致产生返回数据的速率超过了往客户端发送的速率,这是也会导致服务器堆积大量消息,从而导致输出缓冲区越来越大,占用过多内存,甚至导致系统崩溃。redis有以下两种限制来避免出现这种情况,并且不同类型的客户端可以采取不同的配置。

  1. 大小限制:若客户端缓冲区超过某一个设定的值,直接关闭客户端连接。
  2. 持续性限制:若客户端缓冲区连续一定时间内一直超过某个设定的值时,则关闭客户端连接。

下面是一个配置的demo:

client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 8mb 2mb 60

redis把客户端分为3种,一种是普通的客户端,比如应用程序获取的jedis连接、普通的redis-cli,上面配置的三个0意思是对于客户端缓冲区无限制;第二种slave客户端,上面配置的3个参数的意思是若缓冲区超过256m则redis服务器直接关闭客户端连接,或者持续60s缓冲区一直超过64m也会关闭客户端连接。第三种是pub/sub客户端配置。

通过以上几种不同的配置,redis就可以避免客户端缓冲区过大导致占用过大内存引起的问题。后来确认了,其他项目组的同学有一个大key超过了50m(我们配置的是30m),直接导致redis服务器关闭了连接。但是应用程序还是从jedis连接池中获取到了这个被关闭的连接去get的时候,就抛出了Unexpected end of stream异常。

timeout配置

redis为了避免客户端连接数过多,有一个timeout配置,意思是如果连接的空闲时间超过了timeout的值,则关闭连接。默认配置是0,意思是没有超时限制,永远不关闭连接。生产上显然不会配置0,我们生产上配置的是120。这个timeout参数配置,要跟客户端创建的连接池的参数配合起来一起使用。下面我们看下jedis连接池的的一个配置demo。

 JedisPool jedisPool = new JedisPool(getPoolConfig(), "localhost");

private static GenericObjectPoolConfig getPoolConfig() {
        GenericObjectPoolConfig conf = new GenericObjectPoolConfig();
        // 设置获取连接的最大等待时间
        conf.setMaxWaitMillis(poolWaitMillis);
        // 设置最大连接数
        conf.setMaxTotal(poolMaxTotal);
        // 设置最大空闲连接数
        conf.setMaxIdle(poolMaxIdle);
        // 设置最小空闲连接数
        conf.setMinIdle(poolMinIdle);
        // 设置获取连接时不进行连接验证(通过 PoolableObjectFactory.validateObject() 验证连接是否有效)
        conf.setTestOnBorrow(false);
        // 设置退还连接时不进行连接验证(通过 PoolableObjectFactory.validateObject() 验证连接是否有效)
        conf.setTestOnReturn(false);
        // 设置连接空闲时进行连接验证
        conf.setTestWhileIdle(true);
        // 设置连接被回收前的最大空闲时间
        conf.setMinEvictableIdleTimeMillis(5 * 60000);
        // 设置检测线程的运行时间间隔
        conf.setTimeBetweenEvictionRunsMillis(60000);
        // 设置检测线程每次检测的对象数
        conf.setNumTestsPerEvictionRun(-1);

        return conf;
    }

上述代码构建了一个JedisPool,我们可以看到jedis使用apache common-pool2来创建一个jedis连接池的。创建jedis连接池传入了一个GenericObjectPoolConfig配置参数,我只筛选了其中一些主要的属性来说明,有兴趣的同学可以自己去看common-pool2的源码。我们主要看下最下面的几个参数(第16行到24行),我们设置了空闲时验证,连接被回收前最大空闲时间为300s,设置检测线程的运行时间间隔是60s,设置线程每次检测的对象数是-1(如果为负数,则检测所有空闲线程)

private int getNumTests() {
        int numTestsPerEvictionRun = this.getNumTestsPerEvictionRun();
        return numTestsPerEvictionRun >= 0?
        Math.min(numTestsPerEvictionRun, this.idleObjects.size()):
        (int)Math.ceil((double)this.idleObjects.size() / Math.abs((double)numTestsPerEvictionRun));
    }

我们redis服务端配置了timeout是120s,而我们客户端线程检测间隔是60s,每隔60s就会对所有空闲的连接进行检验,会调用JedisFactory的validateObject方法

class JedisFactory implements PooledObjectFactory<Jedis> {
    @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;
    }
  }
}

可以看到以上代码最后有一个jedis.ping().equals(“PONG”),这样能保证那些没有被客户端使用的连接每60s之内都能向redis服务端发送心跳,这个时间在我们配置的timeout 120s范围内,redis服务端也就不会关闭这个连接。

参考


文章作者: 叶明
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 叶明 !
评论
 上一篇
Hystrix初探 Hystrix初探
去年在看spring cloud系列的时候,当时就已经了解到了hystrix,这个组件和eureka,zuul等组件一样都是netflix公司开源的。当时由于没有太多的精力,只简单了解了下hystrix的一些简单功能。这半年因为工作关系一直
2017-09-27
下一篇 
jedis源码分析 jedis源码分析
创建redisClient BinaryJedisCluster我们可以从jedis给出官方的redis-clusterdemo上可以看到通过构造一个BinaryJedisCluster,这个类就是jedis给我们提供的一个与redis-
2017-06-18
  目录