记一次Redis数据库配置致使的链接数泄露的问题

问题背景

去年圣诞节当天,忽然收到一个我经手过的项目的告警邮件,错误消息显示“Redis::CommandError: ERR max number of clients reached”
Redis 链接数告警redis

什么状况?难道这个项目翻车了?第一反应是这台服务器运行着自建的 Redis 数据库,可是客户端只有同个内网的一个 Ruby on Rails 的应用,怎么会有链接数爆掉的可能?数据库

理论链接数计算

老衲掐指一算:安全

  1. sidekiq 客户端所需链接数: 对面 Rails 应用有 10 个 Unicorn 工做进程,每一个unicorn进程初始化一个 sidekiq 客户端,一个 sidekiq 客户端默认链接池大小是 5,并且是懒惰策略,按需链接的,最大值是 10 x 5 = 50;
  2. 显式 Redis 链接: 程序代码里有一个 $redis 全局变量,初始化了一个 redis 链接,10个工做进程,也就是 10 个链接;
  3. sidekiq 服务端所需链接数: sidekiq server 端 concurrency 配置是 10,那么按照官方文档,另有加上 2 个链接,也就是12个链接;
  4. Rails cache 所需链接数: 按照redis-store gem 源码,默认链接池大小应该是 5,10个 unicorn 工做进程,按需链接,最大值是 10 x 5 = 50。

在不考虑其余可能还用到 Redis 链接的状况下,目前已知的最大 Redis 链接数需求是 122,这个数远小于 Redis 理论最大链接数啊,并且当时显示链接数到达上万!并且这个项目已经不多访问,压力极其小,不大可能会达到理论所需链接数啊!服务器

必定是有某种神秘力量在主导这一切!!!网络

监控与分析

以上理论最大链接数分析只是定性分析,只能大概说明有一些诡异的东西存在,而想真正确认问题根源,还得作定量分析,只有数据才能说明一切!ide

初步观察:Redis 数据库服务器端监控

事不宜迟,要采集数据,第一步就是加监控,因此当时就紧急写了一个定时采集 Redis 客户端数量(使用 redis 内建 CLIENT LIST 命令)的脚本,结合 crontab 定时运行,将结果写入文件,做为后续分析的基础。
监控脚本spa

经过监控脚本,发现几个有意思的现象:code

  1. 从 Redis 数据库服务端采集的数据看,一直只有来自一台内网机器,也就是我前面说的 Rails 程序所在的服务器的链接,说明这个 Redis 数据库不存在共享给其余应用的可能性;
  2. 通过3天左右的监控,即从12.25到12.28,连续3天,Redis 链接数一直稳步上升,平均每日增长 70-80。在典型的系统资源泄露类(好比内存泄露)问题的场景中,这样的线条看起来特别熟悉,因此,真的是链接数泄露了?

链接数数量稳步攀升

进一步分析:Redis 数据库服务器端与客户端链接数对比分析

在有了上一步的发现以后,我继续用系统命令 sudo netstat -apnt 检查 6379 端口链接数发现,客户端机器也才只有 42 个左右的链接到 redis 服务器端,结合最开始的理论链接数分析,这个数量是比较合理的。server

可是!可是!反过来去服务端机器用一样的命令检查,在服务端视角,却有多达300+个客户端创建的链接,并且都是在 ESTABLISHED 状态!这个数量和上面另外一种监控方式获得的数量一致!
服务器端与客户端 TCP 链接数不匹配进程

究竟是什么状况?还能有这种操做?
服务器端与客户端谁真谁假

问题根源

至此,Redis 链接数泄露是板上钉钉的事情了,但是又是为何呢?为此,我在网上搜索了不少问答跟文章,后来总算找到了答案,果不其然,仍是默认配置的问题。

Redis 默认配置

redis 为了不客户端链接数过多,有一个timeout配置,意思是若是链接的空闲时间超过了timeout的值,则关闭链接。默认配置是0,意思是没有超时限制,永远不关闭链接。生产上显然不会配置0……
redis timeout配置解释

OMG!赶忙打开咱们的 redis 的配置文件验证是否如此,果不其然,redis一直保持着默认配置!
redis timeout 默认配置

至此,很好解释为何链接数会泄露了,由于有不少空闲或者实际上客户端已经断开的链接,在服务器端一侧仍然保持着。那什么状况会致使这样的状况发生呢?

我猜想:

  1. 网络通讯差: 按照 TCP 协议,客户端断开链接时,向服务器端发送 FIN 信号,可是服务端未接收到,客户端超时后放弃等待,直接断开,服务端因为通讯故障,保持了 ESTABLISHED 状态,不过因为两端机器在同个内网,网络质量没有理由不行;
  2. 客户端异常: 客户端链接以后,因为代码运行过程当中产生异常,致使未正常释放或者关闭链接,sidekiq 的worker极可能就有这类问题。这个的可能性很是大,毕竟我平常写 bug (/ω╲)。

问题修复

找到问题根源以后,修复起来就简直太简单了。事实上,开发领域就是如此,绝大部分时间都花在了找 bug 上,而改掉bug,可能只须要一分钟不到。

首先,修改了下 redis 数据库配置:
redis 修改为建议配置

成功重启 redis 以后,从新运行前面的监控脚本,以便观察修复后状况,初步能够确认这下服务器端和客户端的链接数一致了:

配置生效重启后,屡次从新检查两端看到的链接数,都一直保持一致了,说明服务端能正常释放一些 idle 链接了

再又通过几天的脚本自动采集数据后分析,系统又恢复平稳运行了,链接数一直稳定在理论最大链接数之下。
redis 链接数稳定,稳定在理论最大链接数之下

总结

这个问题的根源其实很小,可是排查过程仍是花了挺多时间,主要是须要等待采集到足够的数据后用于分析。其余心得体会:

  1. 保护“案发现场”很重要,要想挖掘问题根源,必须保持环境可重现,此次出现问题的时候虽然第一时间重启了 redis 使服务恢复,可是因为没有修改任何配置,因此使得后来的监控可以发现问题根源;
  2. 使用开源软件,必须对默认配置保持警戒,相信应该有人之前据说过 redis 默认监听0.0.0.0来源请求的安全漏洞;
  3. 这个项目因为开始较早,当时并无考虑使用 Redis 云数据库,自建数据库有风险,须要慎重对待,尽量的状况下,专业的事情,交给专业的人去作。
相关文章
相关标签/搜索