一次nginx 502问题排查和解决

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一次nginx 502问题排查和解决相关的知识,希望对你有一定的参考价值。

参考技术A 问题描述

现象:生产环境 50 并发压测出现 0.01% 的错误率,全部为502/Bad Gateway

原因分析

1.通过观察nginx error log发现产生502的原因为"upstream prematurely closed connection while reading response header from upstream",也就是请求期间上游服务器关闭连接导致。

2.分析发现线上nginx upstream配置了keepalive参数,但没有配置proxy_http_version和proxy_set_header Connection,nginx与上游服务器间的连接为短链接。上游服务器处理完请求后会主动关闭连接,但因为nginx upstream设置了keepalive,nginx在收到reponse后仍然会尝试复用此连接,而此时连接已被上游服务器关闭,导致请求失败,出现上述报错。

解决方案

问题解决方式有2种,均已验证生效:

1.仍然使用短链接,去掉upstream中的keepalive参数,保证nginx不再尝试复用,否则在请求密集的时候有概率出现502;

2.使用长连接,加上proxy_http_version 1.1和proxy_set_header Connection ""  这两个配置项,保证服务端在请求处理完成后不主动关闭连接。

为避免出现大量TIME_WAIT,提高资源复用率,目前使用第二种长连接的方式。

08 nginx 的一次请求处理调试

前言

呵呵 这里来看一下 nginx 是如何处理一次请求的 

这里 会有两个 case, 一个是 代理请求 upstream 的, 一个是请求 nginx 所在服务器的静态资源 

监听端口, 处理请求的是 worker process 

以下截图, 调试基于 nginx-1.18.0

带 upstream 的请求处理

配置如下, upstream "localhost:8080" 对应的是一个 SpringBoot 项目 

        location ^~ /api/ 
               root html; 
               index  index.html index.htm;
               proxy_pass http://localhost:8080/;
	       proxy_redirect 80 83;
        

localhost:8080 对应的服务 

@RestController
@RequestMapping("/HelloWorld")
public class HelloWorldController 

    @Resource
    private UserService userService;

    @GetMapping("/listFormWithoutHeader")
    public List<JSONObject> listFormWithoutHeader(
            String param01, String param02
    ) 
        List<JSONObject> result = new ArrayList<>();
        result.add(wrapEntity("param01", param01));
        result.add(wrapEntity("param02", param02));
        userService.list();
        return result;
    

    // wrapEntity
    public JSONObject wrapEntity(String name, String age) 
        JSONObject result = new JSONObject();
        result.put("name", name);
        result.put("age", age);
        return result;
    


直接请求 localhost:8080 的服务, 服务通畅 

通过 nginx 代理访问

我们这里关注的流程如下 

1. 客户端请求 nginx, nginx 拿到请求之后添加了一个 ngx_http_wait_request_handler 的事件处理 

2. ngx_http_wait_request_handler 中查询 location, 创建代理请求, 发送代理请求到 upstream 

3. upstream 拿到请求处理之后, 响应结果给 nginx, nginx 拿到结果之后 处理之后响应给客户端 

1. 客户端请求 nginx, nginx 拿到请求之后添加了一个 ngx_http_wait_request_handler 的事件处理 

看一下 accept 中, 对方客户端的 ip 是 127.0.0.1, 对方客户端端口是 "-54 51" = 0xca33 = 51763 

查看一下 建立的连接, 这里 51763 这个 tcp 连接对应的就是 浏览器访问和 nginx 创建的连接 

进程为 474 对应的 chrome 进程创建的连接 

^Cmaster:target jerry$ lsof -i:80
COMMAND    PID  USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
SogouInpu  388 jerry   22u  IPv4 0x9710be4129a54d89      0t0  TCP 192.168.0.104:51827->49.7.115.35:http (ESTABLISHED)
Google     474 jerry   29u  IPv4 0x9710be4129a55711      0t0  TCP localhost:51763->localhost:http (ESTABLISHED)
WeChat     665 jerry  200u  IPv4 0x9710be41340f6a79      0t0  TCP 192.168.0.104:51213->113.96.209.106:http (ESTABLISHED)
nginx     2816 jerry    3u  IPv4 0x9710be412c1b6d89      0t0  TCP localhost:http->localhost:51763 (ESTABLISHED)
nginx     2816 jerry    8u  IPv4 0x9710be413458ba79      0t0  TCP *:http (LISTEN)
nginx     2821 jerry    8u  IPv4 0x9710be413458ba79      0t0  TCP *:http (LISTEN)
?\\x98?   6652 jerry   25u  IPv4 0x9710be4128f8b711      0t0  TCP 192.168.0.104:51521->118.112.225.41:http (CLOSE_WAIT)

