原文鏈接: 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