基本架構如圖所示,客戶端發起 http 請求給 nginx,nginx 轉發請求給網關,網關再轉發請求到後端微服務。html
故障現象是,每隔十幾分鍾或者幾個小時不等,客戶端就會獲得一個或者連續多個請求超時錯誤。查看 nginx 日誌,對應請求返回 499;查看網關日誌,沒有收到對應的請求。node
從日誌分析,問題應該處在 nginx 或者 spring-cloud-gateway 上。nginx
nginx 版本:1.14.2,spring-cloud 版本:Greenwich.RC2。git
nginx 主要配置以下:github
[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 鏈接。spring
[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 鏈接卻沒有通知對端,或者通知了對端但對端沒有收到。後端
先看下 nginx 的抓包數據:bash
序號 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的應用須要心跳包
經過以上抓包分析,基本確認了問題出在 nginx 上。19:25 時,網關發送 tcp keep-alive 心跳包給 nginx 那臺服務器,此時那臺服務器上保留着該 tcp 鏈接,卻沒有迴應;22:20 時,nginx 發送 http 請求給網關,而網關已經關閉該 tcp 鏈接,所以沒有應答。
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,就關閉鏈接。因此這幾個參數,顯然不是咱們須要的。
查看官網文檔,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 報文,關閉鏈接。