然后 accept 之后, 向事件队列中抛了一个事件, 来处理当前连接的读取事件 

handler 是 ngx_http_wait_request_handler

2. ngx_http_wait_request_handler 中查询 location, 创建代理请求, 发送代理请求到 upstream 

这里是从客户端的连接中读取数据, 请求信息如下, 看这个 b->start  

在解析了客户端的请求行, 请求头之后, 会处理客户端请求 

我们的这里的 upstream 的场景, 会被 ngx_http_core_content_phase 处理

ngx_http_core_content_phase 中有一个 ngx_http_proxy_handler, 来和 upstream 创建连接, 创建代理请求, 发送代理请求 

如下便是和 upstream 创建链接之前, 创建代理请求, 在之前的一部分文章中也提到过 

如下这里是 和 upstream 创建 tcp 连接, 这里的 pc 的主机是 127.0.0.1, 端口是 "31 -112" = 0x1f90 = 8080 

如下地方是 发送代理请求到 upstream, 可以看见该处理的地方已经处理过了 

3. upstream 拿到请求处理之后, 响应结果给 nginx, nginx 拿到结果之后 处理之后响应给客户端 

upstream 响应结果之后 使用 ngx_http_upstream_handler 处理响应, nginx 拿到响应的地方如下 

这里可以看到 upstream 响应的 http 响应

可以看到的是 nginx 还对响应做一定程度的处理, 比如响应头中 location 的替换, 增加 Server 响应头, 增加其他自定义响应头 等等

c, connection 响应头的处理

看一下对方客户端的信息, 对方客户端的 ip 是 127.0.0.1, 对方客户端端口是 "-54 51" = 0xca33 = 51763 

比如这里增加了一个 Server 的响应头的处理 

请求 nginx 所在服务器的静态资源

 看一下 accept 中, 对方客户端的 ip 是 127.0.0.1, 对方客户端端口是 "-50 101" = 0xce65 = 52837 

然后 accept 之后, 向事件队列中抛了一个事件, 来处理当前连接的读取事件 

handler 是 ngx_http_wait_request_handler

查看一下 建立的连接, 这里 52837 这个 tcp 连接对应的就是 浏览器访问和 nginx 创建的连接 

进程为 474 对应的 chrome 进程创建的连接 

master:target jerry$ lsof -i:80
COMMAND    PID  USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Google     474 jerry   48u  IPv4 0x9710be412ee46d89      0t0  TCP localhost:52837->localhost:http (ESTABLISHED)
Google     474 jerry   51u  IPv4 0x9710be4129ae5a79      0t0  TCP localhost:52839->localhost:http (ESTABLISHED)
WeChat     665 jerry  200u  IPv4 0x9710be41340f6a79      0t0  TCP 192.168.0.104:51213->113.96.209.106:http (ESTABLISHED)
nginx     2816 jerry    3u  IPv4 0x9710be412f84d401      0t0  TCP localhost:http->localhost:52837 (ESTABLISHED)
nginx     2816 jerry    8u  IPv4 0x9710be413458ba79      0t0  TCP *:http (LISTEN)
nginx     2821 jerry    8u  IPv4 0x9710be413458ba79      0t0  TCP *:http (LISTEN)
?\\x98?   6652 jerry   25u  IPv4 0x9710be4128f8b711      0t0  TCP 192.168.0.104:51521->118.112.225.41:http (CLOSE_WAIT)

这里是从客户端的连接中读取数据, 请求信息如下, 看这个 b->start  

这里可以看到的是 这里的 http_content_phase 处理阶段, 已经到了 ngx_http_static_handler, 来处理静态资源  

这里将 请求 映射到了 "/usr/local/nginx/html/index.html", 然后这个 handler 会响应这个文件的相关信息给客户端 

nginx 将文件内容响应给客户端 

完  

以上是关于一次nginx 502问题排查和解决的主要内容,如果未能解决你的问题,请参考以下文章

NGINX 502错误排查(转)

nginx排查502错误

Nginx出现502和504错误解决方法

错误排查

Nginx+PHP (Fastcgi)常见502和504解决思路分享

nginx 502