### 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對某種狀況無法進行處理,它會發送相應的失敗消息給它的監管者請求幫助。這樣的遞歸結構使得失敗能夠在正確的層次進行處理。
可以將這與分層的設計方法進行比較。分層的設計方法最終很容易形成防護性編程,以防止任何失敗被泄露出來。把問題交由正確的人處理會是比將所有的事情“藏在深處”更好的解決方案。
現在,設計這種系統的難度在于如何決定誰應該監管什么。這當然沒有一個唯一的最佳方案,但是有一些可能會有幫助的原則:
> - 如果一個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們。
1. 不要在Actor之間傳遞可變對象。為了保證這一點,盡量使用不變量消息。如果Actor將他們的可變狀態暴露給外界,打破了封裝,你又回到了普通的Java并發領域并遭遇所有其缺點。
1. 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來接收死信,然后將每個收到的死信在日志中生成一條警告。這對于更快地解析測試失敗非常有幫助。我們覺得可能這個功能也可以用于其它的目的。
### 參考資料
[讓并發和容錯更容易:Akka示例教程](http://www.csdn.net/article/2014-12-17/2823174)
[Akka 2.0官方文檔中文版](http://www.gtan.com/akka_doc/)
**轉載請注明作者Jason Ding及其出處**
[GitCafe博客主頁(http://jasonding1354.gitcafe.io/)](http://jasonding1354.gitcafe.io/)
[Github博客主頁(http://jasonding1354.github.io/)](http://jasonding1354.github.io/)
[CSDN博客(http://blog.csdn.net/jasonding1354)](http://blog.csdn.net/jasonding1354)
[簡書主頁(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)](http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
**Google搜索jasonding1354進入我的博客主頁**