目录
【问题现象】
线上问题:业务后台同一个customerId下有两个账号accountId
1. 产生错误日志:「B cannot cast to java.lang.Long」
2. Redis通过key拿到错乱的value值
【问题分析】
1. JedisConnection是通过JedisPool进行复用的
2. 异常情况下(如Socket超时等),由于未正确关闭问题连接,将会导致下次Redis请求复用了有问题的Connection
【问题原因】
jedis的错误使用,问题代码如下:
经过对代码的深入分析,我们发现问题的关键在于returnResource(jedis)这个操作;因若是网络异常的话,pool.returnResource(jedis)仍能成功执行,即能将其返回到池中(这时jedis并不为 空)。 等网络恢复后,并是多线程环境,导致后续其他某个线程获得了同一个Jedis实例(pool.getResource())。
【问题影响】
1.场景1
请求1: 获取redis连接 -> 发送GET("Uid1")指定 -> 网络异常 -> 归还问题连接
请求2: 复用问题连接 -> 发送INCR指令
分析:在执行请求1的GET("Uid1")时,网络超时,并未实际发送GET("Uid1") 命令,等执行INCR时,网络已恢复正常,
并且是同一个jedis实例,于是将之前的GET("Uid1")命令(已在输出流缓存中)一并发送;INCR指令实际返回的
值是GET("Uid1")的值,两个指令的返回值类型不同,导致了ClassCastException,将会触发问题1,
即类型转换失败「B cannot cast to java.lang.Long
2. 场景2
请求1:获取连接 -> 发送GET("Uid1")指定 -> 网络异常 -> 归还问题连接
请求2:复用问题连接 -> 发送GET("Uid2")指令
分析: 假设 GET("Uid1")的结果是 "12345" ,GET("Uid2")的结果是"7890"
前后两次发送相同类型指令,GET("Uid2")得到的结果将是"12345",触发问题2,即通过key获取到错乱的value值
【解决方法】
推荐的Jedis使用方式,try-catch-finally代码块,catch捕获连接异常,finally里returnBrokenResource,代码如下:
【后续】
Jedis 3.0 以后(含3.0),Jedis returnBrokenResource returnResource的标准写法
finally {
if (jedis != null) {
jedis.close();
}
}
1、redis的使用应该要充分考虑各种异常场景下的异常处理。
2、对java的jedis使用应该要充分了解returnResource和returnBrokenResource的区别