曾经某段时间这个 499
状态码是面试的必考题,好像你不知道这个状态码就觉得你欠了面试官不少钱似的,我也来记录一下。
什么是 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.
翻译:当一个客户端关闭连接而不等待响应时,确定与代理服务器的连接是否应该关闭。