nginx 代理node高併發下報錯 recv() failed(104 Connection reset by peer) while reading response header from upstream
原文連結: https://www.cnblogs.com/yalong/p/16147590.html
專案背景:
我們前端專案是基於node 和 react開發的,node對介面進行轉發,以及啟動server,並通過pm2 守護 node server程序
到了線上,請求先通過nginx 反向代理到node, 然後node 再把介面轉發到 java後端的介面
最近線上專案的介面頻繁報錯 錯誤碼 502 500,檢視監控,如下圖所示:
確實不少500 和 502 的錯誤,而且還是集中在qps高的地方,也就是說在高併發情況下,才出現這種問題
問題排查
首先看下自己的程式碼有有沒有問題,於是檢視pm2 的日誌,發現沒問題,檢視線上機器的記憶體使用率, cpu使用率,也都不高
於是讓java後端同學檢視java端是否正常,發現也沒問題
最終尋求運維同學幫助,然後運維同學檢視nginx 日誌,確實有問題,內容如下:
recv() failed(104 Connection reset by peer) while reading response header from upstream
那麼出現問題的點就找到了
就是nginx 往node請求的時候出了問題
問題分析
網上查了下,這個報錯的原因簡單來說,nodejs服務已經斷開了連線,但是未通知到Nginx,Nginx還在該連線上收發資料,最終導致了該報錯
這裡要引出長連線的概念,就是 Connection: keep-alive
keep-alive
的更多資料大家可以自行百度,或者看這個:https://www.jianshu.com/p/142b35998947
早期的時候一個http請求就要建立一個TCP連線,這個請求用完,TCP連線就關閉,http1.1以後新增了 keep-alive, 可以在一個TCP連線中持續發多個http請求而不中斷連線,從而減少了TCP連線的建立次數
但是開啟了keep-alive
的連線不是說就一直不會斷開了,tpc長連線斷開的原因有好多種
導致tcp連線斷開的原因:
- 連線在規定時間內沒有使用,就會自動斷開
- 每個tcp連線有最大請求限制,比如最大是1000, 那麼該tcp連線在服務了1000個http請求後就斷開了
- 伺服器的tcp併發連線數超過了伺服器承載量,伺服器會將其中一些tcp連線關閉
解決方案
先看下nginx 的長連線配置
nginx關於長連結的配置欄位如下:
-
keepalive_timeout
: 設定客戶端的長連線超時時間,如果超過這個時間客戶端沒有發起請求,則Nginx伺服器會主動關閉長連線,Nginx預設的keepalive_timeout 75s;。有些瀏覽器最多隻保持 60 秒,所以我們一般設定為 60s。如果設定為 0,則關閉長連線。 -
keepalive_requests
:設定與客戶端的建立的一個長連線可以處理的最大請求次數,如果超過這個值,則Nginx會主動關閉該長連線,預設值為100
一般情況下,keepalive_requests 100基本上可以滿足需求,但是在 QPS 較高的情況下,不停的有長連線請求數達到最大請求次數而被關閉,這也就意味著Nginx需要不停的建立新的長連線來處理請求,這樣會可能出現大量的 TIME WAIT
3.keepalive
: 設定到 upstream 伺服器的空閒 keepalive 連線的最大數量,當空閒的 keepalive 的連線數量超過這個值時,最近使用最少的連線將被關閉,如果這個值設定得太小的話,某個時間段請求數較多,而且請求處理時間不穩定的情況,可能就會出現不停的關閉和建立長連線數。我們一般設定 1024 即可。特殊場景下,可以通過介面的平均響應時間和QPS估算一下。
上面幾個引數確定沒問題後,然後在node端檢視關於keepAlive的配置, 看node文件, 可以看到node中的 server.keepAliveTimeout
預設是5000毫秒,就是5秒, 這樣如果連線超多5秒沒有使用,那麼node端就會把連線關閉,但是nginx端的連結還沒關閉,還繼續在該長連線上收發資料,就出現上面的報錯了
所以我們需要增大node端 keepAliveTimeOut
的值
node端keepAlive 配置
我們的專案使用的thinkjs框架,不過無論是什麼node框架,其核心都是基於http模組建立一個server,然後在此基礎上進行了封裝,修改keepAlive核心程式碼如下所示:
const http = require('http');
const server = http.createServer(this.callback());
server.keepAliveTimeout = 61 * 1000;
server.headersTimeout = 62 * 1000;
放在thinkjs專案裡,就是在src/bootstrap 目錄下,common.js 檔案內,新增如下程式碼:
think.app.on('appReady', () => {
if (think.app.server) {
think.app.server.keepAliveTimeout = 61 * 1000
think.app.server.headersTimeout = 62 * 1000
}
console.log(think.app.server) // 這行程式碼只是為了看下server長啥樣,實際用的時候註釋掉
})
也就是說在thinkjs裡找到server例項,然後進行設定
啟動thinkjs 可以看到上面的console資訊,下圖所示:
程式碼文字如下:
Server {
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
_events: [Object: null prototype] {
request: [Function: handleRequest],
connection: [Function: connectionListener]
},
_eventsCount: 2,
_maxListeners: undefined,
_connections: 0,
_handle: {
close: [Function: close],
listen: [Function: listen],
ref: [Function: noop],
unref: [Function: noop],
getsockname: [Function: getsockname],
onconnection: [Function: onconnection],
[Symbol(owner)]: [Circular *1]
},
_usingWorkers: false,
_workers: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 0,
keepAliveTimeout: 61000,
maxHeadersCount: null,
headersTimeout: 62000,
_connectionKey: '4:null:5000',
[Symbol(IncomingMessage)]: [Function: IncomingMessage],
[Symbol(ServerResponse)]: [Function: ServerResponse],
[Symbol(kCapture)]: false,
[Symbol(async_id_symbol)]: 18
}
上線之後問題解決了
總結分析
其實開始的時候,考慮是因為qps
高了,服務頂不住壓力,導致了報錯,當時的機器是1核CPU
, 4G
記憶體,線上就一個node例項
所以就給機器進行升級,1CPU
升級為3CPU
, 1個 node
例項,改為3個例項,然後重新部署上線,發現問題並沒有解決,然後找才找運維檢視nginx
日誌,得出結論是keepAlive
的問題
最終修改node
的keepAlive
,確實問題就解決了
中間找java後端同學,運維同學,整個鏈路都找了一遍,不過問題總算解決,還是很開心的
參考:
https://www.cnblogs.com/satty/p/8491839.html
https://bbs.huaweicloud.com/forum/thread-75184-1-1.html
https://www.its404.com/article/u014607184/107175596
https://www.jianshu.com/p/142b35998947