【Akka】Actor模型探索


Akka是什么

Akka就是為了改變編寫高容錯性和強可擴展性的並發程序而生的。通過使用Actor模型我們提升了抽象級別,為構建正確的可擴展並發應用提供了一個更好的平台。在容錯性方面我們採取了“let it crash”(讓它崩潰)模型,人們已經將這樣的模型用在了電信行業,構建出“自愈合”的應用和永不停機的系統,取得了巨大成功。Actor還為透明的分布式系統以及真正的可擴展高容錯應用的基礎進行了抽象。

Akka是JVM(JAVA虛擬機,下同)平台上構建高並發、分布式和容錯應用的工具包和執行時。

Akka用 Scala語言寫成,同一時候提供了Scala和JAVA的開發接口。

Akka處理並發的方法基於Actor模型。在基於Actor的系統里。全部的事物都是Actor,就好像在面向對象設計里面全部的事物都是對象一樣。可是有一個重要區別——那就是Actor模型是作為一個並發模型設計和架構的。而面向對象模式則不是。更詳細一點,在Scala的Actor系統里,Actor互相交互並共享信息但並不正確交互順序作出預設。Actor之間共享信息和發起任務的機制是消息傳遞。

Akka在多個Actor和以下的系統之間建立了一個層次(Layer),這樣一來,Actor僅僅須要處理消息就能夠了。創建和調度線程、接收和分發消息以及處理競態條件和同步的全部復雜性,都托付給框架,框架的處理對應用來說是透明的。

Akka混合模型特點

  • Actors
    Actors為你提供:
    對並發/並行程序的簡單的、高級別的抽象。
    異步、非堵塞、高性能的事件驅動編程模型(代碼能夠異步處理請求並採用獨占的方式執行非堵塞操作)。
    很輕量的事件驅動處理(1G內存可容納約270萬個Actors)。
  • 容錯性
    使用“let-it-crash”語義和監管者樹形結構來實現容錯。

    很適合編寫永不停機、自愈合的高容錯系統。監管者樹形結構能夠跨多個JVM來提供真正的高容錯系統。

  • 位置透明性
    Akka的全部元素都為分布式環境而設計:全部Actor都僅通過發送消息進行互操作,全部操作都是異步的。
  • 可伸縮性
    在Akka里。不改動代碼就添加節點是可能的,感謝消息傳遞和位置透明性(location transparency)。
  • 高彈性
    不論什么應用都會碰到錯誤並在某個時間點失敗。Akka的“監管”(容錯)策略為實現自愈系統提供了便利。

  • 響應式應用
    今天的高性能和高速響應應用須要對用戶高速反饋,因此對於事件的響應須要很及時。Akka的非堵塞、基於消息的策略能夠幫助達成這個目標。

  • 事務性Actors
    事務性Actor是Actor與STM(Software Transactional Memory)的組合。

    它使你能夠使用自己主動重試和回滾來組合出原子消息流。

Actor系統

Actor本質上就是接收消息並採取行動處理消息的對象。

它從消息源中解耦出來,僅僅負責正確識別接收到的消息類型,並採取對應的行動。


Actor是封裝狀態和行為的對象,他們的唯一通訊方式是交換消息,交換的消息存放在接收方的郵箱里。從某種意義上來說。Actor是面向對象的最嚴格的形式。可是最后把它們看成一些人:在使用Actor來對解決方式建模時,把Actor想象成一群人,把子任務分配給他們,將他們的功能整理成一個有組織的結構,考慮怎樣將失敗逐級上傳。這樣的結果就能夠在腦中形成進行軟件實現的框架。

樹形結構

程序中負責某一個功能的Actor可能須要把它的任務分拆成更小的、更易管理的部分。

為此它啟動子Actor並監管它們。

每一個Actor有且僅有一個監管者。就是創建它的那個Actor。
Actor系統的精髓在於任務被分拆開來並進行托付,直到任務小到能夠被完整地進行處理。這樣做不僅使任務本身被清晰地划分出結構。而且終於的Actor也能依照它們“應該處理的消息類型”,“怎樣完畢正常流程的處理”以及“失敗流程應怎樣處理”來進行解析。假設一個Actor對某種狀況無法進行處理,它會發送對應的失敗消息給它的監管者請求幫助。這樣的遞歸結構使得失敗能夠在正確的層次進行處理。

能夠將這與分層的設計方法進行比較。分層的設計方法終於很easy形成防護性編程,以防止不論什么失敗被泄露出來。

