从一起丢包故障来谈谈 nginx 中的 tcp keep-alive

一、故障

基本架构如图所示,客户端发起 http 请求给 nginx,nginx 转发请求给网关,网关再转发请求到后端微服务。

故障现象是,每隔十几分钟或者几个小时不等,客户端就会得到一个或者连续多个请求超时错误。查看 nginx 日志,对应请求返回 499;查看网关日志,没有收到对应的请求。

从日志分析,问题应该处在 nginx 或者 spring-cloud-gateway 上。

nginx 版本:1.14.2,spring-cloud 版本:Greenwich.RC2。

nginx 主要配置如下:

[root@wh-hlwzxtest1 conf]# cat nginx.conf

worker_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 连接

[[email protected] logs]# ss -n | grep 10.201.0.27:8888
tcp    ESTAB      0      0      10.197.0.38:36674              10.201.0.27:8888
tcp    ESTAB      0      0      10.197.0.38:40106              10.201.0.27:8888

[[email protected] opt]# ss -n | grep 10.197.0.38
tcp    ESTAB      0      0        ::ffff:10.201.0.27:8888                 ::ffff:10.197.0.38:40106
tcp    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_time
7200
[root@eureka2 opt]# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
[root@eureka2 opt]# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9

具体这几个参数的作用,参考文章:为什么基于TCP的应用需要心跳包

3、分析

通过以上抓包分析,基本确认了问题出在 nginx 上。19:25 时,网关发送 tcp keep-alive 心跳包给 nginx 那台服务器,此时那台服务器上保留着该 tcp 连接,却没有回应;22:20 时,nginx 发送 http 请求给网关,而网关已经关闭该 tcp 连接,因此没有应答。

三、解决

1、proxy_send_timeout

nginx 中与 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:    upstream
This 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 报文,关闭连接。

你可能感兴趣的:(nginx,tcp-ip)