Jedis 连接池 详解

一. JedisPool的几个重要参数介绍

1. maxTotal

控制连接池里面最多构建多少个Jedis实例。请看下面代码

public static void main(String[] args) {
	JedisPoolConfig config = new JedisPoolConfig();
	config.setMaxTotal(2);
	JedisPool pool = new JedisPool(config,"172.29.2.10",7000);
	while(true) {
		Jedis jedis = pool.getResource();
		System.err.println(jedis);
	}
}

打印结果

redis.clients.jedis.Jedis@2e5d6d97
redis.clients.jedis.Jedis@238e0d81

说明我们最多只能拿到2个实例,在调用getResource就会阻塞当前线程。我们看下main线程堆栈

"main" #1 prio=5 os_prio=0 tid=0x0000000003702800 nid=0xd588 waiting on condition [0x00000000034fe000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076c3dc110> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
        at org.apache.commons.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:590)
        at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:432)
        at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:349)
        at redis.clients.jedis.util.Pool.getResource(Pool.java:50)
        at redis.clients.jedis.JedisPool.getResource(JedisPool.java:330)
        at Test.main(Test.java:11)

阻塞原因是jedis底层调用了LockSupport的park方法。但是又不可能无限等待下去吧, maxWaitMillis就是最大的等待时间 。超时后会报下面异常:

Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
	at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:439)
	at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:349)
	at redis.clients.jedis.util.Pool.getResource(Pool.java:52)
	... 2 more

2. blockWhenExhausted

blockWhenExhausted为true时,上面拿不到jedis实例后,线程阻塞。 blockWhenExhausted为false时,上面拿不到jedis实例后,线程不阻塞,而是抛出异常。

public static void main(String[] args) {
	JedisPoolConfig config = new JedisPoolConfig();
	config.setMaxTotal(2);
	config.setBlockWhenExhausted(false);
	JedisPool pool = new JedisPool(config,"172.29.2.10",7000);
	while(true) {
		Jedis jedis = pool.getResource();
		System.err.println(jedis);
	}
}

---------------------打印结果---------------------
redis.clients.jedis.Jedis@2e5d6d97
redis.clients.jedis.Jedis@238e0d81
Exception in thread "main" redis.clients.jedis.exceptions.JedisExhaustedPoolException: Could not get a resource since the pool is exhausted
	at redis.clients.jedis.util.Pool.getResource(Pool.java:53)
	at redis.clients.jedis.JedisPool.getResource(JedisPool.java:330)
	at Test.main(Test.java:13)
Caused by: java.util.NoSuchElementException: Pool exhausted
	at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:444)
	at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:349)
	at redis.clients.jedis.util.Pool.getResource(Pool.java:50)
	... 2 more

插曲功能:先教大家一个技能,如何实时监控redis的tcp连接情况

我们先实时监控下redis-server的tcp连接情况

## redis-server占用端口是7000 
[root@localhost 7000]# watch -n 1 -d 'netstat -aln | grep 7000 | wc -l'

为什么会有一个,我们看下是哪个

[root@localhost 7000]# netstat -aln | grep 7000
tcp        0      0 0.0.0.0:7000            0.0.0.0:*               LISTEN 

原来是listen的tcp,不是客户端的,那就不用管了。


3. maxIdle

直接看这么一段程序

public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InterruptedException {
	JedisPoolConfig config = new JedisPoolConfig();
	config.setMaxTotal(5);
	config.setMaxIdle(2);
	config.setBlockWhenExhausted(false);
	JedisPool pool = new JedisPool(config,"172.29.2.10",7000);
	
	Jedis j1 = pool.getResource();
	Jedis j2 = pool.getResource();
	Jedis j3 = pool.getResource();
	Jedis j4 = pool.getResource();
	Jedis j5 = pool.getResource();
	Thread.sleep(3000);
	j1.close();
	j2.close();
	j3.close();
	
	Thread.sleep(1000000);
	
}

先getResource 5次,我们监控插曲功能tcp连接数

