1. 程式人生 > 其它 >nginx 代理node高併發下報錯 recv() failed(104 Connection reset by peer) while reading response header from upstream

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連線斷開的原因:

  1. 連線在規定時間內沒有使用,就會自動斷開
  2. 每個tcp連線有最大請求限制,比如最大是1000, 那麼該tcp連線在服務了1000個http請求後就斷開了
  3. 伺服器的tcp併發連線數超過了伺服器承載量,伺服器會將其中一些tcp連線關閉

解決方案

先看下nginx 的長連線配置

nginx關於長連結的配置欄位如下:

  1. keepalive_timeout: 設定客戶端的長連線超時時間,如果超過這個時間客戶端沒有發起請求,則Nginx伺服器會主動關閉長連線,Nginx預設的keepalive_timeout 75s;。有些瀏覽器最多隻保持 60 秒,所以我們一般設定為 60s。如果設定為 0,則關閉長連線。
  2. 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的問題
最終修改nodekeepAlive,確實問題就解決了
中間找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