Node.js高級程序員晉升系列之-Node進程模型解析和服務器端多進程部署

Node.js的運行模式

讓咱們經過一小段代碼來看Node.js的運行時javascript

const express = require('express')
const app = express()

let times = 0

app.get('/inc',  (req, res) => {
  times += 1
  res.end('success')
})

app.get('/currentValue', (req, res) => {
    res.end(times)
})

app.listen(3000)

複製代碼

若是在java等多線程模型的語言中,這是一個經典的用來解釋鎖的必要性的例子java

咱們使用一些壓測工具或者本身寫代碼在極短的時間內請求/inc這個接口1000次redis

而後調用 /currentValue獲取times的值數據庫

咱們會發現,使用java語言寫出來的相同代碼times值並不等於1000(通常會小於1000),而使用咱們這段Node.js寫出來的值則恰好等於1000,這是爲何呢?express

Node和Java的運行時模型區別

  • Java服務器默認運行是單進程多線程模型
  • Node服務器默認運行是單進程單線程模型

體現到服務器收到網絡請求的時候,java每次都會開一個新的線程來處理這個請求,不一樣線程是同步運行的,因此java服務器在運行上面這個例子時若是不加鎖就會遇到經典的多線程同時修改某一個變量致使最終數據錯誤的問題。服務器

Node在設計之初拋棄了多線程,除了libuv中有一個系統內部的線程池以外,咱們 本身寫的代碼都在主線程中運行,因此也不會涉及到多線程競爭問題。微信

既然Node默認是單進程單線程,爲何還須要多進程部署?

緣由很簡單,若是不作多進程部署的話,那咱們買的4核CPU服務器豈不是要被浪費掉3個核心?或者用戶請求量上去以後,一樣也須要分佈式集羣部署網絡

多進程部署最簡單的方式

經過pm2的cluster方式運行(文檔微信開發

pm2 start index.js -i max --name my-app多線程

這樣就直接開了和系統CPU核心數量同樣的進程跑了起來,pm2會自動將請求均分到不一樣的進程裏

多進程部署以後,會出現哪些問題?

最明顯的一個問題是,如今進程空間再也不共享,每一個進程都有着本身的進程空間,回到咱們文章最初的那個例子,假設咱們如今在一臺2核CPU上多進程運行了這個demo,而後訪問 /inc 這個接口1000次,而後訪問 /currentValue 會出現什麼結果呢?

咱們會發現返回結果會不斷變化,存在2個不一樣的值,但他們的和加起來等於1000,很明顯,是由於存在兩個不一樣的times對象

那麼不一樣的進程之間經過什麼方式來共享數據呢?好比我在作微信開發的時候,會有一個微信的access_token,每次獲取了新的token以後,老的token就會被微信自動做廢,這個token是經過一個http接口獲取,示例代碼

class WeixinTokenService {
    private token = null
    
    async getToken() {
        if (!this.token) {
            this.token = await someHttpResquest()
        }
        return this.token
    }
}
複製代碼

這是一個很簡單的例子,在單進程運行的時候基本上是正確的(但其實也有一個小几率bug,不知道有沒有人能發現😊)但在多進程模型下,第一個進程獲取了以後,第二個進程的token依然是空,那麼他會從新發起一個請求致使第一個進程獲取到的token做廢。

那麼如何解決這個問題呢?答案也很簡單,有2種比較通用的方式

  • 每次獲取了token以後,將token存入Redis或者數據庫中,你們一塊兒共享
  • 將token獲取這塊單獨做爲一個microservice,以單進程的方式部署(由於這塊代碼不一樣於業務代碼,是沒有高併發壓力的)

經過Redis來解決這個問題的代碼以下:

class WeixinTokenService {
    private redisClient = initRedisClient()
    
    async getToken() {
        // line 1
        let token = await this.redisClient.get('wx-token')  
        if (!token) {
            // line 2
            token = await someHttpResquest()
            // line 3
            await this.redisClient.set('wx-token', token)
        }
        return token
    }
}
複製代碼

此時這個問題基本獲得瞭解決,但新的問題出現了:

咱們想象一個這樣的場景,有2個用戶請求同時到達,他們一塊兒執行了line 1,都沒有拿到token,而後一塊兒執行了line 2,再同時執行了line 3,這時候有一個進程拿到的token是廢棄的,而具體被存到redis中的token是哪個,也是咱們無語預料的。

思考:其實單進程模型下也會遇到一樣的問題(還記得我說有一個小几率bug嗎?)

但單進程下的解決方法相對簡單,能夠經過變量標記法(設置一個變量用來標註當前是否正在請求token,防止發出多個http請求)

而多進程下如何解決這個問題呢?咱們下一篇文章再聊。

歡迎加入Node開發高級進階羣,羣主有時間就會給你們解決一些Node實戰中會遇到的各類問題

相關文章
相關標籤/搜索