0x00 前言

华为云WAF使用最好的服务器和带宽为客户提供反向代理和安全检测服务,但是在使用过程中,个别用户的请求出现了502或者504。因此我们团队也经常受到用户的反馈,这里可以大致和大家澄清一下相关知识点。

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

从字面上面翻译看:
1. 502 错误的网关
2. 504 网关超时

网络上有很多关于这两种错误的解决办法,的确他们说的方法能够解决这两种错误,后面我也会总结一下解决办法。但是没有从源头上说明白为什么NGINX会抛出这样的错误码,还有就是在什么情况下会抛出这样的错误码。

0x02 代码跟踪

为了弄明白什么情况下产生产生这两种错误码,我们继续查看相关的源代码:
在ngx_http_upstream.c的3935行中定义了一个ngx_http_upstream_next

static voidngx_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出现的次数比较少,我就先说它吧。
1. 向upstream中write请求数据超时
2. 从upstream中read响应数据超时

NGINX错误日志中会出现以下字样:
1. 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不够。

常见的解决办法:

1. 如果是proxy_pass,那么加大配置文件中proxy_timeout,proxy_connect_timeout,proxy_buffer_size
2. 如果是fastcgi_pass,那么加大配置文件中fastcgi_buffer_size,fastcgi_connect_timeout,fastcgi_read_timeout,fastcgi_write_timeout

这样调大相关参数会有一定的效果,但是如果大请求量一直持续,填满了相关TCP队列,那么整个upstream可能就雪崩了,因此最佳的办法还是优化业务逻辑。

502

502出现的原因非常多,我这里大致划分一下:

1. upstream连接不上,比如后端服务没有开启
2. SSL初始化或者握手失败,比如证书不对
3. 发送请求时,和upstream的连接已经断掉
4. 从upstream中recv数据失败或者长度为0或者eof
5. upstream中recv的数据太大或者不是有效的HTTP header

NGINX的错误日志中会出现以下字样:

1. no live upstreams
2. upstream prematurely closed connection
3. upstream sent too big header
4. Connection reset by peer

情况1,2,3都是在连接或者发送数据过程中出现了错误,upstream是没有接收到数据,那么后端业务是没有执行的,具体失败的原因需要在NGINX服务器上面查找。

与之相反,情况4和5都是在接收响应数据的时候,upstream主动关闭连接或者发送的数据错乱造成的,因此失败原因需要在upstream上面查找,这个阶段后端业务已经执行完毕。 常见的原因有后端WebServer设置了最大执行时间,但是业务还没有执行完毕。

常见的解决办法:

1. 加大后端业务服务器单个请求可以执行时间,比如php.ini中的max_execution_timout和request_terminate_timeout

当然这个还是一个表面的解决办法,针对这种场景最优的解决办法就是后端扩容和前端限流。