前言
最近又遇见一个线上问题,就是Redis连接数达到最大值,导致服务无法获取连接,大量用户反馈APP无法登陆。经过一系列的排查最终定位到问题是使用Redis后没有关闭连接,导致无法获取连接。因为出现问题的服务并没有使用连接池,所以在每次使用后必须手动关闭连接,否则运行一段时间后服务连接必将被占满。本文为技术分享,可能有误望谅解。
问题现象
线上服务日志出现大量的异常:ERR max number of clients reached
1 | redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached |
整个APP使用到Redis的接口也全都不可用,一看到ERR max number of clients reached就猜测是Redis使用后未关闭连接,因为并发请求量还达不到占满Redis连接数的地步,为了验证这一想法按照下面排查步骤一步一步执行。
排查步骤
Linux排查命令
首先介绍关于Linux的一些监控命令,如下图所示
如果想知道服务所在机器和Redis服务建立多少连接可以使用ss或netstat命令来查看。
1 | ss -tn | grep 6379 |
同时也可以实时监控连接数的变化
1 | watch -n 1 -d 'netstat -aln | grep 6379 | wc -l' |
通过以上命令就可以在你的机器上获取Redis连接数。
排查
在机器上使用watch命令搜索目前与Redis建立的连接数,发现连接数一直居高不下,而目前也没有特别多的用户访问,为了尽快恢复线上服务先重启再后续观察。目前的症状是服务运行一段时间后无法获取连接且访问量并不大,首先是去排查代码(如果有权限查看阿里云后台可以先使用Redis的client list命令查看目前客户端的连接信息)看是否有连接未正常关闭。在查看Redis的封装工具类后找到一处代码使用Redis后未关闭连接,如下所示
1 | Jedis jedis = getJedis(); |
在获取一个jedis连接后,直接调用了一个scard()方法,该方法是获取Redis的Set元素的个数。找到可能出现的问题后,先将此处代码改写,使用try()语法释放jedis连接:
1 | try(Jedis jedis = getJedis()){ |
try括号中的资源会在try语句执行完后自动释放,前提是这些可关闭的资源必须实现java.lang.AutoCloseable接口。查看Jedis的继承关系:
在将jedis连接正常关闭后,继续使用watch命令查看机器连接数,恢复正常不再保持递增状态。
Redis相关命令排查
如果能够使用Redis的一些命令,可以更快地排查到是哪一行代码没有释放连接。
- info clients命令:查看Redis连接客户端的信息,可以查看目前有多少客户端连接
1 | ip:6379>info clients |
- client list命令:查看Redis连接客户端的具体信息
1 | ip:6379>client list |
在线上Redis服务使用client list命令后发现有大量的未释放连接,cmd=scard这里可以看到是scard命令未释放,idle=19907和age=19907可以看到该命令存活时间达到19907秒。
后记
在写代码时一定要注意资源的释放,否则很容易出现线上事故引起用户投诉;在改造成本不大的时候可以使用池化技术来优化系统,例如http连接池,数据库连接池等,自动管理连接避免出现未手动释放的情况,当然如果改造成本比较大时还是注意在每次使用后都要手动关闭!