背景
先了解下 499 ,本身并不是标准 http 协议规定产生,而是 nginx 代码中针对网络情况做的一个特殊定义。先看下 nginx 代码中的定义(源码文件 ngx_request_t.h)
/*
* HTTP does notdefine the code for the case when a client closed
* the connectionwhile we are processing its request so we introduce
* own code to logsuch situation when a client has closed the connection
* before we even tryto send the HTTP header to it
*/
#define NGX_HTTP_CLIENT_CLOSED_REQUEST 499
ngx_string(ngx_http_error_495_page), /* 495, https certificate error */
ngx_string(ngx_http_error_496_page), /* 496, https no certificate */
ngx_string(ngx_http_error_497_page), /* 497, http to https */
ngx_string(ngx_http_error_404_page), /* 498, canceled */
ngx_null_string, /* 499, client has closed connection */
- 这是nginx定义的一个状态码,用于表示这样的错误:服务器返回http头之前,客户端就提前关闭了http连接。
- 但还有一种可能就行 proxy 到后端的应用处理很慢或者没有响应。,“客户端等不及” 所以主动关闭了链接
主动断开
先说第一种场景,客户端提前断开。和以下几个原因有关
1、客户端应用层的机制主动断开,无法处理当前的请求。需要结合客户端的应用层日志进行分析,最好在客户的代码中记录 socket 的过程,结合应用的埋点日志。
2、网络层处理超时 TCP 协议栈主动发起了断开,需要客户端能否复现并抓到现场,可以使用 tcpdump 或者 Wireshark 固定本地的端口和其他唯一条件去抓包(常用 tcpdump -i device -s0 host $domain/$ip -w except.pcap)。或者可以用 tcpping 、mtr 初步分析网络是否有明显异常。
转发&处理超时
继续上传的 nginx 代码查找,“NGX_HTTP_CLIENT_CLOSED_REQUEST”,发现目前这个状态值只在 ngx_upstream 中赋值, upstream在以下几种情况下会返回 499
upstream 在收到读写事件处理之前时,会检查连接是否可用:
ngx_http_upstream_check_broken_connection,
if (c->error) {
...
if (!u->cacheable) {
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST);
}
}
1、server处理请求未结束,而client提前关闭了连接,此时也会返回499。这种情况也可能是 CDN 节点在回源到客户源站时间太长,客户端等不及断开。
客户端可以下载 CDN 日志过滤下异常的 URL 和时间点分析
if ( responsetime too long )
{
if(http_cache -> MISS)
//说明没有命中缓存,和回到客户源站导致的响应时间过长有关系。
if(http_cache -> HIT)
//说明命中节点缓存,需要升级售后继续进行分析。
}
2、在一个 upstream 出错,执行 next_upstream 时也会判断连接是否可用,不可用则返回499。但这种情况基本占比很少,问题的核心就是要排查为什么服务端处理时间过长。
建议
1、发现 CDN MISS 回源后响应时间很长而导致 499 ,原站可以看下错误日志,或者代码的埋点日志分析,为什么处理时间会很长。
2、检查原站是否存在慢 SQL 查询,或者一些读写数据库导致响应的时间过长。
3、如果原站是 nginx 或者基于 nginx 二次开发的 http server ,可以开启 proxy_ignore_client_abort on;( 让代理服务端不要主动关闭客户端的连接)。客户端主动断开连接后,nginx 会等待后端处理完然后返回 2xx ,如果后端处理超时,则返回 5xx 。
4、如果原站是 nginx 或者基于 nginx 二次开发的 http server,可以将 read_timeout write_timeout 调整大一些,要结合主机的负载能力调整,不能设置太长,容易造成 FD 耗尽,或者 socket 不够分配。