⼀次redis连接配置修改引发的redis.ption。。
。
在⼀次修改了redis配置之后,出现了⼤批量的redis.ptions.JedisConnectionException: Unexpected end of stream.
原配置
1 JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
2 jedisPoolConfig.tMaxTotal(150);
bed
3 jedisPoolConfig.tMaxIdle(30);
4 jedisPoolConfig.tMinIdle(10);
5 jedisPoolConfig.tNumTestsPerEvictionRun(1024);
6 jedisPoolConfig.tTimeBetweenEvictionRunsMillis(30000);
7 jedisPoolConfig.tMinEvictableIdleTimeMillis(1800000);
8 jedisPoolConfig.tSoftMinEvictableIdleTimeMillis(1800000);
9 jedisPoolConfig.tMaxWaitMillis(1500);
10 jedisPoolConfig.tTestOnBorrow(true);
11 jedisPoolConfig.tTestWhileIdle(true);
12 jedisPoolConfig.tBlockWhenExhausted(fal);
修改后的配置
1 JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
2 jedisPoolConfig.tMaxTotal(60);
3 jedisPoolConfig.tMaxIdle(60);
4 jedisPoolConfig.tMinIdle(60);
5 jedisPoolConfig.tNumTestsPerEvictionRun(1024);
6 jedisPoolConfig.tTimeBetweenEvictionRunsMillis(30000);
7 jedisPoolConfig.tMinEvictableIdleTimeMillis(1800000);
8 jedisPoolConfig.tSoftMinEvictableIdleTimeMillis(1800000);
9 jedisPoolConfig.tMaxWaitMillis(500);
10 jedisPoolConfig.tTestOnBorrow(fal);
11 jedisPoolConfig.tTestWhileIdle(true);
12 jedisPoolConfig.tBlockWhenExhausted(fal);
如果⼀开始你就看出异常出现的问题所在了,那么这篇⽂章你就不⽤看了,是在浪费你的时间
⽽我看到这个异常最开始的想法是redis-rver出现了问题,在不停debug的时候猜测是redis-rver掐断了jedis连接,导致数据没有传输完成就断开了连接,但是这个想法怎么都没办法说服⾃⼰,虽然后来还是⼀直往这个⽅向⽅向查,因为福尔摩斯说过,排开所有不可能,剩下的那个原因再不可能那也是真相。可是福尔摩斯没有说怎么去找到最后的那个不可能,作为菜鸟的我⼀开始就把那个真相的不可能给排出了。对,我⼀开始就跑偏了,凤梨和菠萝傻傻分不清。
这⼏天有点空闲时间,所以想着吧jedis的源码都好好看⼀遍,看⼀下我对这个配置的修改到底引发了jedis的怎么不满,让它那么想弄死我。
⼀开始查找的⽅向是testOnBorrow和testWhileIdle,因为我的理解来看,其它⼏个修改应该不会引发redis-rver对我的不满。不会那么随意掐断我的jedis连接。
testOnBorrow有原来的true改为了fal,即在每次查询获取链接的时候,都会检查⼀下获取到的这个链接是否有效,也即是在borrow⼀个jedis链接之前,会⽤这个链接向redis-rver发送⼀个PING命令,如果能得到⼀个PONG响应则返回此链接给调⽤者,⽽我为了节约这⼀次请求,所以就吧testOnBorrow改成了fal。因为testWhileIdle改为了true,所以理所当然的认为在空闲的时候会触发pool中失效的连接的释放。
所以从现在开始,去彻底理解修改了的这两个参数的⼯作流,⾸先去阅读testOnBorrow参数源码如下:
s.pool2.impl.GenericObjectPool<T>
1if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {
2boolean validate = fal;
3 Throwable validationThrowable = null;
4try {
5 validate = factory.validateObject(p);
6 } catch (final Throwable t) {
7 PoolUtils.checkRethrow(t);
8 validationThrowable = t;
9 }
10if (!validate) {
11try {
12 destroy(p);
13 destroyedByBorrowValidationCount.incrementAndGet();
14 } catch (final Exception e) {
15// Ignore - validation failure is more important
16 }
17 p = null;
18if (create) {
19final NoSuchElementException ne = new NoSuchElementException(
20 "Unable to validate object");
21 ne.initCau(validationThrowable);
22throw ne;
23 }
24 }
silas
25 }
在第5⾏调⽤了factory.validateObject(p),这⼀⾏代码的作⽤则是保证获取到的实例是安全的,对于jedis来说,就是保证获取到的连接是可⽤的,⽽且验证⽅法实现,则是利⽤获取到的jedis连接想redis-rver发送了⼀个PING命令,判断是否能得到⼀个PONG响应,是则连接可⽤,反之则不可⽤。源码如下:
redis.clients.jedis.JedisFactory
1 @Override
2public boolean validateObject(PooledObject<Jedis> pooledJedis) {
3final BinaryJedis jedis = Object();ontology
4try {
5 HostAndPort hostAndPort = ();
6 String connectionHost = Client().getHost();
7int connectionPort = Client().getPort();
Host().equals(connectionHost)
mayqueen9 && Port() == connectionPort && jedis.isConnected()
10 && jedis.ping().equals("PONG");
11 } catch (final Exception e) {
12return fal;
13 }
14 }
eano 在修改testOnBorrow为fal之后,每次获取jedis连接时就不会再进⾏以上的连接验证了。从这样的源码看来,对于testOnBorrow参数的理解好像也没有什么问题。
⽽此时脑⼦⾥突然迸处⼀个想法,也许redis-rver并没有对我有什么不满,也许出现的异常并不是在数据传输过程中,连接被掐断,⽽是连接⼀开始就断了。因为borrow⼀个连接的时候,没有对连接
做有效性检查,那么可能borrow获取到的连接本⾝就是⽆效的,⽽testWhileIdle,可能根本就没有空闲的时候。为此,有去看testWhileIdle的⼯作流程。最后还是在
s.pool2.impl.GenericObjectPool<T>的public abstract void evict() throws Exception;实现中找到了关于testWhileIdle的调⽤。
1// Ur provided eviction policy could throw all sorts of
2// crazy exceptions. Protect against such an exception汽车suv是什么意思
3// killing the eviction thread.
4boolean evict;
5try {
6 evict = evictionPolicy.evict(evictionConfig, underTest,
7 idleObjects.size());
8 } catch (final Throwable t) {
烘炉
9// Slightly convoluted as SwallowedExceptionListener
10// us Exception rather than Throwable
11 PoolUtils.checkRethrow(t);
12 swallowException(new Exception(t));
13// Don't evict on error conditions
14 evict = fal;
15 }
16if (evict) {
17 destroy(underTest);
18 destroyedByEvictorCount.incrementAndGet();
19 } el {
20if (testWhileIdle) {
21boolean active = fal;
22try {
23 factory.activateObject(underTest);
24 active = true;
25 } catch (final Exception e) {
26 destroy(underTest);
27 destroyedByEvictorCount.incrementAndGet();
28 }
specifically29if (active) {
30if (!factory.validateObject(underTest)) {
31 destroy(underTest);
32 destroyedByEvictorCount.incrementAndGet();
33 } el {
34try {
35 factory.passivateObject(underTest);
36 } catch (final Exception e) {
37 destroy(underTest);
38 destroyedByEvictorCount.incrementAndGet();
39 }
toxic什么意思40 }
41 }
42 }
lcross
43if (!dEvictionTest(idleObjects)) {
44// TODO - May need to add code here once additional
45// states are ud
46 }
47 }
这是⼀个空闲线程移除的⽅法,Jedis中默认的触发频率是30s(我的配置也是30s),但timeBetweenEvictionRunsMillis和
minEvictableIdleTimeMillis配置的时间都是1800s,所以,空闲超过半个⼩时的连接才会被回收,没有超过半⼩时的连接会⾛20⾏的逻辑,⽽在testWhileIdle为true时,虽然有destroy⽆效连接的时候,但是在debug时却从来没有看到被调⽤过,在第30⾏的连接校验也是通过校验了的。也就是说连接池中的连接始终有效,直到配置的MinEvictableIdleTimeMillis和SoftMinEvictableIdleTimeMillis的条件(半个⼩时)触发第6⾏使得evict为true的时候,批量destroy旧的连接,然后新建新的连接重新放⼊pool中。
到现在为⽌,猜测有两点,⼀、borrow从pool中拿到的连接就是⽆效的;⼆、testWhileIdle空闲检查时,所有的连接都有效。这是⼀个⽭盾的结论,所以让我先想想静静。
后来想了⼀下,服务器会断开空闲客户端,更何况我已经把redis-rver的连接数占满了。问了⼀下运维的同学,说的是服务的会主动断开超过5s的空闲连接,后来想了⼀下,因为服务实例⽐较多,每个实例60的连接,很容易超过redis允许的最⼤连接数,⽽实际上应⽤可能⽤不到60个连接,使⽤旧的连接配置时,每个容器的连接数也就在10个,就是最⼩空闲连接数,所以使⽤新配置时,最⼩空闲连接数时60,也就是说每个容器⾄少有50个连接都是浪费的,所以在pool中可能会存在⼤量的连接没使⽤到,导致⼤量的连接被redis-rver主动断开,导致实例中会存在很多失效的连接,虽然每隔30s会进⾏⼀次清理,但是redis-rver断开的频率可能更⾼(因为连接满了不够⽤),导致Jedis 获取到的连接⼤概率⽆效。
当然最后的猜测我没有实验成功,主要是环境⼀直没有弄好,如果以后弄好了,我在补上。