Jedis常见异常汇总

点击有惊喜


Jedis虽然使用起来比较简单,但是如果不能根据使用场景设置合理的参数(例如连接池参数),不合理的使用一些功能(例如Lua和事务)也会产生很多问题,本文对这些问题逐个说明:

一.无法从连接池获取到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.异常描述

1

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

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

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();
}
(2) 业务并发量大,maxTotal确实设置小了。

举个例子:

  • 一次命令时间(borrow|return resource + Jedis执行命令(含网络) )的平均耗时约为1ms,一个连接的QPS大约是1000

  • 业务期望的QPS是50000

那么理论上需要的资源池大小是50000 / 1000 = 50个,实际maxTotal可以根据理论值进行微调。

(3) Jedis连接还的太慢

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

(4) 其他问题

例如丢包、DNS、客户端TCP参数配置,具体可以参考:Jedis介绍及常见问题分析

3.解决方法:

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

连接池参数优化可以参考:JedisPool资源池优化

4.处理人

客户先确认,如解决不了,需要借助工单解决

二、客户端缓冲区异常

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连接,实现线程安全,防止出现这种情况,例如下面代码中两个线程用了一个Jedis连接:
new Thread(new Runnable() {

    public void run() {
        for (int i = 0; i < 100; i++) {
            jedis.get("hello");
        }
    }
}).start();

new Thread(new Runnable() {

    public void run() {
        for (int i = 0; i < 100; i++) {
            jedis.hget("haskey", "f");
        }
    }
}).start();
(2) 客户端缓冲区满了

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

config set client-output-buffer-limit "normal 1048576 1048576 60 slave 268435456 67108864 60 pubsub 33554432 8388608 60"

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

(3) 长时间闲置连接被服务端主动断开,可以查询timeout配置的设置以及自身连接池配置是否需要做空闲检测。

3.解决方法和处理人:

客户:排查自身代码是否使用JedisPool管理Jedis连接,是否存在并发操作Jedis的情况。

工单: 排查(2)(3),阿里云Redis中timeout=0,也就是不会主动关闭空闲连接,缓冲区设置为0 0 0 也就是不会对客户端缓冲区进行限制,一般不会有问题

三、非法客户端地址

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)的白名单

4.处理人

客户或者工单都可以

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

1.异常堆栈
redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached
2.异常描述:

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

3.解决方法:

提工单帮助临时调大最大连接数,并让客户找到连接数暴涨的原因(因为上述调整只是临时调整),

4.处理人
  • 工单:临时调整最大连接数,协助定位问题
  • 客户:定位自身问题(可以定位连接最多的客户端),找到问题原因(例如连接池配置等)

五、客户端读写超时

1.异常堆栈
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
2.异常描述:

该问题原因可能有如下几种:
(1) 读写超时设置的过短。
(2) 有慢查询或者Redis发生阻塞。
(3) 网络不稳定。

3.解决方法:

客户提供读写超时时间,提交工单定位相关原因

4.处理人:

工单。

六、密码相关的异常

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)
2.解决方法:弄清楚到底有没有密码

七、事务异常

1.异常堆栈
redis.clients.jedis.exceptions.JedisDataException: EXECABORT Transaction discarded because of previous errors
2.异常描述:

这个是Redis的事务异常:事务中包含了错误的命令,例如如下sett是个不存在的命令。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> sett key world
(error) ERR unknown command 'sett'
127.0.0.1:6379> incr counter
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
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)   
java.lang.ClassCastException: java.util.ArrayList cannot be cast to [B
         at redis.clients.jedis.Connection.getBinaryBulkReply(Connection.java:182)
         at redis.clients.jedis.Connection.getBulkReply(Connection.java:171)
         at redis.clients.jedis.Jedis.rpop(Jedis.java:1109)
         at redis.clients.jedis.ShardedJedis.rpop(ShardedJedis.java:258)
.......
2.异常描述:

Jedis正确的使用方法是:一个线程操作一个Jedis,通常来讲产生该错误是由于没有使用JedisPool造成的,例如如下代码在两个线程并发使用了一个Jedis。(get、hgetAll返回类型也是不一样的)


new Thread(new Runnable() {

    public void run() {
        for (int i = 0; i < 100; i++) {
            jedis.set("hello", "world");
            jedis.get("hello");
        }
    }
}).start();

new Thread(new Runnable() {

    public void run() {
        for (int i = 0; i < 100; i++) {
            jedis.hset("hashkey", "f", "v");
            jedis.hgetAll("hashkey");
        }
    }
}).start();
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是哈希类型的键,所以出现了错误。

jedis.set("hello","world");
jedis.hgetAll("hello");
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)

紧急处理,可以临时提工单帮助临时调整maxmeory,后续咨询用户是否升配或者调整配置。

4.处理人
  • 客户:找到内存增大的原因。
  • 工单:协助临时调整maxmeomry,如果客户需要,可以协助解决

点击有惊喜


redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection refused (Connection refused) 表示在与Redis服务器建立连接时发生了连接被拒绝的异常。这可能是由于以下几种原因引起的。 1. Redis服务器未启动或未正确配置。请确保Redis服务器正在运行,并且已经正确配置了端口和IP地址。 2. 网络连接问题。请检查网络连接是否正常,确保能够与Redis服务器建立连接。 3. 鉴权密码错误。如果Redis服务器设置了密码鉴权,客户端在连接时需要提供正确的密码。如果客户端没有提供密码或提供了错误的密码,会导致连接被拒绝。 解决这个问题的方法有以下几种: 1. 检查Redis服务器是否正常运行,并确保配置正确。 2. 检查网络连接是否正常,尝试重新连接Redis服务器。 3. 如果Redis服务器设置了密码鉴权,确保客户端在连接时提供正确的密码。 请根据具体情况采取相应的解决方法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [详解Redis开启远程登录连接](https://download.youkuaiyun.com/download/weixin_38740201/14836767)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatgptT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Redis常见异常汇总](https://blog.youkuaiyun.com/Huangjiazhen711/article/details/127424204)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatgptT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值