背景:上周消息中心 上线了一个新功能(群发消息),监控系统发现调用我的系统里接口总是出现莫名其妙的502状态码,告警了
502概念:502 Bad Gateway是指错误网关,无效网关;在互联网中表示一种网络错误。表现在WEB浏览器中给出的页面反馈。它通常并不意味着上游服务器已关闭(无响应网关/代理) ,而是上游服务器和网关/代理使用不一致的协议交换数据。鉴于互联网协议是相当清楚的,它往往意味着一个或两个机器已不正确或不完全编程。
一 原因分析
1.1 可能是服务器出现了大规模报错导致网关认为服务不可用,直接拒绝
本次上线的功能是群发消息功能,目前疫情结束,通常会有许多人有群发消息通知顾客已恢复正常运营的需求,因此目前这个功能使用非常多,而且这个消息通知规模比较大,每次通常会向几万或者几十万人发送消息,在现有资源情况下,通常会带动服务器有一定的资源波动,因此第一时间怀疑是服务器资源不够用了,系统产生大规模超时等报错让网关层产生了假性服务器不可用的判断,致使网关直接拒绝第三方调用的情况。
我看了下,确实是每次出现502基本都是出现在群发任务调度比较多的情况,但是我在我们日志系统并没有发现成规模的其他报错,另外服务器资源有波动但是也没那么大的波动,因为我们这一般申请服务器资源比较容易,都是做了一定的富余的。
这边咨询了下运维侧最近是否有什么变动或者解决方案,运维侧觉得是服务器资源问题,先直接给我们加了一倍的机器
但是观察后发现502少了但是问题还是没解决
1.2 网关两边链接保活时间不一致
我新功能上线的那一天的同时把我们的服务切到了K8s下,运维侧对那边服务架构做了比较大的调整,对于代理这块,以前使用的是nginx,现在改用了traefik,我们知道在用了代理的情况下,其实客户端和服务器并不是直接交互建立连接的,而是客户端请求交给代理,代理再进行转发, 两边都是对代理层建立连接。
先解释下什么是keepalive?
这是http 1.1协议的缺省配置,在http 1.0的时候,如果你的网页上有10个图片,那么浏览器和服务器之间要同时建立10个连接,把这10个图片发过去然后再关闭这10个连接,显然对于服务器来说,建立10个连接再关闭10个连接,消耗是比较大的。所以在http 1.1协议里增加了keepalive的功能,在发10张图片的时候只需要建立一个连接就够了,只要还有内容要传输,这个通道会始终保持开放状态,不会在传输完毕之后立刻关闭,这就是keepalive保活的意思。
但是keepalive不能把这个连接永远保持,如果没有内容了还继续保持,无疑也是一种浪费,所以这里就产生了超时的概念,keepalive_timeout的意思是说如果这个连接当中没有内容传输了并且超过了这个时间,那么就把这个连接断掉,keepalive_requests的意思是说我们这个连接最多允许传输多少个内容,超过这个内容那么也把它断掉。
那么这个keepalive_timout和我们的502错误之间有什么关系呢?
如我前面所说:因为所有网站的架构都不是浏览器直接连接后端的应用服务器,而一定是中间有nginx、traefik服务器做反向代理的,浏览器和nginx服务器之间建立keepalive连接,nginx再和后端的应用服务器建立keepalive连接,所以这是两种不同的keepalive连接。
我们把浏览器和nginx之间的keepalive连接叫做ka1,把nginx和应用服务器之间的keepalive连接叫做ka2。
如果ka1的超时设置为100秒,也就是说如果100秒之内没有新内容要传输,就把nginx和浏览器之间的连接断掉。而同时,我们把ka2设置为50秒,也就是说如果nginx和应用服务器之间没有新内容要传输,那么就把应用服务器和nginx之间的连接断掉。那么这时候就会产生一个现象:前50秒没有传输内容,在第51秒的时候,浏览器向nginx发了一个请求,这时候ka1还没有断掉,因为没有到100秒的时间,所以这是没有问题的,但是当nginx试图向应用服务器发请求的时候就出问题了,ka2断了!因为ka2的超时设置是50秒,这时候已经超了,所以就断了,这时候nginx无法再从应用服务器获得正确响应,只好返回浏览器502错误!
但是我们根本就没有设置过这些参数啊,怎么会有这种问题呢?
这没关系,既然没有设置过,那系统肯定用的是缺省参数,我们来看一下ka1的缺省设置是多少,也就是nginx(ingress)和浏览器之间的缺省的keepalive_timeout值:
upstream-keepalive-timeout
Sets a timeout during which an idle keepalive connection to an upstream server will stay open. default: 60
ka1的缺省设置是60秒。
我们再看一下ka2的缺省设置是多少秒,Tomcat官方文档上说:
The number of milliseconds this Connector will wait for another HTTP request before closing the connection.
The default value is to use the value that has been set for the connectionTimeout attribute. Use a value of -1 to indicate no (i.e. infinite) timeout.
缺省值等同于connectionTimeout
的值,那connectionTimeout等于多少呢?
The number of milliseconds this Connector will wait, after accepting a connection, for the request URI line to be presented.
Use a value of -1 to indicate no (i.e. infinite) timeout. The default value is 60000 (i.e. 60 seconds)
but note that the standard server.xml that ships with Tomcat sets this to 20000 (i.e. 20 seconds).
Unless disableUploadTimeout is set to false, this timeout will also be used when reading the request body (if any).
connectionTimeout的缺省值是60秒,但是,他们提供的标准的server.xml里把这个值设为了20秒!
那么现在问题就很清楚了,我们的ka1是60秒,而ka2是20秒,从21秒到60秒之间的任何时间有请求进来都会发生502错误。
找到了问题的根源,解决起来就好办了,我们只需要确保ka1的超时设置小于ka2的设置就够了。或者修改ka1,或者修改ka2,都是可以的。
对于traefik也是一样的,我们在切到traefik后,由于客户端到traefik设置的keepalive为180s,而我们traefik到我们服务器设置的为60s,因此如果在客户端到traefik的连接断了后,traefik到服务器连接还没断的时间区间内请求服务,那么极有可能出现502
二 解决
运维侧调整了客户端到traefik的keepalive时间,调整为小于等于我们服务器到traefik的keepalive皆可,我们这里是调整的等于。
后面观察了几天,发现调整后服务器完全正常了,再也没出现过502;
三 总结
其实这次问题还是比较明显的
- 1.出现时机是新功能发布上线后
- 2.502的同时往往伴随着链接数的下降(先是系统充分预热,链接数全部激活了,后期又逐渐下降)
这次的盲点
盲点主要是不清楚运维侧代理把nginx换成了traefik,不然问题会更加明显,定位的更快些;