https://pic2.zhimg.com/v2-239cfdb21249f6fb9c19f77bcbace1e5_b.jpg

马上从1到6,原来本身有一个listen的tcp,说明getResource 5次 后,实在的产生了5个tcp连接。然后执行3次close,理论上应该释放掉3个tcp,然而结果为

https://pic4.zhimg.com/v2-73621fe4cf909030c590544f387cd353_b.png

(注意:这里要立马看监控才是5,如果等一定的时间的话就会变成3,这也是其余参数控制的,后面讲)

由于我们maxIdle 设置的是2 ,所以有2个归还到连接池了,真正释放tcp的只有1个。

总结

maxIdle实际上才是业务需要的最大连接数,maxTotal 是为了给出余量,所以 maxIdle 不要设置得过小,否则会有new jedis(新连接)开销。

4. timeBetweenEvictionRunsMillis,minEvictableIdleTimeMillis

上面测试,先归还2个到连接池 , 然后等待最多 大约 TimeBetweenEvictionRunsMillis + MinEvictableIdleTimeMillis 时间,连接无操作的话就真正释放那2个tcp连接。

为什么大约是两个时间之和?其实是分为下面两个阶段(检测线程周期执行检测&超时判断)

  • timeBetweenEvictionRunsMillis: 空闲资源的检测周期(单位为毫秒)。
  • minEvictableIdleTimeMillis : 资源池中资源的最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除。

5. minIdle

等上面TimeBetweenEvictionRunsMillis + MinEvictableIdleTimeMillis 的时间到后,会自动释放tcp连接,但是如果指定了minIdle,就会最少保持minIdle 连接。怎么释放也不会低于minIdle 连接数。


二. Redis服务端的超时配置

redis.conf配置文件也有个timeout配置,当设置为0时,表示永远不超时,当设置为其它数时,表示到了timeout后,会主动断开没有活跃请求的tcp连接。



三. 连接池配置建议

  • 建议 maxTotal = maxIdle

这样就避免了连接池伸缩带来的性能干扰。如果您的业务存在突峰访问,建议设置这两个参数的值相等;如果并发量不大或者maxIdle设置过高,则会导致不必要的连接资源浪费。

  • 怎么设置maxTotal的值

假如一个redis命令请求到执行需要花1ms,那么一个tcp的QPS就是1*1000。 如果你配置了maxTotal =50 , 也就是同时最大50个tcp连接,那么你服务的总QPS就是1000*50 = 50000。实际上线上服务都是集群的,所以还要乘以你的机器数。

但是 redis 也有最大的连接限制,所以不建议maxTotal很大

########### redis-server  最大连接数查看
127.0.0.1:7000> CONFIG GET maxclients
1) "maxclients"
2) "10000"


四. redis服务端定位连接数

查看当前所有连接数

127.0.0.1:7000> CLIENT LIST
id=392 addr=172.29.2.194:50751 fd=14 name= age=32 idle=32 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=NULL
id=387 addr=127.0.0.1:51988 fd=8 name= age=125 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=client
id=388 addr=172.29.2.194:50747 fd=9 name= age=32 idle=32 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=NULL
id=389 addr=172.29.2.194:50748 fd=10 name= age=32 idle=32 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=NULL
id=390 addr=172.29.2.194:50749 fd=12 name= age=32 idle=32 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=NULL
id=391 addr=172.29.2.194:50750 fd=13 name= age=32 idle=32 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=NULL

几个重要字段解释

  • age : 以秒计算的已连接时长。
  • idle : 以秒计算的空闲时长。
  • cmd : 最近一次执行的命令。

如果线上发现大部分连接 age 的时间 和 idle 时间差不多大小的,证明都是废物空闲连接。


还有其它几个命令

  • CLIENT KILL ip:port 杀死指定连接

主动杀死连接

127.0.0.1:7000> CLIENT KILL id 392


强烈推荐一个 进阶 JAVA架构师 的博客

Java架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者