Jinyun's Notes

没什么天赋,爱好也不多,但愿坚持做些喜欢的事情

0%

Nginx 499 状态码

201911151505.jpg

🧨 第一次听说 499 状态码是去新浪阅读的一次面试,后来在参加 PHP 开发者年会时,王晶老师现场做了一次提问分享,再后来我就记不清了,只有它认识我,我不认识它。有些问题因为自己偷懒而一知半解,直到再次被虐。

什么是 Nginx 499 错误

Nginx 源码中对 499 状态码的定义:

1
2
3
4
5
6
7
/*
* 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

翻译成人话就是:

当一个客户端关闭时,HTTP 不为这种情形定义代码。同时我们处理它的请求时,我们引入了当一个客户端在我们尝试向其发送 HTTP 头之前关闭连接时,使用自己的代码(也就是 499 状态码)来记录这种情况。

进一步理解就是:我们特么的也不知道这种情况该怎样弄,于是乎我们就决定定义一种状态码来记录一下,至于这种情况怎么处理,你看着办吧!

总结一下:

  • 499 状态码不是 HTTP 的标准代码
  • 499 状态码是 Nginx 自己定义,用来 记录(你没看错,就是记录一下) 服务端向客户端发送 HTTP 请求头之前,客户端已经关闭连接的一种情况
  • 最常见的场景就是 timeout 设置不合理,Nginx 把请求转发上游服务器,上游服务器慢吞吞的处理,客户端等不及了主动断开链接,Nginx 就负责记录了 499

什么情况 Nginx 记录 499 错误日志

这里我们使用 curl 模拟请求一下,更多 curl 的骚操作请访问 curl 的用法指南。

1
2
3
4
5
6
7
8
9
10
11
for i in $(seq 1 10); do curl -m 2 http://api.example.test; done
curl: (28) Operation timed out after 2000 milliseconds with 0 bytes received
curl: (28) Operation timed out after 2004 milliseconds with 0 bytes received
curl: (28) Operation timed out after 2004 milliseconds with 0 bytes received
curl: (28) Operation timed out after 2001 milliseconds with 0 bytes received
curl: (28) Operation timed out after 2000 milliseconds with 0 bytes received
curl: (28) Operation timed out after 2003 milliseconds with 0 bytes received
curl: (28) Operation timed out after 2002 milliseconds with 0 bytes received
curl: (28) Operation timed out after 2005 milliseconds with 0 bytes received
curl: (28) Operation timed out after 2000 milliseconds with 0 bytes received
curl: (28) Operation timed out after 2001 milliseconds with 0 bytes received
1
2
3
4
5
6
7
8
9
10
11
12
tail -f /var/log/nginx/apiexample.access.log

172.19.0.1 - - [15/Nov/2019:06:32:19 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"
172.19.0.1 - - [15/Nov/2019:06:32:22 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"
172.19.0.1 - - [15/Nov/2019:06:32:24 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"
172.19.0.1 - - [15/Nov/2019:06:32:26 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"
172.19.0.1 - - [15/Nov/2019:06:32:28 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"
172.19.0.1 - - [15/Nov/2019:06:32:30 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"
172.19.0.1 - - [15/Nov/2019:06:32:32 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"
172.19.0.1 - - [15/Nov/2019:06:32:34 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"
172.19.0.1 - - [15/Nov/2019:06:32:36 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"
172.19.0.1 - - [15/Nov/2019:06:32:38 +0000] "GET / HTTP/1.1" 499 0 "-" "curl/7.67.0"

如上所见,使用 Timeout 很容易模拟出 499 这种情形。

记录 499 的情形:

  • 如上所示,数据传输的最大允许时间超时的话,Curl 断开了请求,而 Web 服务器如 Nginx 还在处理的话,则 Nginx 会记录 499
  • 如果 Nginx 作为反向代理时,Nginx 将请求分发至对应的处理服务器时,有两对超时参数的设置:proxy_send_timeout 和 proxy_read_timeout、fastcgi_send_timeout 和 fastcgi_read_timeout。两对参数默认的超时时间都是 60s。在 Nginx 出现 499 的情况下,可以结合请求断开前的耗时和这两对设定的时间进行对比,看一下是不是在 proxy_pass 或者 fastcgi_pass 处理时,设置的超时时间短了
  • 如果 PHP 操作超时。打开 php.ini 查看 max_execution_time 和 max_input_time 两个参数。两者分别是 PHP 程序执行的最长时间和表单提交的最长时间
  • 如果两次提交 POST 过快就会出现 499 的情况,Nginx 认为是不安全的连接,主动拒绝了客户端的连接
  • 相关负载均衡配置等

如何有效防止 Nginx 记录 499 错误

综上所述,我们可以得出一个结论,HTTP 请求在指定的时间内没能拿到响应而关闭了连接,就会发生 Nginx 记录 499 错误的情况。这个涉及到两个重要的问题:时间问题 和 性能问题(性能问题太过宽泛就不提及了),所以解决这个问题也就从这两方面入手。

当然还有配置 proxy_ignore_client_abort 参数为 on 来解决的(让代理服务端不要主动关闭客户端的连接)。但是这样也有一定的风险,会拖垮服务器。发生这个错误,如果服务器 CPU 和 Memory 不算太高,一般是数据库和程序的问题,数据库处理较慢或者程序线程较低。结合情况调整,比如读写分离或者程序线程数调高。

文档中对 proxy_ignore_client_abort 参数的说明:

Determines whether the connection with a proxied server should be closed when a client closes the connection without waiting for a response.

翻译:当一个客户端关闭连接而不等待响应时,确定与代理服务器的连接是否应该关闭。

本笔记是笔者在学习和工作中的一些整理,如对您有用,请鼓励我继续写作