一、故障
基本架構如圖所示,客戶端發起 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 連線
[root@10.197.0.38 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
[root@10.201.0.27 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 報文,關閉連線。