Nginx upstream 到 node 服务 502 问题
in Note with 0 comment
Nginx upstream 到 node 服务 502 问题
in Note with 0 comment

背景

近期做了 nginx 的监控,发现有些 5xx 的错误,主要是502和503,出现 503 是因为请求并发超出限制,这个需要提醒客户自行降低并发,之后也有客户反馈偶尔会有 502 情况,虽然很少,他的接口每天 10 来个,整个系统每天 100 来个,虽然中途加了一些实例看起来缓解了问题,但这个客户时不时问一下,还是挺让人纠结的,所以利用节前的空余时间找资料研究一下。

详细情况

nginx 在报 502 时,error日志是下面这两个情况:

1、Connection reset by peer

2019/01/31 11:50:40 [error] 19324#0: *65019204 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 1.119.136.18, server: xxx.com, request: "POST /path/abc HTTP/1.0", upstream: "htt
p://xxx:12345/path/abc", host: "xxx.com"

2、upstream prematurely closed connection

2019/01/31 13:39:04 [error] 19323#0: *65264092 upstream prematurely closed connection while reading response header from upstream, client: 42.62.19.174, server: xxx.com, request: "POST /path/abc HTTP/1.1", upstream: "http://xxx:12345/path/abc", host: "xxx.com"

然后 node 服务未见异常,这个也可能是出错后没打印出来。。。

问题分析

1、从问题的描述上看,都是因为上游服务(node服务)断开了连接,网上虽然说第一种情况可能是客户端主动关闭了连接,但从nginx请求日志上看,各个客户接口都有,而且在服务重启时,就会触发,所以可以确定是我们本身node服务的问题;
2、网上的资料显示,upstream keepAlive的配置需要小一些,原因是过大的值会导致过多空闲的长连接,如果上游长连接断开并且处理半关闭状态,即nginx还未感知到那个长连接已经不可用了,如果这时再往这个长连接发数据,就会触发到上述问题2;
3、至于为什么node服务会主动断开长连接,原因是node 8.0.0开始,新增了keepAliveTimeout参数,并且默认值为5000ms,这个参数的作用是设置长连接允许空闲的时间,如果空闲时间超过配置,就主动断开长连接,然后连接就会等待发起端确认并关闭;
4、长连接确实需要空闲时回收,防止占用太多资源,但最好是发送端主动发起关闭,这样不会导致连接被过早关闭的问题,这里的场景下,nginx作为长连接的发起者,可以选择主动断开,但目前服务使用的nginx版本,官方不提供upstream keepAliveTimeout,得引入一些第三方模块,除非升级到1.15.3,两者都需要重启nginx,而且出问题对稳定性影响比较大,所以还是选择先调整node服务;

问题修复

先说下折腾的过程,防止之后还踩坑。先是怀疑 upstream keep_alive 数值配置过大,当时是 64,改为 5 还是继续不定时出现 502;
另外还调整了 http, server, location 三者中的 keep_alive_timeout,nginx 文档上说该项配置的默认值是75s,我想既然 node 8.0 之后默认是 5s,那 nginx 调整为4s就可以避免过早断开的问题了,然而配置 4s 后还是没能解决问题,最后才发现这里的 keep_alive_timeout 是指 nginx 跟客户端的,不是跟 upstream 的,upstream 的配置需要在 1.15.3 之后才有官方支持,旧版本需要用第三方模块。文档如下:

1548937842148-c8c99a84-1a2c-4fd8-afd6-d8da5f8422a8-image-resized.png

1548938067089-79f48432-25b8-4bc0-bd7d-6c6c63fe2ac2-image-resized.png

经过一番搜索和查询,问题终于浮出水面,即 node keepAliveTimeout 配置不当,根据 node.js 官方文档,可能通过指定 keepAliveTimout 为 0 来关闭这个功能。

1548935381955-50c26de9-c157-4888-b6cc-469b0057b0e1-image-resized.png

node 服务的调整如下:

const server = app.listen(options, function () {
    // A value of 0 will disable the keep-alive timeout behavior on incoming connections
    // default 5000 (5 seconds) 从node v8.0.0开始默认是5秒,开启时如果数值小于nginx的keepAliveTimeout的话,在低并发时有一定概率会使主动断开与nginx代理的长连接,导致请求502
    // 而且目前nginx所用的1.12.1版本不支持upstream的keepAliveTimeout配置,所以暂时关闭node服务的keepAliveTimeout
    server.keepAliveTimeout = 0

    console.info('listen', options.port, 'process.version', process.version)
})

更新上线后,nginx 不再有 502 的情况了

参考

有关服务端主动关闭socket带来的几个问题分析--tcp四次握手半关闭问题导致
node http 文档
nginx长连接文档中文解析
ngx_http_upstream_module
ngx_http_core_module

Responses