egg多進程模型注意事項梳理

背景

最近在項目中使用egg進行服務端開發,在開發過程當中遇到了比較詭異的問題,具體表現爲mq在監聽到信息時,其回調函數會被屢次執行,那麼這會致使某個文件被同時操做等問題。vue

問題成因

這邊梳理egg文檔時,重點過了一下egg多進程的設計模式,瞭解到egg的master-agent-worker模式,那麼這裏面有些問題是須要咱們在開發時去注意的了。node

首先介紹下egg的多進程實現方式

egg經過node提供的cluster實現了多進程模式,爲了更好地利用多核環境,egg通常會啓用至關於cpu核數的worker,以此來最大化利用cpu能力。設計模式

在egg啓動時,master,agent,worker的關係如圖所示app

+---------+           +---------+          +---------+
|  Master |           |  Agent  |          |  Worker |
+---------+           +----+----+          +----+----+
     |      fork agent     |                    |
     +-------------------->|                    |
     |      agent ready    |                    |
     |<--------------------+                    |
     |                     |     fork worker    |
     +----------------------------------------->|
     |     worker ready    |                    |
     |<-----------------------------------------+
     |      Egg ready      |                    |
     +-------------------->|                    |
     |      Egg ready      |                    |
     +----------------------------------------->|

在這種模式下,master、agent、worker各司其職,主要製做分配以下:
master:負責維護整個應用穩定性,當有worker因異常而退出時,master負責拉起新的worker,以確保應用正常運行。
agent:因爲egg的多進程模型會在每一個進程中運行一份咱們的應用實例,那麼在某些狀況下,這種機制會致使問題。好比,保存日誌的邏輯若是在每一個進程中都執行的話,那麼在觸發日誌保存操做的時候,會有多個進程同時操做日誌文件,那麼此時就會致使文件讀寫問題。因此egg設計了agent進程,agent進程只會有一個,不會出現上述問題,這樣,對於相似上述的後臺運行的邏輯就統一放到agent中去處理了。
worker:負責執行業務代碼,處理用戶請求和定時任務,egg在框架層保證了定時任務只會在單個worker中執行,因此能夠放心使用。框架

分析egg多進程致使的問題

上面咱們分析過了egg的多進程機制,因此咱們知道了問題成因,出現咱們最開始說的問題的緣由是咱們把mq的監聽和處理邏輯放到了worker中,那麼這樣的話在實際運行過程當中,就會致使mq收到消息時,回調函數被執行屢次。dom

到這裏咱們已經知道如何優化了,那就是把mq的處理邏輯放到agent中,以確保mq消息的回調僅執行一次。可是細心地你確定發現了,這裏有個問題,agent只有一個實例,若是事情在agent裏面作,那麼不是沒法利用多核性能了嗎?

agent與worker通訊

的確,咱們能夠在agent中處理僅須要單次執行的邏輯,可是這樣作就無法利用多核性能了。那麼有什麼辦法嗎?沒錯,就是進程間通訊,具體思路就是,agent仍是負責mq的鏈接和監聽邏輯,可是回調函數不在agent中執行,而是寫在worker裏面。那麼worker何時執行這個邏輯呢?答案是,agent經過進程間通訊通知worker。egg內部實現了一個進程間通訊機制,咱們直接調用便可,主要實現方式以下:async

廣播消息: agent => all workers
                  +--------+          +-------+
                  | Master |<---------| Agent |
                  +--------+          +-------+
                 /    |     \
                /     |      \
               /      |       \
              /       |        \
             v        v         v
  +----------+   +----------+   +----------+
  | Worker 1 |   | Worker 2 |   | Worker 3 |
  +----------+   +----------+   +----------+

指定接收方: one worker => another worker
                  +--------+          +-------+
                  | Master |----------| Agent |
                  +--------+          +-------+
                 ^    |
     send to    /     |
    worker 2   /      |
              /       |
             /        v
  +----------+   +----------+   +----------+
  | Worker 1 |   | Worker 2 |   | Worker 3 |
  +----------+   +----------+   +----------+

這裏咱們能夠看出來,進程間通訊都是基於master轉發的,因此咱們能夠利用egg提供的機制,解決咱們的問題。函數

解決辦法

如上文分析,咱們把mq的鏈接和監聽邏輯放到agent中,當接收到消息時,經過進程間通訊把通知發送給worker,而後由worker執行具體的業務邏輯便可。具體代碼其實能夠參考vue的事件機制,在worker中監聽指定事件:性能

app.messenger.on(action, data => {
  // 執行業務邏輯
});

在agent中創建mq鏈接並監聽消息,收到消息後觸發事件:優化

exports.task = async ctx => {
  ...// 收到mq消息的邏輯此處省略
  // 準備發送通知
  ctx.app.messenger.sendRandom(action);
};

注意,須要單次執行的任務要調用sendRandom方法,這個是發送給一個worker的方法。固然,若是要執行屢次的,能夠調用app.messenger.sendToApp()方法,這個方法會把消息發送給全部worker,並執行屢次處理邏輯。

總結

egg在多進程模型中的使用仍是須要有一些技巧的,因此須要咱們先熟悉egg的多進程機制後再進行業務開發,避免遇到奇怪的坑,浪費時間。

相關文章
相關標籤/搜索