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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM