Code前端首页关于Code前端联系我们

499 以及 nginx 配置不正确导致的故障转移机制故障

terry 2年前 (2023-09-28) 阅读数 116 #未命名

nginx 499 在服务器推送流量高峰期间长期存在。有时,它可能会达到警报阈值并触发小的警报波。然而,主观上,499 始终被认为是客户倡议。出现断线的情况可能与用户在推送高峰期打开推送后迅速杀掉应用有关,且没有进一步排查问题根源。

不过,最近在非高峰时段,偶尔会出现499超过报警阈值的情况。有时一天发生几次,有时几天一次,通常只持续几分钟,而且这类错误通常集中在一台机器上。 Api机器与高峰期多台机器同时报警499明显不同。我的脑海里浮现出问号。经过和朋友一起排查,最后发现之前的499是客户端主动断开造成的,与服务器端关系不大。不言而喻的认知是错误的。我们把它保存在这里吧。

499

499的含义及可能原因实际上不是标准的HTTP协议状态码而是自定义的nginx状态码。 nginx官方文档中没有对该状态码的明确解释。这里引用了它。博文中比较专业的解释:

HTTP error 499 simply means that the client shut off in the middle of processing the request through the server. The 499 error code puts better light that something happened with the client, that is why the request cannot be done. So don’t fret: HTTP response code 499 is not your fault at all.

大意是,499一般表示客户端正在主动终止处理进程,而HTTP请求仍在处理中——丢弃相应的网络连接。 499一般表示客户端出现问题,与服务器无关。
以下是nginx源码中的注释:

/*
* HTTP does not define the code for the case when a client closed
* the connection while we are processing its request so we introduce
* own code to log such situation when a client has closed the connection
* before we even try to send the HTTP header to it
*/
#define NGX_HTTP_CLIENT_CLOSED_REQUEST     499

这意味着nginx引入了自定义代码499来保存客户端中断时nginx尚未处理完请求的场景。
记得很多年前第一次接触499场景的时候,在网上搜索的时候也看到过类似的答案,所以我一直认为499与服务器关系不大,应该都是客户端造成的。

客户主动行为导致499的例子

我曾经接触过联想的搜索界面,其499的比例比其他API高出几十倍——一骑绝尘,单看这个API,基本上已经在搜索引擎中长时间超出报警阈值,我们还监控了异常的具体原因,最终与客户合作伙伴一起得出结论:联想界面搜索499比例较高属于正常现象,因为:

  1. 该API的调用场景是当用户搜索时,当用户在搜索框中输入搜索词时,用户每输入一个字符,就会立即调用该API,并返回最后一次输入的关联结果显示给用户,从而实现近实时的搜索关联功能。
  2. 因为每次用户输入一个新字符时,都会触发最后一次API调用请求,即使之前的调用请求还在进行中,客户端应该直接终止那些没有实际效果的旧请求,这会反映出来在nginx日志中上面是499,说明客户端主动断开连接。

所以,尽管联想的API搜索与普通API的499的高比例不同,但它是完全合理的。断开活动连接是客户端的责任,但他们没有做错任何事,服务器也没有问题。

被动客户行为导致 499 的示例

之前认为客户行为导致 499 的另一个示例是推送的峰值。有些用户可能会在推送打开后立即杀掉应用,而在推送高峰期,一般来说,压力比较大,响应会比高峰期慢。目前,某些 API 请求可能仍待处理。那时,用户杀死了应用程序——应用程序不公平地死掉了,无可奈何——相应的连接自然就断开了,操作系统恢复了。 ,结果也是499。这种场景下,服务器端是没有问题的。

服务器端问题会导致499?

从上面两个例子来看,499无论是主动行为还是被动行为,都是客户造成的。正是这两个例子,加深了博主的认识:499服务器不应该承担责任。 。
总结一下服务器端错误可能导致的nginx错误码,应该主要是以下几种场景:

  1. 500:内部错误,通常是请求参数直接导致上游处理线程的执行代码错误。业务代码或者框架直接返回内部错误
  2. 502:一般情况是上游服务器直接挂掉,无法连接。 nginx无法访问上游,所以返回Bad Gateway
  3. 503:上游负载过大,但不挂,服务不可用
  4. 504直接返回。 :上游处理请求的时间太长。 nginx等待上游返回超时,返回Gateway Timeout

