1.场景:
客户端采用客户端分片使用redis集群(即每个redis之间无关联,每个redis都是master角色)
1.1说明:
由于采用客户端hash,加上数据不可能均匀,因此最慢的一台redis就是天花板,所以下面都是按照一台redis的量来预估,这样是考虑最坏情况来进行容量评估;
2要面对的问题:
- 单台客户端(指连接redis的服务)最多用多少连接?
- 单台redis实例要设置多少个maxclients?
3思路:
假设单台redis实例设置maxclients = 20000,而且我们通过压测发现单台客户端最多使用了500个连接,那么可以推断出
理论上,最多只能部署40个客户端;
4详细分析
4.1 说明
针对每个实例创建一个池:maxIdle和maxTotal指的是针对一个redis实例的设置,意思就是如果连接两个实例,那么就真正的池的大小要增加一倍,如果三个,那么就又增加一倍;
4.2连接池不够用,又不能创建
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
此刻连接池用光了,并且blockWhenExhausted该值为true(该值默认为true),就需要等连接池有可用的,但是等待超时报该错误;
分两种情况解决上面的问题:
1.调大等待时间(如果还是超时,就在上层限流)
2.增加连接池数目(千万不能超过服务端的maxclients限制)
注意:
jedis的如下值
maxIdle:表示池中目前可用的(不代表真的可用);
maxTotal:表示池中能装多少(超过这个池的大小,不会再创建了)
取决于服务端允许多少,如果服务端允许1个,jedis的这两个值设置再大都是无用的;
复现方法:
1.客户端多线程并发调用
2.maxIdle和maxTotal设置为2,服务端maxclients也设置为大于2的值即可();
4.3 连接池用完了,然后新创建的validate失败
Caused by: java.util.NoSuchElementException: Unable to validate object
无论blockWhenExhausted是否为true,都说明连接池用光了,而且也创建了(新的对象并放入池中)。
另外,也激活(activateObject)了该对象(不能激活会报错:Unable to activate object),但是该对象(网络连接)是不可用的(一种可能是服务端最多可连接5个,你这个是第6个连接);
说明:对象激活和验证可用是两个性质的问题,激活不代表真的可用;
因此,针对上面的情况(即不够用采取创建但是此时可能不可用),一种折中的方法是:不创建(来避免这种意外的情况),但是不创建怎么够用,
那么就把天花板(maxTotal)设置的小一点;如果此时,客户端会等待超时,那么就调大点等待超时时间;最好的做法是上层限流(把目前能处理的批次当做一个任务来对待,处理完成了,再接收一个新任务);
复现方法:
1.一台客户端多线程并发调用,把连接吃光,另外一台客户端就拿不到可用的;
2.maxIdle和maxTotal远大于大于2的值,服务端maxclients也设置为2的值即可;
5 压测示例
说明:
* A任务表示对一个设备进行和redis交互操作(一个A任务包含5次redis操作);
* 如下是一个服务 + 一个redis实例
20个线程任务(每个任务分为10000个A任务) + (使用了)10个redis连接 = 1785(qps的值)
10000个线程任务(每个任务分为20个A任务) + (使用了)500个redis连接 = 2531(qps的值)
2000个线程任务(每个任务分为100个A任务) + (使用了)500个redis连接 = 2325(qps的值)
20000个线程任务(每个任务分为10个A任务) + (使用了)500个redis连接 = 2040(qps的值)
200000个线程任务(每个任务分为1个A任务) + (使用了)500个redis连接 = 2083(qps的值) 接近符合我们的业务场景(因为我们的业务场景是一个任务是2000个线程任务)
6 关键代码与分析
说明:要通过报错信息来分析和推导出原因是什么
public T borrowObject(long borrowMaxWaitMillis) throws Exception {
this.assertOpen();
AbandonedConfig ac = this.abandonedConfig;
if(ac != null && ac.getRemoveAbandonedOnBorrow() && this.getNumIdle() < 2 && this.getNumActive() > this.getMaxTotal() - 3) {
this.removeAbandoned(ac);
}
PooledObject p = null;
boolean blockWhenExhausted = this.getBlockWhenExhausted();
long waitTime = 0L;
while(p == null) {
boolean create = false;
if(blockWhenExhausted) {
p = (PooledObject)this.idleObjects.pollFirst();
if(p == null) {
create = true;
p = this.create();
}
if(p == null) {
if(borrowMaxWaitMillis < 0L) {
p = (PooledObject)this.idleObjects.takeFirst();
} else {
waitTime = System.currentTimeMillis();
p = (PooledObject)this.idleObjects.pollFirst(borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
waitTime = System.currentTimeMillis() - waitTime;
}
}
if(p == null) {//此时表明已经超过maxTotal,不能创建;报错:等空闲超时
throw new NoSuchElementException("Timeout waiting for idle object");
}
if(!p.allocate()) {
p = null;
}
} else {
p = (PooledObject)this.idleObjects.pollFirst();
if(p == null) {
create = true;
p = this.create();
}
if(p == null) {
throw new NoSuchElementException("Pool exhausted");
}
if(!p.allocate()) {
p = null;
}
}
if(p != null) {
try {
this.factory.activateObject(p);
} catch (Exception var15) {
try {
this.destroy(p);
} catch (Exception var14) {
;
}
p = null;
if(create) {
NoSuchElementException validationThrowable = new NoSuchElementException("Unable to activate object");
validationThrowable.initCause(var15);
throw validationThrowable;
}
}
if(p != null && this.getTestOnBorrow()) {
boolean validate = false;
Throwable validationThrowable1 = null;
try {
validate = this.factory.validateObject(p);
} catch (Throwable var13) {
PoolUtils.checkRethrow(var13);
validationThrowable1 = var13;
}
if(!validate) {
try {
this.destroy(p);
this.destroyedByBorrowValidationCount.incrementAndGet();
} catch (Exception var12) {
;
}
p = null;
if(create) {
NoSuchElementException nsee = new NoSuchElementException("Unable to validate object");
nsee.initCause(validationThrowable1);
throw nsee;
}
}
}
}
}
this.updateStatsBorrow(p, waitTime);
return p.getObject();
}
7.性能追求
如果要追求性能,(生产环境)需要将testOnBorrow和testOnReturn设置为false(至于不能用的或者空闲的连接,可以用其他配置或者方式处理,因为连接不可用造成的异常也要捕获进行相应处理),因为进行这两个操作,性能低;
8.总结
涉及到网络连接的开发,都会有连接池;
在出现连接池连接不够用,一般原因的原因可能有如下几种:
1.客户端线程长期占用,不还回池中;
2.服务端处理性能低,一个连接被长期使用和占用;
3.向池子拿的速度比还的快(如果在内网,网络操作还是非常快的,这种情况很少发生,公网需要特殊分析和对待)
在出现连接超时时,一般原因可能有如下几种:
1.服务端的backlog的限制,这个时候要分析连接状态(SYN_SENT、SYN_RCVD、主动断开连接的可能的状态(FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT)、被动断开连接的可能的状态(CLOSED_WAIT、LAST_ACK))是否存在异常(正常情况下,这些状态一般都是比较少的,或者通过netstat看不到的)。
2.客户端的连接也是有限制,这个也需要考虑,方法同服务端检查方法一样;
3.网络情况很差;
9其他
缓存网络对象,参见http://blog.csdn.net/KuaiLeShiFu/article/details/77746857
参考:http://blog.csdn.net/hguisu/article/details/38700899
http://www.cnblogs.com/Toonter/p/5926094.html