Redis学习之Jedis常见异常汇总
- 概述
- 错误明细
-
- redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
- redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream
- redis.clients.jedis.exceptions.JedisDataException: ERR illegal address
- redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached
- redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
- redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required
- redis.clients.jedis.exceptions.JedisDataException: EXECABORT Transaction discarded because of previous errors
- java.lang.ClassCastException: java.lang.Long cannot be cast to java.util.List
- redis.clients.jedis.exceptions.JedisDataException: WRONGTYPE Operation against a key holding the wrong kind of value
- redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'
- redis.clients.jedis.exceptions.JedisDataException: LOADING Redis is loading the dataset in memory
- redis.clients.jedis.exceptions.JedisDataException: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
- redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
- UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.
- java.lang.NoClassDefFoundError
- redis.clients.jedis.exceptions.JedisDataException: ERR unknown command
- redis.clients.jedis.exceptions.JedisDataException: Please close pipeline or multi block before calling this method.
- redis.clients.jedis.exceptions.JedisDataException: ERR command role not support for normal user.
- 其他问题
- 参考链接
概述
Jedis虽然使用起来比较简单,但是不能根据使用场景设置合理的参数(例如连接池参数),或者不合理地使用了一些功能(例如Lua和事务)时,也会产生很多问题,本文对这些常见问题进行逐一说明。
错误明细
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
- 异常堆栈
连接池参数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)
连接池参数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)
- 异常描述
上述异常是客户端没有从连接池(最大maxTotal个)拿到可用Jedis连接造成的,具体可能有如下原因:
- 连接泄露 (较为常见)
JedisPool默认的maxTotal=8,下面的代码从JedisPool中借了8次Jedis,但是没有归还,当第9次(jedisPool.getResource().ping())
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) {
logger.error(e.getMessage(), e);
}
}
jedisPool.getResource().ping();
所以推荐使用的代码规范是:
执行命令如下:
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//具体的命令
jedis.executeCommand()
} catch (Exception e) {
//如果命令有key最好把key也在错误日志打印出来,对于集群版来说通过key可以帮助定位到具体节点。
logger.error(e.getMessage(), e);
} finally {
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
if (jedis != null)
jedis.close();
}
- 业务并发量大,maxTotal确实设置小了
举个例子:
(1) 一次命令时间(borrow|return resource + Jedis执行命令(含网络) )的平均耗时约为1ms,一个连接的QPS大约是1000
(2) 业务期望的QPS是50000
那么理论上需要的资源池大小是50000 / 1000 = 50个,实际maxTotal可以根据理论值进行微调。
- Jedis连接还的太慢
例如Redis发生了阻塞(例如慢查询等原因),所有连接在超时时间范围内等待,并发量较大时,会造成连接池资源不足。
- 其他问题
例如丢包、DNS、客户端TCP参数配置,具体可以参考:Jedis介绍及常见问题分析
- 解决方法
可以看到这个问题稍微复杂一些,不要被异常的表象所迷惑,简单地认为连接池不够就盲目加大maxTotal,要具体问题具体分析。
连接池参数优化可以参考:JedisPool资源池优化
还有一种情况:从池子里拿连接,由于没有空闲连接,需要重新生成一个Jedis连接,但是连接被拒绝。
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
at redis.clients.util.Pool.getResource(Pool.java:50)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:99)
at TestAdmin.main(TestAdmin.java:14)
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused
at redis.clients.jedis.Connection.connect(Connection.java:164)
at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:80)
at redis.clients.jedis.BinaryJedis.connect(BinaryJedis.java:1676)
at redis.clients.jedis.JedisFactory.makeObject(JedisFactory.java:87)
at org.apache.commons.pool2.impl.GenericObjectPool.create(GenericObjectPool.java:861)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:435)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363)
at redis.clients.util.Pool.getResource(Pool.java:48)
... 2 more
Caused by: java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:579)
at redis.clients.jedis.Connection.connect(Connection.java:158)
... 9 more
可以从at redis.clients.jedis.Connection.connect(Connection.java:158)看到实际是一个Socket连接:
socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// <-@wjw_add
158: socket.connect(new InetSocketAddress(host, port), connectionTimeout);
一般这种需要检查Redis的域名配置是否正确,排查该段时间网络是否正常
redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream
- 异常堆栈
客户端缓冲区异常
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)
......
- 异常描述
这个异常是客户端缓冲区异常,产生这个问题可能有三个原因:
- 常见原因:多个线程使用一个Jedis连接,正常的情况是一个线程使用一个Jedis连接,可以使用JedisPool管理Jedis连接,实现线程安全,防止出现这种情况,例如下面代码中两个线程用了一个Jedis连接:
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
jedis.get("hello");