Nginx错误码502和504的区别
29/May 2017
0x00 前言
某日某B:我们系统出现了504,你赶紧去问问PHP的人有什么错误日志?
某A:我已经问过,PHP的人说没有日志。
某B:PHP那面的人说的话你不要相信,赶紧再去查一遍。
某A:WTF。。。
因此某A很疑惑,为什么NGINX有502和504, 有那些原因导致504, PHP那面有没有日志呢?
0x01 定义
通过阅读nginx的源码,备注:我这里查看的是openresty中nginx-1.11.2的源代码,我们发现502和504的定义。
ngx_http_request.h的130行有如下的代码
#define NGX_HTTP_INTERNAL_SERVER_ERROR 500
#define NGX_HTTP_NOT_IMPLEMENTED 501
#define NGX_HTTP_BAD_GATEWAY 502
#define NGX_HTTP_SERVICE_UNAVAILABLE 503
#define NGX_HTTP_GATEWAY_TIME_OUT 504
#define NGX_HTTP_INSUFFICIENT_STORAGE 507
从字面上面翻译看:
- 502 错误的网关
- 504 网关超时
网络上有很多关于这两种错误的解决办法,的确他们说的方法能够解决这两种错误,后面我也会总结一下解决办法。但是没有从源头上说明白为什么NGINX会抛出这样的错误码,还有就是在什么情况下会抛出这样的错误码。
0x02 代码跟踪
为了弄明白什么情况下产生产生这两种错误码,我们继续查看相关的源代码:
在ngx_http_upstream.c的3935行中定义了一个ngx_http_upstream_next
static void
ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u,
ngx_uint_t ft_type)
{
//-------------省略代码
switch (ft_type) {
case NGX_HTTP_UPSTREAM_FT_TIMEOUT:
status = NGX_HTTP_GATEWAY_TIME_OUT;
break;
case NGX_HTTP_UPSTREAM_FT_HTTP_500:
status = NGX_HTTP_INTERNAL_SERVER_ERROR;
break;
case NGX_HTTP_UPSTREAM_FT_HTTP_403:
status = NGX_HTTP_FORBIDDEN;
break;
case NGX_HTTP_UPSTREAM_FT_HTTP_404:
status = NGX_HTTP_NOT_FOUND;
break;
default:
status = NGX_HTTP_BAD_GATEWAY;
}
//-------------省略代码
}
这段代码的作用是:根据ft_type进行status设置,然后根据相关属性判断是传给下一个upstream还是结束连接ngx_http_upstream_finalize_request。
我们可以看到了ft_type除了指定几种类型外,比如504对应的NGX_HTTP_UPSTREAM_FT_TIMEOUT,都是502对应的NGX_HTTP_BAD_GATEWAY。
从上面的代码看出来了,我们只需要跟踪什么地方调用ngx_http_upstream_next的同时,判断第三个参数ft_type的赋值情况即可。
但是从整个代码文件中,我们可以看到基本上所有的函数都调用到它,比如ngx_http_upstream_connect等。
具体的各个地方的调用代码如下:
ngx_http_upstream_connect
作用:连接到upstream并发送请求,这里如果设置了SSL,那么还会调用SSL连接
rc = ngx_event_connect_peer(&u->peer);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream connect: %i", rc);
if (rc == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); //500
return;
}
u->state->peer = u->peer.name;
if (rc == NGX_BUSY) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no live upstreams");
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_NOLIVE); //502
return;
}
if (rc == NGX_DECLINED) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); //502
return;
}
ngx_http_upstream_ssl_init_connection
作用:初始化一个到upstream的ssl连接,其中包括了SSL握手。 调用的代码比较少:
if (ngx_http_upstream_test_connect(c) != NGX_OK) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); //502
return;
}
ngx_http_upstream_ssl_handshake
作用:在SSL握手的过程中,校验证书等操作
failed:
c = r->connection;
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); //502
ngx_http_run_posted_requests(c);
ngx_http_upstream_send_request
作用:向upstream发送请求包
if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); //502
return;
}
ngx_http_upstream_send_request_handler
作用:和upstream连接的write_event的处理函数
if (c->write->timedout) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); //504
return;
}
ngx_http_upstream_process_header
作用:和upstream连接的read_event的处理函数
if (c->read->timedout) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT); //504
return;
}
if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); //502
return;
}
无法从connection中recv到数据,返回502错误
n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last);
........
if (n == 0) {
ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream prematurely closed connection");
}
if (n == NGX_ERROR || n == 0) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); //502
return;
}
对接收到的内容进行HTTP头解析,无效的头结构返回502,其他错误返回500
rc = u->process_header(r);
if (rc == NGX_AGAIN) {
if (u->buffer.last == u->buffer.end) {
ngx_log_error(NGX_LOG_ERR, c->log, 0, "upstream sent too big header");
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); //502
return;
}
continue;
}
break;
}
if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); //502
return;
}
if (rc == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); //500
return;
}
ngx_http_upstream_process_body_in_memory
作用:处理upstream的响应包body的内容
读取内容超时就返回504错误码,这里并没有调用next_upstream
c = u->peer.connection;
rev = c->read;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream process body on memory");
if (rev->timedout) {
ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); //504
return;
}
ngx_http_upstream_process_upgraded
作用:upstream的upgrade,这里的具体调用还需要了解一下。
if (upstream->read->timedout || upstream->write->timedout) {
ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); //504
return;
}
ngx_http_upstream_process_non_buffered_upstream
作用:接收non buffered upstream的数据
if (c->read->timedout) {
ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_GATEWAY_TIME_OUT); //504
return;
}
ngx_http_upstream_process_non_buffered_request
作用:处理上面non buffered upstream的请求
if (upstream->read->eof) {
ngx_log_error(NGX_LOG_ERR, upstream->log, 0, "upstream prematurely closed connection");
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); //502
return;
}
if (upstream->read->error) {
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); //502
return;
}
ngx_http_upstream_process_request
作用:处理upstream的请求。
if (p->upstream_done || p->upstream_eof || p->upstream_error) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream exit: %p", p->out);
if (p->upstream_done || (p->upstream_eof && p->length == -1))
{
ngx_http_upstream_finalize_request(r, u, 0);
return;
}
if (p->upstream_eof) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream prematurely closed connection");
}
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY); //502
return;
}
0x03 总结
504
504出现的次数比较少,我就先说它吧。
- 向upstream中write请求数据超时
- 从upstream中read响应数据超时
NGINX错误日志中会出现以下字样:
- upstream timed out
在socket异步编程里面,好像socket的connected和closed事件触发都是在read事件中,当然我已经好久没有写这块代码了,所以这些都是我以前写windows编程中event_select的一些记忆,可能有一些偏差。
我只是觉得connect_timeout应该也是触发的504,当然这里有待考证,我持保留意见。
情况1中write_timeout,那么数据还没有发送到upstream的server上,因此后端的业务是没有执行的。
情况2中read_timeout,表示数据发送给upstream了,但是upstream在规定的时间内没有返回任意一个字节。
因此这里后端业务可能还没有执行,请求还在服务器TCP队列里面;有可能已经在执行中,只是还没有执行完。如果后端业务平均处理速度都比较快,那么更大的可能性就是请求还在队列中。
这里可能的原因有系统的TCP队列设置过大,而后端WebServer的backlog不够。
常见的解决办法:
- 如果是proxy_pass,那么加大配置文件中proxy_timeout,proxy_connect_timeout,proxy_buffer_size
- 如果是fastcgi_pass,那么加大配置文件中fastcgi_buffer_size,fastcgi_connect_timeout,fastcgi_read_timeout,fastcgi_write_timeout
这样调大相关参数会有一定的效果,但是如果大请求量一直持续,填满了相关TCP队列,那么整个upstream可能就雪崩了,因此最佳的办法还是优化业务逻辑。
502
502出现的原因非常多,我这里大致划分一下:
- upstream连接不上,比如后端服务没有开启
- SSL初始化或者握手失败,比如证书不对
- 发送请求时,和upstream的连接已经断掉
- 从upstream中recv数据失败或者长度为0或者eof
- upstream中recv的数据太大或者不是有效的HTTP header
NGINX的错误日志中会出现以下字样:
- no live upstreams
- upstream prematurely closed connection
- upstream sent too big header
- Connection reset by peer
情况1,2,3都是在连接或者发送数据过程中出现了错误,upstream是没有接收到数据,那么后端业务是没有执行的,具体失败的原因需要在NGINX服务器上面查找。
与之相反,情况4和5都是在接收响应数据的时候,upstream主动关闭连接或者发送的数据错乱造成的,因此失败原因需要在upstream上面查找,这个阶段后端业务已经执行完毕。
常见的原因有后端WebServer设置了最大执行时间,但是业务还没有执行完毕。
常见的解决办法:
- 加大后端业务服务器单个请求可以执行时间,比如php.ini中的max_execution_timout和request_terminate_timeout
当然这个还是一个表面的解决办法,针对这种场景最优的解决办法就是后端扩容和前端限流。