我是靠谱客的博主 淡淡钻石,最近开发中收集的这篇文章主要介绍常见JedisConnectionException异常分析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

转载:http://blog.csdn.net/fachang/article/details/7984123

最近项目开发中用到了Redis, 选择了官网推荐的java client Jedis。

Redis常用命令学习:http://redis.io/commands
Redis官方推荐Java客户端Jedis(包含了所有Redis命令的实现):https://github.com/xetorthio/jedis

Jedis使用过程中最常见异常JedisConnectionException有时确实给我们带来了很多困惑,这个异常通常出现在两个使场景。

一、当我们执行如下JedisPool类实例的getResource()时抛出can't get a resource异常。

异常代码如下:

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

at redis.clients.util.Pool.getResource(Pool.java:22)

分析:

redis.clients.util.Pool.getResource会从JedisPool实例池中返回一个可用的redis连接。分析源码可知JedisPool extends redis.clients.util.Pool<Jedis> .而Pool<T>是通过

commons-pool开源工具包中的org.apache.commons.pool.impl.GenericObjectPool来实现对Jedis实例的管理的。所以我们分析一下GenericObjectPool或许能找到答案。

首先看一下common-pool的api:http://commons.apache.org/pool/apidocs/index.html?org/apache/commons/pool/impl/GenericObjectPool.html。
其中三个重要个几个属性是:
MaxActive: 可用连接实例的最大数目,为负值时没有限制。
MaxIdle: 空闲连接实例的最大数目,为负值时没有限制。Idle的实例在使用前,通常会通过org.apache.commons.pool.BasePoolableObjectFactory<T>的activateObject()方法使其变得可用。
MaxWait: 等待可用连接的最大数目,单位毫秒(million seconds)。
     (注:pool.getResource()方法实际调用的GenericObjectPool类borrowObject()方法,该方法会根据MaxWait变量值在没有可用连接(idle/active)时阻塞等待知道超时,具体含义参看api。)

也就是说当连接池中没有active/idle的连接时,会等待maxWait时间,如果等待超时还没有可用连接,则抛出Could not get a resource from the pool异常。所以为避免这样的错误,

我们应该根据程序实际情况合理设置这三个参数的值,同时在我们获取一个连接的程序方法中也应该合理的处理这个异常,当没有连接可用时,等待一段时间再获取也许是个比较好的选择。


二、当我们获取连接后对redis进行操作时,抛出redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out异常。

异常代码如下:

redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out

at redis.clients.jedis.Protocol.process(Protocol.java:79)
at redis.clients.jedis.Protocol.read(Protocol.java:131)
at redis.clients.jedis.Connection.getIntegerReply(Connection.java:188)
at redis.clients.jedis.Jedis.sismember(Jedis.java:1266)

这是一个比较麻烦的异常,困扰了我一天的时间。我们都知道Redis是对内存进行操作,速度应该都在毫秒级,这是我们通常的认识,所以当对Redis操作出现几秒的超时时间,你能想象吗?
我们还是先分析一下Jedis的源代码吧,以sadd操作为例:
  1. public Long sadd(final String key, final String... members) {
  2. checkIsInMulti();
  3. client.sadd(key, members);
  4. return client.getIntegerReply();
  5. }
client是redis.clients.jedis.Client.java的实例,继承关系如下:
public class Client extends BinaryClient implements Commands;

public class BinaryClient extends Connection;

Connection包装了对Redis server的socket操作,命令写操作通过socket.getOutputStream()输出流将命令信息发送到redis server,当写完命令后要通过socket.getInputStream()的到的输入流将
命令执行结果返回,这中间必然会有一个命令执行到结果返回的延时时间,这就是一个Jedis调用redis命令操作所用的时间。

需要说明的是,Redis server是单线程执行所有连接发送过来的命令的,也就是说不管并发中有多少个client在发送命令,redis-server端是单线程处理的,并按照默认的FIFO方式处理请求,

这个可在redis.conf配置文件中配置。关于redis server的详细运行机制参见:http://redis.io/documentation

所以client.sadd(key, members);调用完后只是将命令信息发送到了redis server端,具体有没有执行要看redis server的负载情况。然后,通过client.getIntegerReply();等待(time out)返回结果。
Connection初始化socket时有多种选择,其中设置socket time out 的方法如下:
  1. public void rollbackTimeout() {
  2.           try {
  3.             socket.setSoTimeout(timeout);
  4.             socket.setKeepAlive(false);
  5.           } catch (SocketException ex) {
  6.             throw new JedisException(ex);
  7.           }
  8.       }
由redis.clients.jedis.Protocol.DEFAULT_TIMEOUT = 2000 我们知道默认的超时时间是2秒,这个时间相对于redis操作内存毫秒级的速度来说已经很长,那我们为什么还会遇到
ava.net.SocketTimeoutException: Read timed out异常呢?redis操作内存虽然平均毫秒级的,但当数据量很大时未必都如此快速。在我的开发过程中就遇到过一个集合到了

千万级数据量,一次操作超时时间在秒级是很正常的,而且机器性能很好的情况下已经如此,更何况我们本机开发的机器相对于生产服务器来说速度会更慢了。所以在初始化JedisPool时应该根据实际

情况通过redis.clients.jedis.JedisPoolConfig合理设置连接池参数,通过edisPool构造方法,合理设置socket读取输入InputStream的超时时间。

  1. pool = new JedisPool(config, host, port, 100000);

注意第四个参数time out,设置成我们能容忍的超时时间,单位是毫秒。但不知道为什么既然单位是毫秒,为什么参数类型是int而不是long。

设置第四个参数后,我在四千万数据量集合上操作最多一次大概超时5秒,问题基本解决。



------------------------------------------------------------------------------------------------

另一篇: 

Redis客户端常见异常分析

 http://carlosfu.iteye.com/blog/2338597


https://cachecloud.github.io/2016/11/17/Redis%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%B8%B8%E8%A7%81%E5%BC%82%E5%B8%B8%E5%88%86%E6%9E%90/

 

 

在Redis客户端的使用过程中,无论是客户端使用不当或者Redis服务端出现问题,客户端会反应出一些异常,下面分析一下Jedis使用过程中常见的异常情况:

一.无法从连接池获取到连接

JedisPool中的Jedis对象个数是有限的,默认是8个。这里假设使用的默认配置,如果有8个Jedis对象被占用,并且没有归还,如果调用者还要从JedisPool中借用Jedis,就需要进行等待(例如设置了maxWaitMillis>0),如果在maxWaitMillis时间内仍然无法获取到Jedis对象就会抛出如下异常。

Java代码   收藏代码
  1. redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool  
  2.     …  
  3. Caused by: java.util.NoSuchElementException: Timeout waiting for idle object  
  4.     at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)  

还有一种情况,就是设置了blockWhenExhausted=false,那么调用者发现池子中没有资源时,会立即抛出异常不进行等待,下面的异常就是blockWhenExhausted=false时的效果。

Java代码   收藏代码
  1. redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool  
  2.     …  
  3. Caused by: java.util.NoSuchElementException: Pool exhausted  
  4.     at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:464)  

 

对于这个问题,需要重点讨论的是为什么连接池没有资源了,造成没有资源的可能的原因非常多

  • 1.客户端:高并发下连接池设置过小,出现供不应求,所以会出现上面的错误,但是正常情况下只要比默认的最大连接数(8个)多一些即可,因为正常情况下JedisPool以及Jedis的处理效率足够高。

  • 2.客户端:没有正确使用连接池,比如没有进行释放,例如下面代码所示:
    定义JedisPool,使用默认的连接池配置。

Java代码   收藏代码
  1. GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();  
  2. JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1"6379);  
  3. //向JedisPool借用8次连接,但是没有执行归还操作。  
  4. for (int i = 0; i < 8; i++) {  
  5.     Jedis jedis = null;  
  6.     try {  
  7.     jedis = jedisPool.getResource();  
  8.     jedis.ping();  
  9.     } catch (Exception e) {  
  10.     e.printStackTrace();  
  11.     }  
  12. }  

当调用者再向连接池借用Jedis时(如下操作),就会抛出异常:

Java代码   收藏代码
  1. jedisPool.getResource().ping();  
  • 3.客户端:存在慢查询操作,这些慢查询持有的Jedis对象归还速度会比较慢,造成池子满了。
  • 4.服务端:客户端是正常的,但是Redis服务端由于一些原因造成了客户端命令执行过程的阻塞,也会使得客户端抛出这种异常。
    可以看到造成这个异常的原因是多个方面的,不要被异常的表象所迷惑,而且并不存在通用的解决方案,开发和运维只能不断加强对于Redis的理解,顺藤摸瓜逐渐找到问题所在。

二、 客户端读写超时

Jedis在调用Redis时,如果出现了读写超时后,会出现下面的异常:

Java代码   收藏代码
  1. redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out  
造成该异常的原因也有以下几种: 

 

  • 读写超时设置的过短。
  • 命令本身就比较慢。
  • 客户端与服务端网络不正常。
  • Redis自身发生阻塞。

三 客户端连接超时

Jedis在调用Redis时,如果出现了读写超时后,会出现下面的异常:

Java代码   收藏代码
  1. redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out  
造成该异常的原因也有以下几种: 

 

  • 连接超时设置的过短。
  • Redis发生阻塞,造成tcp-backlog已满,造成新的连接失败。
  • 客户端与服务端网络不正常。

四、客户端缓冲区异常

Jedis在调用Redis时,如果出现客户端数据流异常,会出现下面的异常。

 

