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

1.异常堆栈

(1) 连接池参数blockWhenExhausted = true(默认)

如果连接池没有可用Jedis连接,会等待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)

(2) 连接池参数blockWhenExhausted = false

设置如果连接池没有可用Jedis连接,立即抛出异常: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)

2、异常描述

上述异常是客户端没有从连接池(最大maxTotal个)拿到可用Jedis连接造成的,具体可能有如下原因:

(1) 连接泄露 (较为常见)

JedisPool默认的maxTotal=8,业务系统从JedisPool中借了8次Jedis,但是没有归还,当第9次借时,就会发生异常

(2) 业务并发量大,maxTotal确实设置小了。

举个例子:

一次命令时间(borrow|return resource + Jedis执行命令(含网络) )的平均耗时约为1ms,一个连接的QPS大约是1000,业务期望的QPS是50000那么理论上需要的资源池大小是50000 / 1000 = 50个,实际maxTotal可以根据理论值进行微调。

(3) Jedis连接还的太慢

例如Redis发生了阻塞(例如慢查询等原因),所有连接在超时时间范围内等待,并发量较大时,会造成连接池资源不足。

(4) 其他问题

例如丢包、DNS、客户端TCP参数配置。

3.解决方法:

可以看到这个问题稍微复杂一些,不要被异常的表象所迷惑,简单地认为连接池不够就盲目加大maxTotal,要具体问题具体分析。

二、客户端缓冲区异常

1.异常堆栈

redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.    at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:199)    at redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40)    at redis.clients.jedis.Protocol.process(Protocol.java:151)......

2.异常描述:

这个异常是客户端缓冲区异常,产生这个问题可能有三个原因:

(1) 常见原因:多个线程使用一个Jedis连接,正常的情况是一个线程使用一个Jedis连接,可以使用JedisPool管理Jedis连接,实现线程安全,防止出现这种情况.

(2) 客户端缓冲区满了

Redis有三种客户端缓冲区:

普通客户端缓冲区(normal):用于接受普通的命令,例如get、set、mset、hgetall、zrange等

slave客户端缓冲区(slave):用于同步master节点的写命令,完成复制。

发布订阅缓冲区(pubsub):pubsub不是普通的命令,因此有单独的缓冲区。

(3) 长时间闲置连接被服务端主动断开

可以查询timeout配置的设置以及自身连接池配置是否需要做空闲检测。

三、非法客户端地址

1.异常堆栈

Caused by: redis.clients.jedis.exceptions.JedisDataException: ERR illegal address    at redis.clients.jedis.Protocol.processError(Protocol.java:117)    at redis.clients.jedis.Protocol.process(Protocol.java:151)    at redis.clients.jedis.Protocol.read(Protocol.java:205)    ......

2.异常描述:

Redis实例配置了白名单,但当前访问Redis的客户端(IP)不在白名单中。

3.解决方法:

添加该客户端(IP)的白名单

四、客户端连接数达到最大值

1.异常堆栈

redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached

2.异常描述:

如果客户端连接数超过了Redis实例配置的最大maxclients

3.解决方法:

调大最大连接数,并需要找到连接数暴涨的原因(因为上述调整只是临时调整)。

五、客户端读写超时

1.异常堆栈

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

2.异常描述:

该问题原因可能有如下几种:

(1) 读写超时设置的过短。

(2) 有慢查询或者Redis发生阻塞。

(3) 网络不稳定。

3.解决方法:

排查读写超时原因

六、密码相关的异常

1.异常堆栈

Redis设置了密码,客户端请求没传密码:

Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required.     at redis.clients.jedis.Protocol.processError(Protocol.java:127)     at redis.clients.jedis.Protocol.process(Protocol.java:161)     at redis.clients.jedis.Protocol.read(Protocol.java:215)

Redis没有设置密码,客户端传了密码:

Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: ERR Client sent AUTH, but no password is set     at redis.clients.jedis.Protocol.processError(Protocol.java:127)     at redis.clients.jedis.Protocol.process(Protocol.java:161)     at redis.clients.jedis.Protocol.read(Protocol.java:215)

客户端传了错误的密码:

redis.clients.jedis.exceptions.JedisDataException: ERR invalid password    at redis.clients.jedis.Protocol.processError(Protocol.java:117)    at redis.clients.jedis.Protocol.process(Protocol.java:151)    at redis.clients.jedis.Protocol.read(Protocol.java:205)

2.解决方法:

弄清楚到底有没有密码,密码是否正确。

七、事务异常

1.异常堆栈

redis.clients.jedis.exceptions.JedisDataException: EXECABORT Transaction discarded because of previous errors

2.异常描述:

Redis的事务异常:比如事务中包含了错误的命令

3.解决方法:

修复代码错误。

八、类转换错误

1.异常堆栈

java.lang.ClassCastException: java.lang.Long cannot be cast to java.util.List         at redis.clients.jedis.Connection.getBinaryMultiBulkReply(Connection.java:199)         at redis.clients.jedis.Jedis.hgetAll(Jedis.java:851)         at redis.clients.jedis.ShardedJedis.hgetAll(ShardedJedis.java:198)  

2.异常描述:

jedis获取的数据的方法和实际数据类型不一致

3.解决方法:

修复代码错误。

九、命令使用错误

1.异常堆栈

Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: WRONGTYPE Operation against a key holding the wrong kind of value    at redis.clients.jedis.Protocol.processError(Protocol.java:127)    at redis.clients.jedis.Protocol.process(Protocol.java:161)    at redis.clients.jedis.Protocol.read(Protocol.java:215).....

2.异常描述:

例如key="hello"是字符串类型的键,而hgetAll是哈希类型的键,所以出现了错误。

3.解决方法:

修复代码错误。

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

1.异常堆栈

redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'.

2.异常描述:

Redis节点(如果是集群,则是其中一个节点)使用大于该实例的内存规格(maxmemory配置)。

3.解决方法:

原因可能有以下几个:

业务数据正常增加客户端缓冲区异常:例如使用了monitor、pub/sub使用不当等等纯缓存使用场景,但是maxmemory-policy配置有误(例如没有过期键的业务配置volatile-lru)

十一、Redis正在加载持久化文件

1.异常堆栈

redis.clients.jedis.exceptions.JedisDataException: LOADING Redis is loading the dataset in memory

2.异常描述:

Jedis调用Redis时,如果Redis正在加载持久化文件,无法进行正常的读写。

3.解决方法:

业务重试

十二、Lua脚本超时

1.异常堆栈

redis.clients.jedis.exceptions.JedisDataException: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

2.异常描述:

如果Redis当前正在执行Lua脚本,并且超过了lua-time-limit,此时Jedis调用Redis时,会收到下面的异常

3.解决方法:

按照异常提示:You can only call SCRIPT KILL or SHUTDOWN NOSAVE. (使用script kill:kill掉Lua脚本)

十三 连接超时

1.异常堆栈

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

2.异常描述:

可能产生的原因:连接超时设置的过短。tcp-backlog满,造成新的连接失败。客户端与服务端网络不正常。


3.解决方法:

排查调用链路,定位原因