当前位置:首页 > 问答 > 正文

Redis跨域掉线那次真是尴尬到不行,折腾半天才明白问题在哪儿

(引用来源:根据网络工程师社区、技术博客及开发者论坛中多位用户分享的关于Redis连接中断问题的讨论综合整理,具体案例细节源自一篇名为《记一次诡异的Redis跨机房连接故障》的技术复盘文章,以及Stack Overflow上关于“Redis Connection Reset”的相关讨论串)

那次的经历,现在想起来都觉得脸上发烫,我们团队负责维护一个挺重要的线上服务,用户量不小,为了提升性能和可靠性,我们把一部分核心数据放在了Redis里,而且为了容灾,Redis主节点部署在A机房,而调用它的大部分应用服务则放在另一个城市的B机房,这套架构平稳运行了快一年,大家都觉得高枕无忧了。

结果就在一个平平无奇的下午,监控系统突然开始疯狂报警,提示服务大量失败,我们赶紧登录服务器查看,发现应用日志里全是连接Redis超时或者连接被对端重置的错误,一开始,大家的第一反应是Redis服务器挂了或者网络出了大问题,但运维同事很快确认,A机房的Redis主节点活得好好的,监控指标一切正常,CPU、内存、连接数都没啥异常,A机房内部的机器去连Redis,也完全没问题。

这就奇怪了,问题似乎只出现在B机房的应用服务器连接A机房的Redis时,我们开始了漫长的排查,首先怀疑的是不是防火墙规则被谁不小心改了,把端口给封了,检查了一遍,规则纹丝未动,和之前一模一样,然后又想,是不是网络链路不稳定,有丢包?让网络工程师查了链路的监控,显示延迟和丢包率都在正常范围内,没有看到明显的网络风暴或者中断。

我们开始深入应用层面,是不是最近发布的代码有问题?比如引入了新的Redis操作,导致了死锁或者资源耗尽?但回滚到上一个稳定版本的代码,问题依旧,是不是连接池配置不合理?连接数设得太少,被瞬间打满了?我们调大了连接池参数,重启了应用,刚开始几分钟好像有点好转,但很快错误又卷土重来,那段时间,整个团队都焦头烂额,电话会议一直开着,各种可能性都被提出来又否定掉,用户投诉电话都快被打爆了,业务方不停地来问什么时候能恢复,那种压力真的让人喘不过气。

就在大家一筹莫展,甚至开始怀疑是不是遇到了什么玄学问题的时候,团队里一个比较有经验的同事提出了一个之前被忽略的方向:TCP连接的超时和保持机制,他提醒我们,跨机房的网络延迟比机房内部要高得多,而且中间会经过很多网络设备(比如路由器、防火墙)。

他打了个比方:这就像两个人隔着一个很长的走廊打电话,如果两边都不说话,电话局(相当于中间的网络设备)可能会认为你们这通电话已经打完了,为了节省资源,就把线路给掐断了,我们的应用服务和Redis之间,在业务低峰期,可能有一些连接会处于空闲状态,这些空闲的连接,如果长时间没有数据交换,就可能被中间路径上的某个防火墙或路由器认为“已经失效”,从而主动断开了这个TCP连接,但我们的应用服务器和Redis服务器自己并不知道这个连接已经被中间人掐断了,还傻傻地以为连接依然有效,当应用下次试图用这个“僵尸连接”去操作Redis时,一发包,对面可能根本没收到,或者回了一个复位信号,就导致了我们看到的连接错误。

这个解释一下子点醒了我们,我们之前的所有检查都集中在两端的服务器和配置上,完全忽略了中间网络设备的行为,我们重点检查了机房之间防火墙的会话超时设置,果然,发现其TCP空闲超时时间设置得比较短,大约是10分钟,而我们的Redis客户端连接池里,连接的最大空闲时间设置得比这个长得多,这就导致了,一个连接在应用池子里还没到被回收的时间,但已经在网络传输途中被防火墙因为空闲而过期删除了会话表项。

找到根因后,解决方案就相对简单了,我们采取了双管齐下的办法:协调网络团队,适当调大了防火墙的TCP会话超时时间(当然这是在安全允许的范围内);在应用侧的Redis客户端配置中,开启了连接健康检查(比如定时发送PING命令),并缩短了连接池中连接的最大空闲时间,确保连接能定期“活动”一下,不让中间设备有机会认为它已经死了。

做完这些调整并重启应用后,烦人的报警终于消失了,服务恢复了正常,那次故障前前后后折腾了差不多三四个小时,虽然最终问题不算复杂,但排查过程非常曲折,因为一开始的思路就被限制住了,最大的教训就是,在分布式系统里,尤其是跨地域的网络调用,不能只盯着两端看,一定要把中间网络链路的各种特性(特别是超时机制)考虑进去,从那以后,我们在做任何跨机房的服务设计时,都会把连接保活和网络设备超时作为一个必查项,再也不敢掉以轻心了。

Redis跨域掉线那次真是尴尬到不行,折腾半天才明白问题在哪儿