把問題交由正確的人處理會是比將全部的事情“藏在深處”更好的解決方式。

如今,設計這樣的系統的難度在於怎樣決定誰應該監管什么。

這當然沒有一個唯一的最佳方案,可是有一些可能會有幫助的原則:

  • 假設一個Actor管理還有一個Actor所做的工作。如分配一個子任務,那么父Actor應該監督子Actor。原因是父Actor知道可能會出現哪些失敗情況,知道怎樣處理它們。
  • 假設一個Actor攜帶着關鍵數據(i.e. 它的狀態要盡可能地不被丟失),這個Actor應該將不論什么可能的危急子任務分配給它所監管的子Actor,並酌情處理子任務的失敗。

    視請求的性質,可能最好是為每一個請求創建一個子Actor。這樣能簡化收集回應時的狀態管理。這在Erlang中被稱為“Error Kernel Pattern”。

  • 假設Actor A須要依賴Actor B才干完畢它的任務,A應該觀測B的存活狀態並對收到B的終止提醒消息進行響應。這與監管機制不同,因為觀測方對監管機制沒有影響,須要指出的是。僅僅是功能上的依賴並不足以用來決定是否在樹形監管體系中加入子Actor.

配置容器

多個Actor協作的Actor系統是管理如日程計划服務、配置文件、日志等共享設施的自然單元。使用不同的配置的多個Actor系統能夠在同一個jvm中共存。Akka自身沒有全局共享的狀態。

將這與Actor系統之間的透明通訊(在同一節點上或者跨網絡連接的多個節點)結合,能夠看到Actor系統本身能夠被作為功能層次中的積木構件。

Actor實踐

  1. Actor們應該被視為很友好的同事:高效地完畢他們的工作而不會無必要地打攪其他人,也不會爭搶資源。

    轉換到編程里這意味着以事件驅動的方式來處理事件並生成響應(或很多其他的請求)。Actor不應該因為某一個外部實體而堵塞(i.e.占領一個線程又被動等待),這個外部實體可能是一個鎖、一個網絡socket等等。

    堵塞操作應該在某些特殊的線程里完畢,這個線程發送消息給可處理這些消息的Actor們。

  2. 不要在Actor之間傳遞可變對象。為了保證這一點。盡量使用不變量消息。

    假設Actor將他們的可變狀態暴露給外界,打破了封裝,你又回到了普通的Java並發領域並遭遇全部其缺點。

  3. Actor是行為和狀態的容器。接受這一點意味着不要在消息中傳遞行為(比如在消息中使用scala閉包)。有一個風險是意外地在Actor之間共享了可變狀態,而與Actor模型的這樣的沖突將破壞使Actor編程成為良好體驗的全部屬性。

Akka中的Actor模型

使用Actor就像租車——我們假設須要,能夠高速便捷地租到一輛。假設車輛發生問題,也不須要自己修理,直接打電話給租車公司更換另外一輛就可以。
Actor模型是一種適用性很好的通用並發編程模型。它能夠應用於共享內存架構和分布式內存架構,適合解決地理分布型的問題。

同一時候它還能提供很好的容錯性。

一個Actor是一個容器,它包括了 狀態,行為,一個郵箱,子Actor和一個監管策略。全部這些包括在一個Actor Reference里。

Actor引用

Actor是以Actor引用的形式展現給外界的。Actor引用能夠被自由的無限制地傳來傳去。內部對象和外部對象的這樣的划分使得全部想要的操作能夠透明:重新啟動Actor而不須要更新別處的引用,將實際Actor對象放置到遠程主機上,向另外一個應用程序發送消息。但最重要的方面是從外界不可能到Actor對象的內部獲取它的狀態。除非這個Actor很不明智地將信息發布出去。

狀態

Actor對象通常包括一些變量來反映Actor所處的可能狀態。這可能是一個明白的狀態機(e.g. 使用 FSM 模塊),或是一個計數器。一組監聽器。待處理的請求,等等。

這些數據使得Actor有價值,而且必須將這些數據保護起來不被其他的Actor所破壞。

好消息是在概念上每一個Akka Actor都有它自己的輕量線程。這個線程是全然與系統其他部分隔離的。

這意味着你不須要使用鎖來進行資源同步。能夠全然不必操心並發性地來編寫你的Actor代碼。

在幕后,Akka會在一組線程上執行一組Actor。一般是很多Actor共享一個線程,對某一個Actor的調用可能會在不同的線程上進行處理。

Akka保證這個實現細節不影響處理Actor狀態的單線程性。

