最近在項目中使用egg進行服務端開發,在開發過程當中遇到了比較詭異的問題,具體表現爲mq在監聽到信息時,其回調函數會被屢次執行,那麼這會致使某個文件被同時操做等問題。vue
這邊梳理egg文檔時,重點過了一下egg多進程的設計模式,瞭解到egg的master-agent-worker模式,那麼這裏面有些問題是須要咱們在開發時去注意的了。node
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的多進程機制,因此咱們知道了問題成因,出現咱們最開始說的問題的緣由是咱們把mq的監聽和處理邏輯放到了worker中,那麼這樣的話在實際運行過程當中,就會致使mq收到消息時,回調函數被執行屢次。dom
到這裏咱們已經知道如何優化了,那就是把mq的處理邏輯放到agent中,以確保mq消息的回調僅執行一次。可是細心地你確定發現了,這裏有個問題,agent只有一個實例,若是事情在agent裏面作,那麼不是沒法利用多核性能了嗎?
的確,咱們能夠在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的多進程機制後再進行業務開發,避免遇到奇怪的坑,浪費時間。