。因此,无论是代码执行错误、服务挂起、服务太忙还是请求处理时间太长导致 HTTP 请求失败,都会返回 5XX。 ,499根本无法启动。

总的来说确实是这样,但是这次的新款平峰499可不是一般情况。网上查资料,经常有人提出nginx 499可能是服务器处理时间过长导致的,导致客户端超时后断开活动连接。是的,但是这种情况不应该是上面描述的场景4——上游处理请求的时间太长,所以nginx返回504,对吧?
所以看起来服务器端处理时间太长,这可能会导致客户端与活动连接断开499,或者导致nginx网关返回超时504。那么造成这种差异的关键因素是什么呢?

简单来说,如果客户端先断开连接,nginx检测到,就是499。如果upstream耗时太长,nginx先设置超时,就是504。所以nginx时间很关键。上游超时设置在这里。我快速浏览了一下 nginx 超时相关配置。嗯,相关的有效期并没有直接配置——!

nginx中的504超时配置

由于api和nginx通过uwsgi协议进行通信,主要的超时配置参数有:

Syntax:	uwsgi_connect_timeout time;
Default:	
uwsgi_connect_timeout 60s;
Context:	http, server, location
Defines a timeout for establishing a connection with a uwsgi server. It should be noted that this timeout cannot usually exceed 75 seconds.

Syntax:	uwsgi_send_timeout time;
Default:	
uwsgi_send_timeout 60s;
Context:	http, server, location
Sets a timeout for transmitting a request to the uwsgi server. The timeout is set only between two successive write operations, not for the transmission of the whole request. If the uwsgi server does not receive anything within this time, the connection is closed.

Syntax:	uwsgi_read_timeout time;
Default:	
uwsgi_read_timeout 60s;
Context:	http, server, location
Defines a timeout for reading a response from the uwsgi server. The timeout is set only between two successive read operations, not for the transmission of the whole response. If the uwsgi server does not transmit anything within this time, the connection is closed.

除非指定,超时默认为60。简单来说(实际情况比较复杂,这里不再赘述)nginx可以设置自己的网关超时时间,只有当上游处理请求时间超过60秒时才以504处理。然而,客户端设置的HTTP请求超时时间实际上只有15秒。 --这还包括外部网络的数据传输时间,所以这里出现了问题:每个服务器处理一个耗时超过15秒的请求,nginx不会评估504,因为它还没有达到60秒的超时阈值,并且客户端因为超过了本地15秒的超时时间,直接断开连接,nginx记录为499。
查看Nginx日志,确实是有一个上游请求处理缓慢,耗时过长非高峰期499报警期间,可能会导致:

  1. 用户在需要阻塞等待请求的页面上等待,即使请求没有到达。 15秒后我不耐烦了,直接通过切换页面或杀死应用程序并重新启动来结束当前请求。
  2. 用户耐心等待15秒或非阻塞后台HTTP请求超过超时阈值超过15秒并断开连接以终止活动请求。

499服务器耗时过长导致

我们已经知道最近单条上行随机499是由于响应慢造成的。由于客户端超时(15 秒)远小于 nginx 上游超时(60 秒),这应该是一个明显的错误配置,导致三个明显的问题: 由于活跃的用户操作,客户端达到

  1. 499。由于各种原因(例如杀死应用程序)而快速断开连接 由超时(此处为 15 秒)引起的 499 会混淆,并且无法区分客户端责任和服务器责任会导致 499 问题。
  2. 如果nginx评估请求为499,则不认为是客户端主动断开连接,也不认为是服务器导致的失败尝试,并且将其包含在用于确定失败决策的max_fails列表中。 ,所以即使大量的upstream触发达到499时,nginx也不会从可用upstream中移除,相当于移除不可用节点的功能。由于过载,上游到 499 的请求数量仍在增加,这最终可能会导致更大的问题。
  3. 值为499的请求也是因为不认为失败,所以uwsgi_next_upstream配置不起作用。因此,如果第一个上游处理请求花费的时间太长并且超时,nginx 将不会尝试。将其请求转发到下一个上游并在返回之前尝试处理它,这只能直接失败。