因為內部狀態對於Actor的操作是至關重要的,所以狀態不一致是致命的。

當Actor失敗並由其監管者又一次啟動,狀態會進行又一次創建。就象第一次創建這個Actor一樣。這是為了實現系統的“自愈合”。

行為

每次當一個消息被處理時。消息會與Actor的當前的行為進行匹配。行為是一個函數,它定義了處理當前消息所要採取的動作,比如假設客戶已經授權過了。那么就對請求進行處理,否則拒絕請求。

這個行為可能隨着時間而改變,比如因為不同的客戶在不同的時間獲得授權。或是因為Actor進入了“非服務”模式,之后又變回來。這些變化要么通過將它們放進從行為邏輯中讀取的狀態變量中實現,要么函數本身在執行時被替換出來。見become 和 unbecome操作。

可是Actor對象在創建時所定義的初始行為是特殊的。因為當Actor重新啟動時會恢復這個初始行為。

郵箱

Actor的用途是處理消息,這些消息是從其他的Actor(或者從Actor系統外部)發送過來的。

連接發送者與接收者的紐帶是Actor的郵箱:每一個Actor有且僅有一個郵箱,全部的發來的消息都在郵箱里排隊。排隊依照發送操作的時間順序來進行,這意味着從不同的Actor發來的消息在執行時沒有一個固定的順序,這是因為Actor分布在不同的線程中。從還有一個角度講。從同一個Actor發送多個消息到同樣的Actor,則消息會按發送的順序排隊。

能夠有不同的郵箱實現供選擇,缺省的是FIFO:Actor處理消息的順序與消息入隊列的順序一致。這一般是一個好的選擇,可是應用可能須要對某些消息進行優先處理。在這樣的情況下,能夠使用優先郵箱來依據消息優先級將消息放在某個指定的位置。甚至可能是隊列頭。而不是隊列末尾。

假設使用這樣的隊列,消息的處理順序是由隊列的算法決定的,而不是FIFO。

Akka與其他Actor模型實現的一個重要區別在於當前的行為必須處理下一個從隊列中取出的消息,Akka不會去掃描郵箱來找到下一個匹配的消息。無法處理某個消息一般是作為失敗情況進行處理,除非Actor覆蓋了這個行為。

子Actor

每一個Actor都是一個潛在的監管者:假設它創建了子Actor來托付處理子任務。它會自己主動地監管它們。子Actor列表維護在Actor的上下文中。Actor能夠訪問它。

對列表的更改是通過創建(tt class=”docutils literal”>context.ActorOf(…))或者停止(context.stop(child))子Actor來實現,而且這些更改會立馬生效。實際的創建和停止操作在幕后以異步的方式完畢。這樣它們就不會“堵塞”其監管者。

監管策略

Actor的最后一部分是它用來處理其子Actor錯誤狀況的機制。錯誤處理是由Akka透明地進行處理的,將監管與監控中所描寫敘述的策略中的一個應用於每一個出現的失敗。

因為策略是Actor系統組織結構的基礎,所以一旦Actor被創建了它就不能被改動。

考慮對每一個Actor僅僅有唯一的策略,這意味着假設一個Actor的子Actor們應用了不同的策略。這些子Actor應該依照同樣的策略來進行分組,生成中間的監管者。又一次傾向於依據任務到子任務的划分來組織Actor系統的結構。

Actor終止

一旦一個Actor終止了,i.e. 失敗了而且不能用重新啟動來解決。停止它自己或者被它的監管者停止,它會釋放它的資源,將它郵箱中全部未處理的消息放進系統的“死信郵箱”。而Actor引用中的郵箱將會被一個系統郵箱所替代,系統郵箱會將全部新的消息重定向到“排水溝”。 可是這些操作僅僅是盡力而為,所以不能依賴它來實現“保證投遞”。

不是簡單地把(未處理的:譯者注)消息扔掉的想法來源於我們(Akka:譯者注)測試:我們在事件總線上注冊了TestEventListener來接收死信,然后將每一個收到的死信在日志中生成一條警告。

這對於更快地解析測試失敗很有幫助。

我們認為可能這個功能也能夠用於其他的目的。

參考資料

讓並發和容錯更easy:Akka演示樣例教程
Akka 2.0官方文檔中文版

轉載請注明作者Jason Ding及其出處
GitCafe博客主頁(http://jasonding1354.gitcafe.io/)
Github博客主頁(http://jasonding1354.github.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
簡書主頁(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354進入我的博客主頁


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM