概述
转载:http://blog.csdn.net/fachang/article/details/7984123
最近项目开发中用到了Redis, 选择了官网推荐的java client Jedis。
Redis常用命令学习:http://redis.io/commandsRedis官方推荐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)
我们还是先分析一下Jedis的源代码吧,以sadd操作为例:
- public Long sadd(final String key, final String... members) {
- checkIsInMulti();
- client.sadd(key, members);
- return client.getIntegerReply();
- }
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 的方法如下:
- public void rollbackTimeout() {
- try {
- socket.setSoTimeout(timeout);
- socket.setKeepAlive(false);
- } catch (SocketException ex) {
- throw new JedisException(ex);
- }
- }
ava.net.SocketTimeoutException: Read timed out异常呢?redis操作内存虽然平均毫秒级的,但当数据量很大时未必都如此快速。在我的开发过程中就遇到过一个集合到了
千万级数据量,一次操作超时时间在秒级是很正常的,而且机器性能很好的情况下已经如此,更何况我们本机开发的机器相对于生产服务器来说速度会更慢了。所以在初始化JedisPool时应该根据实际
情况通过redis.clients.jedis.JedisPoolConfig合理设置连接池参数,通过edisPool构造方法,合理设置socket读取输入InputStream的超时时间。
- 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对象就会抛出如下异常。
- redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
- …
- Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
- at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)
还有一种情况,就是设置了blockWhenExhausted=false,那么调用者发现池子中没有资源时,会立即抛出异常不进行等待,下面的异常就是blockWhenExhausted=false时的效果。
- redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
- …
- Caused by: java.util.NoSuchElementException: Pool exhausted
- at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:464)
对于这个问题,需要重点讨论的是为什么连接池没有资源了,造成没有资源的可能的原因非常多
1.客户端:高并发下连接池设置过小,出现供不应求,所以会出现上面的错误,但是正常情况下只要比默认的最大连接数(8个)多一些即可,因为正常情况下JedisPool以及Jedis的处理效率足够高。
2.客户端:没有正确使用连接池,比如没有进行释放,例如下面代码所示:
定义JedisPool,使用默认的连接池配置。
- GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
- JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
- //向JedisPool借用8次连接,但是没有执行归还操作。
- for (int i = 0; i < 8; i++) {
- Jedis jedis = null;
- try {
- jedis = jedisPool.getResource();
- jedis.ping();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
当调用者再向连接池借用Jedis时(如下操作),就会抛出异常:
- jedisPool.getResource().ping();
- 3.客户端:存在慢查询操作,这些慢查询持有的Jedis对象归还速度会比较慢,造成池子满了。
- 4.服务端:客户端是正常的,但是Redis服务端由于一些原因造成了客户端命令执行过程的阻塞,也会使得客户端抛出这种异常。
可以看到造成这个异常的原因是多个方面的,不要被异常的表象所迷惑,而且并不存在通用的解决方案,开发和运维只能不断加强对于Redis的理解,顺藤摸瓜逐渐找到问题所在。
二、 客户端读写超时
Jedis在调用Redis时,如果出现了读写超时后,会出现下面的异常:
- redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
- 读写超时设置的过短。
- 命令本身就比较慢。
- 客户端与服务端网络不正常。
- Redis自身发生阻塞。
三 客户端连接超时
Jedis在调用Redis时,如果出现了读写超时后,会出现下面的异常:
- redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
- 连接超时设置的过短。
- Redis发生阻塞,造成tcp-backlog已满,造成新的连接失败。
- 客户端与服务端网络不正常。
四、客户端缓冲区异常
Jedis在调用Redis时,如果出现客户端数据流异常,会出现下面的异常。
- redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
- 1.输出缓冲区满。例如将普通客户端的输出缓冲区设置为1M 1M 60:
- 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配置之前章节已经介绍了)
- 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正在加载持久化文件,那么会收到下面的异常。
- redis.clients.jedis.exceptions.JedisDataException: LOADING Redis is loading the dataset in memory
七、Redis使用的内存超过maxmemory配置
Jedis调用Redis执行写操作时,如果Redis的使用内存大于maxmemory的设置,会收到下面的异常,此时应该调整maxmemory并找到造成内存增长的原因(maxmemory之前章节已经介绍了)
- redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'.
八、客户端连接数过大
如果客户端连接数超过了maxclients,新申请的连接就会出现如下异常:
- redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached
此时新的客户端连接执行任何命令,返回结果都是如下:
- 127.0.0.1:6379> get hello
- (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的重要属性
序号 参数名 含义 默认值1 | maxActive | 连接池中最大连接数 | 8 |
2 | maxIdle | 连接池中最大空闲的连接数 | 8 |
3 | minIdle | 连接池中最少空闲的连接数 | 0 |
4 | maxWaitMillis | 当连接池资源用尽后,调用者的最大等待时间(单位为毫秒),一般不建议使用默认值 | -1:表示永远不超时,一直等。 |
5 | jmxEnabled | 是否开启jmx监控,如果应用开启了jmx端口并且jmxEnabled设置为true,就可以通过jconsole或者jvisualvm看到关于连接池的相关统计,有助于了解连接池的使用情况,并且可以针对其做监控统计 | true |
6 | minEvictableIdleTimeMillis | 连接的最小空闲时间,达到此值后空闲连接将被移除 | 30分钟 |
7 | numTestsPerEvictionRun | 做空闲连接检测时,每次的采样数 | 3 |
8 | testOnBorrow | 向连接池借用连接时是否做连接有效性检测(ping),无效连接会被移除,每次借用多执行一次ping命令 | false |
9 | testOnReturn | 向连接池归还连接时是否做连接有效性检测(ping),无效连接会被移除,每次归还多执行一次ping命令 | false |
10 | testWhileIdle | 向连接池借用连接时是否做连接空闲检测,空闲超时的连接会被移除 | false |
11 | timeBetweenEvictionRunsMillis | 空闲连接的检测周期(单位为毫秒) | -1:表示不做检测 |
12 | blockWhenExhausted | 当连接池用尽后,调用者是否要等待,这个参数是和maxWaitMillis对应的,只有当此参数为true时,maxWaitMillis才会生效 | true |
本文部分内容来自《Redis开发与运维》一书,转载请声明。
最后
以上就是淡淡钻石为你收集整理的常见JedisConnectionException异常分析的全部内容,希望文章能够帮你解决常见JedisConnectionException异常分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复