七叶笔记 » 数据库 » 使用Jedis线程池returnResource异常注意事项

使用Jedis线程池returnResource异常注意事项

在线上环境发现了一个工作线程异常终止

看日志先是一些SocketTimeoutException,然后突然有一个ClassCastException

经过在本地人工模拟网络异常的情境,最终复现了线上的这一异常。

又经过深入分析(提出假设-->验证假设),最终找出了导致这一问题的原因。

见如下示例代码

以及日志输出

分析

等执行第二遍的get("foo")时,网络超时,并未实际发送 get foo 命令,等执行sismember时,网络已恢复正常,并且是同一个jedis实例,于是将之前的get foo命令(已在输出流缓存中)一并发送。

执行顺序如下所示

故在上述示例代码中最后的sismember得到的结果是get foo的结果,即一个字符串,而sismember需要的是一个Long型,故导致了ClassCastException。

执行redis的逻辑

为什么线上会出现这一问题呢?原因是其执行redis的逻辑类似这样:

因若是网络异常的话,pool.returnResource(jedis)仍能成功执行,即能将其返回到池中(这时jedis并不为空)。等网络恢复后,并是多线程环境,导致后续其他某个线程获得了同一个Jedis实例(pool.getResource()),

若该线程中的jedis操作返回类型与该jedis实例在网络异常期间第一条未执行成功的jedis操作的返回类型不匹配(如一个是get,一个是sismember),则就会出现ClassCastException异常。

这还算幸运的,若返回的是同一类型的话(如lpop("queue_order_pay_failed"),lpop("queue_order_pay_success")),那我真不敢想象。

如在上述示例代码中的sismember前插入一get("nonexist-key")(redis中不存在该key,即应该返回空).

实际的日志输出为

分析:

get("nonexist-key")得到是之前的get("foo")的结果, 而sismember得到的是get("nonexist-key")的结果,而get("nonexist-key")返回为空,于是这时是报空指针异常了.

解决方法:

不能不管什么情况都一律使用returnResource。更健壮可靠以及优雅的处理方式如下所示:

补充

Ubuntu本地模拟访问redis网络超时:

sudo iptables -A INPUT -p tcp --dport 6379 -j DROP

恢复网络:

sudo iptables -F

补充:

若jedis操作逻辑类似下面所示的话,

若一旦发生了JedisConnectionException,如网络异常,会先执行returnBrokenResource,这时jedis已被destroy了。然后进入了finally,再一次执行returnResource,这时会报错:

临时解决方法

但不建议这样处理,更严谨的释放资源方法见前文所述。

以上就是使用Jedis线程池returnResource异常注意事项的详细内容,更多关于Jedis线程池returnResource异常的资料请关注七叶笔记其它相关文章!

相关文章