Actors 基于消息驱动的异步编程模型
Actors 是一種異步編程模式,它的提出主要是為了解決 “多線程,MTA” 編程帶來的復雜性、困難度。
但這些都不是 Actors 本身提供的最大優點,它真正的價值是易于解決多線程處理,容易導致共享資源(share resources)之間產生競爭,故而導致 deadlock(死鎖)。
一個軟件工程項目里面并非所有的開發人員都經歷過嚴格的?“多核編程” 數年摧殘,縱然受過多年毒打的開發人員有時也容易寫出 deadlock(死鎖)的疑難問題,往往經驗及技術越強的人搞出死鎖就越難解決。
設計模式及編程模型,提出目的都是為了讓代碼寫起來更簡單、更易于閱讀、后期工程維護,但不意味著設計模式及編程模型的應用會讓代碼執行的效率更高,或許是更低也說不一定。
有時候不要過于注重形式,沒有意義,可以解決人們迫切問題的設計模式及編程模型才是有價值與意義的,個人多年經驗之談。
Actors 基于消息驅動的異步編程模型,本文在標題上就已經明確提出了 Actors 異步編程模型的核心要點:“基于消息驅動”
每個線程可以運行一個或多個 Actors 消息驅動器,有些類似 Windows 窗體編程中的消息隊列(WndProc),當某個消息到達,人們對感興趣的消息逐個進行處理。
Actors 異步編程模型里面要求,所有的線程或進程之間都通過消息的方式進行合作,所以采用 Actors 異步編程模型的結構大約如下圖所示:
?上圖為簡化的理解圖,Actors 是一個消息生產 + 消費的驅動模型,設A與B兩個端點,A為生產Actors Action,B為消費處理 Actors Action 端點
A與B之間傳遞消息(Actor Action)的傳輸層介質,可以是進程內存、共享內存、Socket套接字、Pipe 匿名/命名管道,Actor 本身不限制消息傳遞的介質層。
Actors 模型本身不解決傳輸介質層出現故障而導致的 Actor Actions 消息丟失的問題,例如:A節點生產一個消息經Socket套接字推送到B節點,但Socket套接字發生故障導致該生產的消息被丟失。
如何解決此問題?對于異步編程而言,一些行為并不需要ACK確認已經處理該消息,但如果確定需要對方處理該消息,則需要實現一種名為 “停等ARQ”【停等超時自動重傳】ACK確認的機制以確保消息被傳遞到目標節點上被處理,但這屬于傳輸介質層設計時需要考慮的東西,與 Actors 模型并沒有任何關系。
所以,人們從此處不需要參考 Actors 更多博客/書籍/文獻就可以自行推導出,Actors 從設計之初就是為了解決多線程編程的一些問題,而不是為了分布式而設計的,只是發展到分布式的時代仍然有大量的解決方案工程采用 Actors 異步編程編程模型而已。
那么什么是本文提出的 “Actors Action”?
比如A節點向B節點 Publish(生產)一個 Actor Action,那么它至少攜帶以下信息:
1、Action ID(動作ID)
2、進階:Sequence ID(序列ID)用于ACK確認,注意:它與 Actor 模型本身無關
3、Action 載荷的數據模型(貧血模型)
4、Origin(來源信息若不需要應答則不需要)
B節點從消息隊列中 Peek 彈出頂部入列消息(FIFO先入先出原則),調度派發器則基于 Action ID 來派發消息到對應的 Actor Action Handler(Actor 動作處理器)來消費處理該動作的行為。
那么 Action 載荷的數據模型,如何在調度派發器內解決呢?
很容易:
C# 工程語言可以編寫工程內消息模型的序列化算法,根據ID來映射對應的 “貧血數據模型”,管理的方法有兩種:
1、基于反射檢索元數據的方式來自動構建及建立映射,例如在數據模型頭上聲明特性,靜態標記:Action ID。
2、手動注冊 Action ID 跟數據模型的映射關系
C/C++ 工程語言可以編寫工程內消息模型的靜態序列化算法,如采用靜態編譯的序列化?google protobuf,解決方案仍是手動注冊 Action ID 跟數據模型之間的映射關系。
當我們為 Action 增加進階的序列ID,那么則可以實現 “停等ARQ” 的機制,那么也為RPC遠過程調用的實現提供可能性,如果我們在 C/C++ 語言中,最好實現的編程模型為APM(異步編程模型)
例如:
修改玩家狀態
參考一:ChangePlayerStatusAsync( args..., lambda...?)
參考二:BeginChangePlayerStatus( args...., [...](args...) {
? ?auto result = EndChangePlayerStatus(...);
? ?// TO:DO Your are code here.
})
但當我們在 Actors 異步編程模型的基礎上,增加了RPC遠過程調用,那么則可能帶來一個全新的潛在問題,“deadlock” 邏輯層的死鎖,如:A遠過程調用B,B遠過程調用A,調用上下層級關系不明確,或許大多數開發人員皆曾犯過該錯誤。
注意:此類錯誤跟 Actors 異步編程模式并沒有直接關系,將其歸納于 Actors 異步編程模式本身的缺點,理解上有問題的。
Actors 異步編程模型總結:
重點(一):
Actors 異步編程模式,本身是沒有多線程下的安全問題的,原因很明顯,它是只是把消息生產并推送到消息消費者的消息隊列,這個過程甚至不需要加鎖(臨界區)
這取決于底層傳輸媒介,若同個進程內存傳遞的需要上鎖,消息隊列本身并非必是線程安全,例如:C/C++ 工程語言常常適用STL標準庫中的 std::queue、std::list 泛型模板BCL基礎類庫。
重點(二):
Actors 異步編程模式,本身不提供停等ARQ,消息確認機制,因為實現類似的機制導致的邏輯死鎖是開發人員的問題,不是 Actors 模式本身的缺點,扣鍋還是的要點臉,沒有的東西不要給別人加頭上。
重點(三):
Actors 異步編程模式,是最初是用于解決并緩解 “單進程多線程” 內共享資源(share-resource)競爭導致的死鎖、內存安全、多核編程編碼復雜度的問題。
例如:
A線程訪問共享資源競爭鎖,B線程訪問共享資源競爭到鎖,但A線程訪問另外一個共享資源競爭到鎖,B線程嘗試訪問另外一個共享資源競爭鎖,那么就會發生相互競爭死鎖現象。
當人采用:
Actors 模式時,我們將該共享資源按需要自行劃分掛在到一個具體的 Actors 調度器負責驅動處理,當其它線程/進程需要訪問該共享資源時,交付請求到目的?Actors 調度器上負責的?Actors Action Handler 進行處理,那么操作該共享的資源都在單個工作線程上,所以不需要額外的增加互斥鎖。
Actors 模型系統執行非常高效,但其效能表現仍無法比擬多線程內存之間相互訪問的效率,但并非絕對的,某些情況下 Actors 模型系統的效能表現優于多線程模型的系統,當然不說大家也明白不是,這么對比沒有意義。
重點(四):
Actors 異步編程模式,基于消息驅動,勢必會帶來大量碎片化的消息報文數據,這必定造成嚴重內存碎片問題,致內存分配的效率下跌,C/C++ 開發人員應注意對內存碎片問題進行優化處理,由更先進的 “現代工業高級編程語言” 特性來解決,個人建議人們適用:“Microsoft C# by dotNET.”
C/C++ 可以采用以下幾類解決方案:
1、定長/對齊數據報文,盡量避免隨機顆?;峙?/p>
2、分配固定大小區塊,劃分若干個小區塊,每個小區塊緩存一個消息
3、自行實現碎片整理(移動內存)
4、適用開源內存池,掩耳盜鈴把問題丟向內存池(例:jemalloc)
重點(五):
Actors 異步編程模式驅動的執行單元是一個線程(線程是程序執行最小單元),如果單個線程負載過多性能會成為瓶頸的,但這并不算 Actors 模式本身的缺點,如果需要承擔的負載過重,開發人員應當思考如何優化并解決
例如:更加細化的拆分非本職業務到其它的 Actors 的驅動器/線程進行負責,而不是說這就是 Actors 模式本身的缺點,這不是一個好的解決問題的態度。
如果學過 “領域驅動模型” 的童靴們,應該很明白大多數某個具體 Actors 產生性能瓶頸,均是上層模型領域職責過于寬泛,負責的事務遠遠超出它本身應當承擔的事務導致的,但凡是不是絕對的,對多數軟件工程項目而言,它卻是問題的本質原因。
是的為每個模塊/接口的粒度劃分存在一些問題,就像人們在進行多線程(多核)編程時,需要注重鎖(lock)的粒度問題,如果人們粒度太大那么多線程系統的效率可能還不如單線程的效能,如果粒度太小,編程復雜性就會直線提高,因為通常鎖粒度應用越小越容易遇到死鎖的問題,但相對的代碼效能通常就越高(指綜合并發效能指數,對硬件負載及利用率越高)。
總結
以上是生活随笔為你收集整理的Actors 基于消息驱动的异步编程模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ShowWindow 显示窗口
- 下一篇: springboot项目部署后项目启动慢