这是否意味着增加客户端超时?或者减少nginx上游超时来解决问题?

延长客户端的超时时间当然是不合理的。任何在15秒内没有收到响应的用户请求一定是有问题的,所以正确的做法应该是减少上游超时。一般情况下,服务器对客户端的响应是否定的。请求处理结束时间应该在几十到几百毫秒之间。如果超过1秒,则认为是极长请求。因此,不仅默认值60s不起作用,而且客户指定的15s也不能用于设置上游超时。

最后,在充分考虑各个服务器端API超时情况后,完成了上游5个超时时间配置。由于之前没有任何经验,所以第一次修改并不算太大。我观察了一段时间后继续适应。它已经完成。大体上解决以上三个问题就足够了:

  1. 区分了499和504,前者是由于用户出于各种原因(例如杀死应用程序)主动快速终止而导致的,后者是在以下情况下自动终止的: nginx 达到上游超时。通过运行 nginx 删除失败节点逻辑,
  2. 504 包含在 max_fails 计算中。如果一台机器出现故障且响应缓慢,可以检测到并暂时将其从可用节点列表中移除,以防止其负载进一步增加,并确保后续请求正常可用。节点进程返回。
  3. 当nginx等待upstream处理触发5秒超时时,它会根据uwsgi_next_upstream配置尝试将请求(默认只是幂等GET请求)转发到下一个upstream配置,处理后返回它。这样,一个上游由于异常负载较高而被处理,如果超时较高,则其他正常上游可以作为备份来处理这些超时请求。这里,客户端的原始请求,一直等待15秒超时,一般可以在5-10秒内返回。

通过proxy_ignore_client_abort配置解决499?

在网上查资料时,有网友提出解决nginx 499问题的一个思路是设置参数proxy_ignore_client_abort。默认情况下该参数处于禁用状态。开启此功能后,nginx将忽略客户端主动请求断开连接。根据上游返回的实际状态,nginx官方文档是这么说的:

Syntax:	proxy_ignore_client_abort on | off;
Default:	
proxy_ignore_client_abort off;
Context:	http, server, location
Determines whether the connection with a proxied server should be closed when a client closes the connection without waiting for a response.

但是,当客户端主动断开连接时,设置该参数的重要性不仅仅在于完全确定nginx日志中记录的状态码。上游回来,但除了 499 意味着客户端已断开连接之外,它对实际问题没有任何帮助。感觉就像鸵鸟一样把头埋在沙子里。不知道这个参数设置有什么实际场景。

非高峰期偶尔有一个上游人反应慢而死掉的原因

这个问题问得好。这个问题是最近才出现的。解决了上面提到的nginx不匹配问题后,我尝试解决这个问题。从现象来看,似乎是某些特定的请求触发了上游CPU的尖峰,响应缓慢进一步影响后续请求的处理,最终导致所有请求响应缓慢,启动客户端499。

解决nginx不匹配问题后,当再次发生上游缓慢超时时,nginx 通过上游故障转移快速消除问题,以防止进一步恶化,并在第一次访问问题时发出 GET 请求上游超时。备份被转发到其他可用的上游处理然后返回,这大大减少了此类异常的影响。

最后,修复配置后,某些POST API的上游偶尔出现异常,每隔几天就会触发少量504阈值警报。问题的根本原因仍在调查中。

总结

想当然,人生有太多的错误。例如,nginx 499 通常是客户端的责任。对于网上长期存在的少数异常病症,还是要保持敬畏之心,小心热水器煮青蛙。 、探索和思考超出时间允许的范围。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门