Redis作为当前服务架构不可或缺的Cache,其支持丰富多样的数据结构,Redis在使用中其实也有很多坑,本次博主遇到的坑或许说是Java程序员会遇到的多一点,下面就听博主详细道来。
线上服务堵塞
1 | String key = keyOf(appid); |
12月2日被告知服务出现异常,查看日志发现其运行到上述代码getMap方法处后日志就没有内容了。
问题分析
1 | "pool-13-thread-6" prio=10 tid=0x00007f754800e800 nid=0x71b5 waiting on condition [0x00007f758f0ee000] |
从线程日志可以看出服务堵塞在获取redis连接
处.
分析:
- 代码配置中redis最大连接为
3000
- redis配置中
session_max_timeout
为0,即永不断开连接
一次修改分析
从以上两点分析得出,redis连接被耗尽,于是查找代码得知由于重写spring-data-redis中的hscan方面导致,代码如下:
1 | RedisConnection rc = redisTemplate.getConnectionFactory().getConnection(); |
上述代码返回ConvertingCursor后未释放连接,导出连接被占满。
二次修改分析
于是修改代码为正常释放连接
1 | try { |
代码经过上线,再次跑程序查看线上日志发现报了大量的Connection time out.
于是博主就思考是不是由于重写代码不对,尝试使用spring-data-redis的原生代码,即直接调用hashOps.scan(key, scanOptions)
方法,再次上线。
上线后观察日志:发现这次不是报Connection time out
,日志中大量报Unknown reply:
错误。
分析如下:
由于代码是在多线程环境下运行,有几百个线程去调用hscan操作,spring-data-redis原生的代码执行完一次hscan操作后就会关闭连接并返回一个迭代器Cursor,但是遍历Cursor时在本次count后会再次根据游标重新使用该连接进行查询,可是连接却已经被关闭,这时会使用新的连接是可以正常迭代的,但是一旦复用到其他线程使用的连接则会导致报错Unknown reply
.
三次修改分析
经过思考后得出结论,redis在执行scan操作时一旦连接被释放,那么scan操作将不会进行下去,则报Connection time out.
查阅官方文档得出结论,redis的scan操作需要full iteration,即最优方式是一个连接将以此scan任务执行完全后释放该连接。
修改代码如下:
1 | RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); |
将连接返回给业务代码,并在业务代码执行完毕后将连接释放,问题解决。
总结
- 连接一旦开启就必须释放,否则造成内存泄漏或服务堵塞不可用
- 重写代码时需要谨记仔细查阅官方文档给出的方案并实施
- 多线程下使用redis的scan操作需要使用一个连接遍历完Cursor,而不能复用连接,否则导致报错
Unknown reply.