一次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问题排查和解决的主要内容,如果未能解决你的问题,请参考以下文章