在线上环境发现了一个工作线程异常终止 看日志先是一些SocketTimeoutException,然后突然有一个ClassCastException 1 2 3 4 5 redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException:
在线上环境发现了一个工作线程异常终止看日志先是一些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网络超时:
恢复网络:
补充: 若jedis操作逻辑类似下面所示的话,
若一旦发生了JedisConnectionException,如网络异常,会先执行returnBrokenResource,这时jedis已被destroy了。然后进入了finally,再一次执行returnResource,这时会报错:
临时解决方法
但不建议这样处理,更严谨的释放资源方法见前文所述。 |
2021-04-08
2021-10-03
2021-07-26
2019-10-11
2022-08-27