一、故障基本架构如图所示,客户端发起 http 请求给 nginx,nginx 转发请求给网关,网关再转发请求到后端微服务。故障现象是,每隔十几分钟或者几个小时不等,客户端就会得到一个或者连续多个请求超时错误。查看 nginx 日志,对应请求返回 499;查看网关日志,没有收到对应的请求。从日志分析,问题应该处在 nginx 或者 spring-cloud-gateway 上。nginx 版本:1.14.2,spring-cloud 版本:Greenwich.RC2。nginx 主要配置如下:[root@wh-hlwzxtest1 conf]# cat nginx.confworker_processes 8;events { use epoll; worker_connections 10240;}http { include mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; #gzip on; upstream dbg2 { server 10.201.0.27:8888; keepalive 100; } server { listen 80; server_name localhost; charset utf-8; location /dbg2/ { proxy_pass http://dbg2/; proxy_http_version 1.1; proxy_set_header Connection “”; } }}为了提高性能,nginx 发送给网关的请求为 http 1.1,可以复用 tcp 连接。二、排查1、查看 tcp 连接[root@10.197.0.38 logs]# ss -n | grep 10.201.0.27:8888tcp ESTAB 0 0 10.197.0.38:36674 10.201.0.27:8888tcp ESTAB 0 0 10.197.0.38:40106 10.201.0.27:8888[root@10.201.0.27 opt]# ss -n | grep 10.197.0.38tcp ESTAB 0 0 ::ffff:10.201.0.27:8888 ::ffff:10.197.0.38:40106tcp ESTAB 0 0 ::ffff:10.201.0.27:8888 ::ffff:10.197.0.38:39266可以看到 nginx 和网关之间建立的 socket 连接为 (10.201.0.27:8888,10.197.0.38:40106),另外的 2 条记录就很可疑了。猜测原因是:一端异常关闭了 tcp 连接却没有通知对端,或者通知了对端但对端没有收到。2、抓包分析先看下 nginx 的抓包数据:序号 8403:转发 http 请求给网关;序号 8404:在 RTT 时间内没有收到 ack 包,重发报文;序号 8505:RTT 约等于 0.2s,tcp 重传;序号 8506:0.4s 没收到 ack 包,tcp 重传;序号 8507:0.8s 没收到 ack 包,tcp 重传;序号 8509:1.6s 没收到 ack 包,tcp 重传;…序号8439:28.1s(128RTT)没收到 ack 包,tcp 重传。序号 8408:请求设置了超时时间为 3s,因此发送 FIN 包。再看下网关的抓包数据:序号 1372:17:24:31 收到了 nginx 发过来的 ack 确认包,对应 nginx 抓包图中的序号 1348(nginx 那台服务器时间快了差不多 1 分 30 秒);序号 4221:2 小时后,发送 tcp keep-alive 心跳报文,(从 nginx 抓包图中也可以看出这 2 小时之内该 tcp 连接空闲);序号 4253:75s 后再次发送 tcp keep-alive 心跳;序号 4275:75s 后再次发送心跳;连续 9 次;序号 4489:发送 RST 包,通过对端重置连接。2 小时,75s, 9 次,系统默认设置。[root@eureka2 opt]# cat /proc/sys/net/ipv4/tcp_keepalive_time7200[root@eureka2 opt]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl75[root@eureka2 opt]# cat /proc/sys/net/ipv4/tcp_keepalive_probes9具体这几个参数的作用,参考文章:为什么基于TCP的应用需要心跳包3、分析通过以上抓包分析,基本确认了问题出在 nginx 上。19:25 时,网关发送 tcp keep-alive 心跳包给 nginx 那台服务器,此时那台服务器上保留着该 tcp 连接,却没有回应;22:20 时,nginx 发送 http 请求给网关,而网关已经关闭该 tcp 连接,因此没有应答。三、解决1、proxy_send_timeoutnginx 中与 upstream 相关的超时配置主要有如下参数,参考:Nginx的超时timeout配置详解proxy_connect_timeout:nginx 与 upstream server 的连接超时时间;proxy_read_timeout:nginx 接收 upstream server 数据超时, 默认 60s, 如果连续的 60s 内没有收到 1 个字节, 连接关闭;proxy_send_timeout:nginx 发送数据至 upstream server 超时, 默认 60s, 如果连续的 60s 内没有发送 1 个字节, 连接关闭。这几个参数,都是针对 http 协议层面的。比如 proxy_send_timeout = 60s,并不是指如果 60s 没有发送 http 请求,就关闭连接;而是指发送 http 请求后,在两次 write 操作期间,如果超过 60s,就关闭连接。所以这几个参数,显然不是我们需要的。2、upstream 模块的 keepalive_timeout 参数查看官网文档,Module ngx_http_upstream_module,Syntax: keepalive_timeout timeout;Default: keepalive_timeout 60s;Context: upstreamThis directive appeared in version 1.15.3.Sets a timeout during which an idle keepalive connection to an upstream server will stay open.设置 tcp 连接空闲时间超过 60s 后关闭,这正是我们需要的。为了使用该参数,升级 nginx 版本到 1.15.8,配置文件如下:http { upstream dbg2 { server 10.201.0.27:8888; keepalive 100; keepalive_requests 30000; keepalive_timeout 300s; } …}设置 tcp 连接上跑了 30000 个 http 请求或者空闲 300s,那么就关闭连接。之后继续测试,没有发现丢包。序号 938:空闲 5 分钟后,nginx 主动发起 FIN 报文,关闭连接。