Jedis连接超时问题分析 Unexpected end of stream
文章目录Jedis连接问题一、现状二、复现 -_Unexpected end of stream_三、分析 -_Unexpected end of stream_四、方案- _Unexpected end of stream_方案一:设置服务端不超时 _timeout=0_方案二:设置TestOnBorrow = true,服务端超时30s方案三:设置最大空闲连接为0方案四:设置Jedis驱逐策略
文章目录
Jedis连接问题
一、现状
- 项目实施中生产环境获取redis缓存报错。
Unexpected end of stream.
二、复现 - Unexpected end of stream
- 环境版本信息
应用 | 版本 | 数据来源 |
---|---|---|
jdk | 1.8.0_231 | – |
redis | 2.8.19 | ./redis-cli info server | grep redis_version |
jedis | 2.7.3 | – |
ss-redis | 1.2.6.1 | – |
-
设置redis服务端超时时间或者在redis.conf修改
./redis-cli -p 7379 config set timeout 30
-
客户端验证超时(伪代码)
/** 重要参数 MinIdle=2 MaxIdle = 2 TestOnBorrow = false TestOnReturn = false; MaxTotal = -1; MaxWaitMillis = 100L; TestWhileIdle = false; **/ initPool(); //初始化连接池 Thread.sleep(1000*40); //线程阻塞40s(大于服务端超时时间) Jedis jedis = getResource(); //从连接池获取连接 jedis.set(k, v); //报错 Unexpected end of stream
-
模拟报错结果如下
三、分析 - Unexpected end of stream
-
设置服务端超时30s,每隔2秒采集redis client连接情况
while true ;do ./redis-cli -p 7379 info Clients | grep connected_clients && sleep 2; done
-
连接数监测结果
- 如下图中可以看出,在redis服务端30s超时之后,服务端断开连接,连接数变成7。当Jedis客户端再次操作的时候就会报错_Unexpected end of stream_
- 如下图中可以看出,在redis服务端30s超时之后,服务端断开连接,连接数变成7。当Jedis客户端再次操作的时候就会报错_Unexpected end of stream_
-
对初始及剩下的7个连接分析结果
- 6个连接是sentinel与redis-server建立的连接
- 1个是当前执行采集脚本时候,与redis-server建立的连接
-
四、方案 - Unexpected end of stream
方案一:设置服务端不超时 timeout=0
-
修改redis-server配置
./redis-cli -p 7379 config set timeout 0
-
结果验证
服务端连接一直保持,不断开。客户端操作正常。
方案二:设置TestOnBorrow = true,服务端超时30s
-
修改Jedis连接池配置
setTestOnBorrow(true)
-
修改redis-server配置
./redis-cli -p 7379 config set timeout 30
-
结果验证
服务端连接超时断开,客户端重新建立连接获取数据。见下图
-
源码分析
- redis pool将连接对象保存在队列里面,每次获取连接时从队列中取 pollFirst,拿到连接对象执行ping操作。如果正常返回则使用该连接。如果报错则重建连接。
- testOnBorrow=true保证了连接的可用性。
public boolean validateObject(PooledObject<Jedis> pooledJedis) { BinaryJedis jedis = (BinaryJedis)pooledJedis.getObject(); try { HostAndPort 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 (Exception var6) { return false; } }
方案三:设置最大空闲连接为0
设置最大空闲连接为0,每一次重新建立连接,业务操作正常。
方案四:设置Jedis驱逐策略
-
redis pool驱逐参数
minEvictableIdleTimeMillis:硬闲置时间,连接多久没有使用设置为闲置,检测线程直接剔除闲置 softMinEvictableIdleTimeMillis:软闲置时间,连接多久没有使用设置为闲置,当空闲连接 > MinIdle,才执行剔除闲置,否则维持最小空闲数,即使闲置了也不会剔除 timeBetweenEvictionRunsMillis:逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
-
参数设置如下,将逐出扫描时间设置大于服务端超时时间,并得出结果。
- 设置服务端超时25s
- Jedis驱逐间隔35s
minEvictableIdleTimeMillis = 1000 * 35 softMinEvictableIdleTimeMillis = 1000 * 60 timeBetweenEvictionRunsMillis = 1000 * 60
-
默认驱逐策略源码
public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> { public DefaultEvictionPolicy() { } public boolean evict(EvictionConfig config, PooledObject<T> underTest, int idleCount) { return config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() && config.getMinIdle() < idleCount || config.getIdleEvictTime() < underTest.getIdleTimeMillis(); } }
-
结果验证
如下图,存在连接数为7的时间段,说明在此时间段内,如果发生redis操作,会造成服务端超时报错。
-
将逐出扫描时间设置小于服务端超时时间,并得出结果。
minEvictableIdleTimeMillis = 1000 * 25 softMinEvictableIdleTimeMillis = 1000 * 25 timeBetweenEvictionRunsMillis = 1000 * 28
-
通过将驱逐间隔以及空闲时间修改为小于服务端超时时间。达到客户端主动超时重建的目的。如下图,不会出现redis连接为7的情况,即不产生服务端超时。
-
设置服务端超时时间30s,Jedis逐出间隔28s,闲置时间25s。
-
重建并维护最小连接源码
private void ensureIdle(int idleCount, boolean always) throws Exception { if (idleCount >= 1 && !this.isClosed() && (always || this.idleObjects.hasTakeWaiters())) { while(this.idleObjects.size() < idleCount) { PooledObject<T> p = this.create(); if (p == null) { break; } if (this.getLifo()) { this.idleObjects.addFirst(p); } else { this.idleObjects.addLast(p); } } if (this.isClosed()) { this.clear(); } } }
-
方案对比:以上方案均可解决服务端超时报错的问题。
方案 | 修改点 | 优缺点 | 建议 |
---|---|---|---|
方案一 | 修改服务端超时时间为 0,不超时 | 连接不超时,如果客户端不限制,会打满连接 | 不使用 |
方案二 | 设置testOnBorrow = true,设置服务端超时30s | 每次操作之前ping,多一次网络消耗 | 在客户端请求量不大的情况下,可作为临时解决方案 |
方案三 | 设置最大空闲连接maxIdle为0 | 每次需要重新建立连接,违背了连接池的理念 | 不使用 |
方案四 | 设置服务端超时时间,设置合理的客户端驱逐策略 | 能很好的使用Jedis连接池 | 设置605s服务端超时时间 > ss-redis源码中定义的驱逐时间间隔600s,让客户端先断开。 |
五、验证Jedis最大连接参数
-
验证当从Jedis连接池中获取超过预定参数配置(redis.pool.maxActive)的Jedis最大连接数时,系统的响应情况。下面是简单的模拟这种情况:
/** * 设置maxActive 100, 并持有100连接不释放,验证101请求到来时的情况 */ private static void scene2() { Jedis jedis = null; int count = 0; while (count <= 99) { for (int i = 0; i < 20; i++) { try { jedis = SentinelJedisUtil.getResource(); } catch (Exception e) { e.printStackTrace(); } count++; } System.out.println("print redis client: " + jedis.info("Clients").split("\r\n")[1]); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } } try { System.out.println("start get 101 link"); jedis = SentinelJedisUtil.getResource(); System.out.println("print redis client: " + jedis.info("Clients")); } catch (Exception e) { e.printStackTrace(); } }
-
设置 blockWhenExhausted=true 当超过连接池最大连接时,会阻塞等待,超过获取连接等待时间,则抛错。
-
设置 blockWhenExhausted=false 当超过连接池最大连接时,直接抛错。
-
获取连接源码
org.apache.commons.pool2.impl.GenericObjectPool#borrowObject(long)
更多推荐
所有评论(0)