node.js之多進程後續

內容概括

  • 進程重啓

  • 處理HTTP服務

  • cluster模塊

  • 負載均衡

1.進程重啓

worker進程可能因爲某些異常情況而退出,爲了提高集羣的穩定性,master進程需要監聽子進程的存活狀態。

當子進程退出之後,master進程要及時重啓新的子進程。在Node中,子進程退出時,會在父進程中觸發exit事件。

父進程只需通過監聽該事件便可知道子進程是否退出,並在退出的時候做出相應的處理。

以下是master進程代碼,文件名爲master.js

const childProcess = require('child_process')
const net = require('net')
const cpuNum = require('os').cpus().length - 1

// 創建工作進程
let workers = []
let cur = 0
for (let i = 0; i < cpuNum; ++i) {
  workers.push(childProcess.fork('./worker.js'))
  console.log('Create worker-' + workers[i].pid)
}

// 創建TCP服務器
const server = net.createServer()

// 由於master進程也會監聽端口。因此需要對請求做出處理
server.on('connection', (socket) => {
  // 利用setTimeout模擬處理請求時的操作耗時
  setTimeout(() => {
    socket.end('Request handled by master')
  }, 10)
})

server.listen(8080, () => {
  console.log('TCP server: 127.0.0.1:8080')
  // 監聽端口後將服務器句柄發送給工作進程
  for (let i = 0; i < cpuNum; ++i) {
    workers[i].send('server', server)
    // 工作進程退出後重啓
    workers[i].on('exit', ((i) => {
      return () => {
        console.log('Worker-' + workers[i].pid + ' exited')
        workers[i] = childProcess.fork('./worker.js')
        console.log('Create worker-' + workers[i].pid)
        workers[i].send('server', server)
      }
    })(i))
  }
  // 關閉主線程服務器的端口監聽
  // server.close()
})

以下是worker進程的代碼,文件名爲worker.js

process.on('message', (msg, server) => {
  if (msg === 'server' && server) {
    server.on('connection', (socket) => {
      // 利用setTimeout模擬處理請求時的操作耗時
      setTimeout(() => {
        socket.end('Request handled by worker-' + process.pid)
      }, 10)
    })
  }
})

執行node master.js啓動服務器後,可以通過任務管理器直接殺掉進程來模擬進程異常退出。

在這裏插入圖片描述
可以看到worker進程退出後,master能夠發現並及時創建新的worker進程。任務管理器中的Node進程數量恢復原樣。

在這裏插入圖片描述
執行node tcp_client.js啓動客戶端,客戶端發出的連接請求被處理的情況如下,同樣地,由於監聽同一端口,進程之間採取搶佔式服務,不一定保障負載均衡。

在這裏插入圖片描述

2.處理HTTP服務

前面的示例所使用的是TCP服務器,如果要處理HTTP請求,需要使用HTTP服務器。而HTTP其實是基於TCP的,發送HTTP請求的時候同樣也會發起TCP連接。

只需要對前面的TCP服務器進行一點小改動便可以支持HTTP了。在進程中新增HTTP服務器,當TCP服務器收到請求時,把請求提交給HTTP服務器處理即可。

以下是worker進程的代碼,文件名爲worker.js

const http = require('http')
const httpServer = http.createServer((req, res) => {
  // 利用setTimeout模擬處理請求時的操作耗時
  setTimeout(() => {
    res.writeHead(200, { 'Content-Type': 'text/plain' })
    res.end('Request handled by worker-' + process.pid)
  }, 10)
})

process.on('message', (msg, server) => {
  if (msg === 'server' && server) {
    server.on('connection', (socket) => {
      // 提交給HTTP服務器處理
      httpServer.emit('connection', socket)
    })
  }
})

3.cluster模塊

前面簡單描述了使用child_process實現單機Node集羣的做法,需要處理挺多的細節。

Node提供了cluster模塊,該模塊提供了更完善的API,除了能夠實現多進程充分利用CPU資源以外,還能夠幫助我們更好地進行進程管理和處理進程。

下面是簡單示例if條件語句判斷當前進程是master還是worker,master進程會執行if語句塊包含的代碼,而worker進程則執行else語句塊包含的代碼。

master進程中,利用cluster模塊創建了與CPU數量相應的worker進程,並通過監聽cluster的online事件來判斷worker的創建成功。

在worker進程退出後,會觸發master進程中cluster模塊上的exit事件,通過監聽該事件可以瞭解worker進程的退出情況並及時fork新的worker。

最後,worker進程中只需創建服務器監聽端口,對客戶端請求做出處理即可。(這裏設置相同端口8080之後,所有worker都將監聽同一個端口)

以下是master進程代碼,文件名爲master.js

const cluster = require('cluster')

if (cluster.isMaster) {

  const cpuNum = require('os').cpus().length

  for (let i = 0; i < cpuNum; ++i) {
  
    cluster.fork()
  }

  // 創建進程完成後輸出提示信息
  cluster.on('online', (worker) => {
  
    console.log('Create worker-' + worker.process.pid)
  })

  // 子進程退出後重啓
  cluster.on('exit', (worker, code, signal) => {
  
    console.log('[Master] worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal)
    
    cluster.fork()
  })
} else {

  const net = require('net')
  
  net.createServer().on('connection', (socket) => {
  
    // 利用setTimeout模擬處理請求時的操作耗時
    setTimeout(() => {
    
      socket.end('Request handled by worker-' + process.pid)
    }, 10)
    
  }).listen(8080)
}

執行node server.js啓動服務器,繼續按照之前的做法,利用任務管理器殺死進程,可以看到在進程被殺後master能夠及時啓動新的worker。

繼續運行tcp_client,可以看到服務器能夠正常處理請求。

在這裏插入圖片描述

4.負載均衡

說到多進程,目的肯定是儘可能利用多核CPU,提高單機的負載能力。

但往往在實際項目中,受到業務邏輯的處理時間長短和系統CPU調度影響,導致實際上所有進程的負載並不是理想的徹底均衡。

官方也說了:

In practice however, distribution tends to be very unbalanced due to operating system scheduler vagaries. Loads have been observed where over 70% of all connections ended up in just two processes, out of a total of eight.

翻譯一下:70%的請求最終都落到2個worker身上,而這2個worker佔用更多的CPU資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章