转自 @twt 社区,作者:付磊。
【导读】Jedis 是 Redis 的 java 版本的客户端实现。在 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
这个问题可能会比拟辣手,因为此时无奈执行 Redis 命令,一般来说能够从两个方面进行着手。
1. 客户端:如果 maxclients 参数不是很小的话,利用方的客户端连接数根本不会超过 maxclients,通常来看是因为利用方对于 Redis 客户端使用不当造成的。此时如果利用方是分布式构造的话,能够通过下线局部利用节点(例如占用连贯较多的节点),使得 Redis 的连接数先降下来。从而让绝大部分节点能够失常运行,此时在再通过查找程序 bug 或者调整 maxclients 进行问题的修复。
2. 服务端:如果此时客户端无奈解决,而以后 Redis 为高可用模式(例如 Redis Sentinel 和 Redis Cluster),能够思考将以后 Redis 做故障转移。
此问题不存在确定的解决形式,然而无论从哪个方面进行解决,故障的疾速复原极为重要,当然更为重要的是找到问题的所在,否则一段时间后客户端连接数仍然会超过 maxclients。
附赠 GenericObjectPoolConfig 的重要属性