Java代码   收藏代码
  1. redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.  
造成这个异常原因可能有如下几种: 

 

  • 1.输出缓冲区满。例如将普通客户端的输出缓冲区设置为1M 1M 60:

 

Java代码   收藏代码
  1. config set client-output-buffer-limit "normal 1048576 1048576 60 slave 268435456 67108864 60 pubsub 33554432 8388608 60"  
 

 

如果使用get命令获取一个bigkey(例如3M),就会出现这个异常。

  • 2.长时间闲置连接被服务端主动断开,可以查询timeout配置的设置以及自身连接池配置是否需要做空闲检测。
  • 3.不正常并发读写:Jedis对象同时被多个线程并发操作,可能会出现上述异常。

五、Lua脚本正在执行

如果Redis当前正在执行Lua脚本,并且超过了lua-time-limit,此时Jedis调用Redis时,会收到下面的异常。对于如何处理这类问题(Lua lua-time-limit配置之前章节已经介绍了)

Java代码   收藏代码
  1. redis.clients.jedis.exceptions.JedisDataException: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE  

六、Redis正在加载持久化文件

Jedis调用Redis时,如果Redis正在加载持久化文件,那么会收到下面的异常。

Java代码   收藏代码
  1. redis.clients.jedis.exceptions.JedisDataException: LOADING Redis is loading the dataset in memory  

七、Redis使用的内存超过maxmemory配置

Jedis调用Redis执行写操作时,如果Redis的使用内存大于maxmemory的设置,会收到下面的异常,此时应该调整maxmemory并找到造成内存增长的原因(maxmemory之前章节已经介绍了)

Java代码   收藏代码
  1. redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'.  

八、客户端连接数过大

如果客户端连接数超过了maxclients,新申请的连接就会出现如下异常:

Java代码   收藏代码
  1. redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached  

此时新的客户端连接执行任何命令,返回结果都是如下: 

Java代码   收藏代码
  1. 127.0.0.1:6379> get hello  
  2. (error) ERR max number of clients reached  
 
  •  1.客户端:如果maxclients参数不是很小的话,应用方的客户端连接数基本不会超过maxclients,通常来看是由于应用方对于Redis客户端使用不当造成的。此时如果应用方是分布式结构的话,可以通过下线部分应用节点(例如占用连接较多的节点),使得Redis的连接数先降下来。从而让绝大部分节点可以正常运行,此时在再通过查找程序bug或者调整maxclients进行问题的修复。这个问题可能会比较棘手,因为此时无法执行Redis命令,一般来说可以从两个方面进行着手。
  • 2.服务端:如果此时客户端无法处理,而当前Redis为高可用模式(例如Redis Sentinel和Redis Cluster),可以考虑将当前Redis做故障转移。

此问题不存在确定的解决方式,但是无论从哪个方面进行处理,故障的快速恢复极为重要,当然更为重要的是找到问题的所在,否则一段时间后客户端连接数依然会超过maxclients。

九、JedisCluster异常将在集群章节介绍。

附赠GenericObjectPoolConfig的重要属性

序号 参数名 含义 默认值
1maxActive连接池中最大连接数8
2maxIdle连接池中最大空闲的连接数8
3minIdle连接池中最少空闲的连接数0
4maxWaitMillis当连接池资源用尽后,调用者的最大等待时间(单位为毫秒),一般不建议使用默认值-1:表示永远不超时,一直等。
5jmxEnabled是否开启jmx监控,如果应用开启了jmx端口并且jmxEnabled设置为true,就可以通过jconsole或者jvisualvm看到关于连接池的相关统计,有助于了解连接池的使用情况,并且可以针对其做监控统计true
6minEvictableIdleTimeMillis连接的最小空闲时间,达到此值后空闲连接将被移除30分钟
7numTestsPerEvictionRun做空闲连接检测时,每次的采样数3
8testOnBorrow向连接池借用连接时是否做连接有效性检测(ping),无效连接会被移除,每次借用多执行一次ping命令false
9testOnReturn向连接池归还连接时是否做连接有效性检测(ping),无效连接会被移除,每次归还多执行一次ping命令false
10testWhileIdle向连接池借用连接时是否做连接空闲检测,空闲超时的连接会被移除false
11timeBetweenEvictionRunsMillis空闲连接的检测周期(单位为毫秒)-1:表示不做检测
12blockWhenExhausted当连接池用尽后,调用者是否要等待,这个参数是和maxWaitMillis对应的,只有当此参数为true时,maxWaitMillis才会生效true

本文部分内容来自《Redis开发与运维》一书,转载请声明。

最后

以上就是淡淡钻石为你收集整理的常见JedisConnectionException异常分析的全部内容,希望文章能够帮你解决常见JedisConnectionException异常分析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(39)

评论列表共有 0 条评论

立即
投稿
返回
顶部