【Akka】Actor引用

Actor系統的實體

在Actor系統中,actor之間具有樹形的監管結構,并且actor可以跨多個網絡節點進行透明通信。
對于一個Actor而言,其源碼中存在Actor,ActorContextActorRef等多個概念,它們都是為了描述Actor對象而進行的不同層面的抽象。
我們先給出一個官方的示例圖,再對各個概念進行解釋。


上圖很清晰的展示了一個actor在源碼層面的不同抽象,和不同actor之間的父子關系:
Actor類的一個成員context是ActorContext類型,ActorContext存儲了Actor類的上下文,包括self、sender。
ActorContext還混入了ActorRefFactory特質,其中實現了actorOf方法用來創建子actor。
這是Actor中context的源碼:

trait Actor {
  /**
   * Stores the context for this actor, including self, and sender.
   * It is implicit to support operations such as `forward`.
   *
   * WARNING: Only valid within the Actor itself, so do not close over it and
   * publish it to other threads!
   *
   * [[akka.actor.ActorContext]] is the Scala API. `getContext` returns a
   * [[akka.actor.UntypedActorContext]], which is the Java API of the actor
   * context.
   */
  implicit val context: ActorContext = {
    val contextStack = ActorCell.contextStack.get
    if ((contextStack.isEmpty) || (contextStack.head eq null))
      throw ActorInitializationException(
        s"You cannot create an instance of [${getClass.getName}] explicitly using the constructor (new). " +
          "You have to use one of the 'actorOf' factory methods to create a new actor. See the documentation.")
    val c = contextStack.head
    ActorCell.contextStack.set(null :: contextStack)
    c
  }

ActorCell的self成員是ActorRef類型,ActorRef是一個actor的不可變,可序列化的句柄(handle),它可能不在本地或同一個ActorSystem中,它是實現網絡空間位置透明性的關鍵設計。
這是ActorContext中self的源碼:

trait ActorContext extends ActorRefFactory {

  def self: ActorRef

ActorRef的path成員是ActorPath類型,ActorPath是actor樹結構中唯一的地址,它定義了根actor到子actor的順序。
這是ActorRef中path的源碼:

abstract class ActorRef extends java.lang.Comparable[ActorRef] with Serializable {
  /**
   * Returns the path for this actor (from this actor up to the root actor).
   */
  def path: ActorPath

Actor引用

Actor引用是ActorRef的子類,它的最重要功能是支持向它所代表的actor發送消息。每個actor通過self來訪問它的標準(本地)引用,在發送給其它actor的消息中也缺省包含這個引用。反過來,在消息處理過程中,actor可以通過sender來訪問到當前消息的發送者的引用。

不同類型的Actor引用

根據actor系統的配置,支持幾種不同的actor引用:

  1. 純本地引用被配置成不支持網絡功能的,這些actor引用發送的消息不能通過一個網絡發送到另一個遠程的JVM。
  1. 支持遠程調用的本地引用使用在支持同一個jvm中actor引用之間的網絡功能的actor系統中。為了在發送到其它網絡節點后被識別,這些引用包含了協議和遠程地址信息。
  2. 本地actor引用有一個子類是用在路由(比如,混入了Router trait的actor)。它的邏輯結構與之前的本地引用是一樣的,但是向它們發送的消息會被直接重定向到它的子actor。
  3. 遠程actor引用代表可以通過遠程通訊訪問的actor,i.e. 從別的jvm向他們發送消息時,Akka會透明地對消息進行序列化。
  4. 有幾種特殊的actor引用類型,在實際用途中比較類似本地actor引用:
  • PromiseActorRef表示一個Promise,作用是從一個actor返回的響應來完成,它是由akka.pattern.ask調用來創建的
  • DeadLetterActorRef是死信服務的缺省實現,所有接收方被關閉或不存在的消息都在此被重新路由。
  • EmptyLocalActorRef是查找一個不存在的本地actor路徑時返回的:它相當于DeadLetterActorRef,但是它保有其路徑因此可以在網絡上發送,以及與其它相同路徑的存活的actor引用進行比較,其中一些存活的actor引用可能在該actor消失之前得到了。
  1. 然后有一些內部實現,你可能永遠不會用上:
  • 有一個actor引用并不表示任何actor,只是作為根actor的偽監管者存在,我們稱它為“時空氣泡穿梭者”。
  • 在actor創建設施啟動之前運行的第一個日志服務是一個偽actor引用,它接收日志事件并直接顯示到標準輸出上;它就是Logging.StandardOutLogger。

獲得Actor引用

創建Actor

一個actor系統通常是在根actor上使用ActorSystem.actorOf創建actor,然后使用ActorContext.actorOf從創建出的actor中生出actor樹來啟動的。這些方法返回指向新創建的actor的引用。每個actor都擁有到它的父親,它自己和它的子actor的引用。這些引用可以與消息一直發送給別的actor,以便接收方直接回復。

具體路徑查找

另一種查找actor引用的途徑是使用ActorSystem.actorSelection方法,也可以使用ActorContext.actorSelection來在actor之中查詢。它會返回一個(未驗證的)本地、遠程或集群actor引用。向這個引用發送消息或試圖觀察它的存活狀態會在actor系統樹中從根開始一層一層從父向子actor發送消息,直到消息到達目標或是出現某種失敗,i.e.路徑中的某一個actor名字不存在(在實際中這個過程會使用緩存來優化,但相較使用物理actor路徑來說仍然增加了開銷,因為物理路徑能夠從actor的響應消息中的發送方引用中獲得),這個消息傳遞過程由Akka自動完成的,對客戶端代碼不可見。
使用相對路徑向兄弟actor發送消息:

context.actorSelection("../brother") ! msg

也可以用絕對路徑:

context.actorSelection("/user/serviceA") ! msg

查詢邏輯Actor層次結構

由于actor系統是一個類似文件系統的樹形結構,對actor的匹配與unix shell中支持的一樣:你可以將路徑(中的一部分)用通配符(?*? 和???)替換來組成對0個或多個實際actor的匹配。由于匹配的結果不是一個單一的actor引用,它擁有一個不同的類型ActorSelection,這個類型不完全支持ActorRef的所有操作。同樣,路徑選擇也可以用ActorSystem.actorSelection或ActorContext.actorSelection兩種方式來獲得,并且支持發送消息。
下面是將msg發送給包括當前actor在內的所有兄弟actor:

context.actorSelection("../*") ! msg

與遠程部署之間的互操作

當一個actor創建一個子actor,actor系統的部署者會決定新的actor是在同一個jvm中或是在其它的節點上。如果是在其他節點創建actor,actor的創建會通過網絡連接來到另一個jvm中進行,結果是新的actor會進入另一個actor系統。 遠程系統會將新的actor放在一個專為這種場景所保留的特殊路徑下。新的actor的監管者會是一個遠程actor引用(代表會觸發創建動作的actor)。這時,context.parent(監管者引用)和context.path.parent(actor路徑上的父actor)表示的actor是不同的。但是在其監管者中查找這個actor的名稱能夠在遠程節點上找到它,保持其邏輯結構,e.g.當向另外一個未確定(unresolved)的actor引用發送消息時。

因為設計分布式執行會帶來一些限制,最明顯的一點就是所有通過電纜發送的消息都必須可序列化。雖然有一點不太明顯的就是包括閉包在內的遠程角色工廠,用來在遠程節點創建角色(即Props內部)。
另一個結論是,要意識到所有交互都是完全異步的,它意味著在一個計算機網絡中一條消息需要幾分鐘才能到達接收者那里(基于配置),而且可能比在單JVM中有更高丟失率,后者丟失率接近于0(還沒有確鑿的證據)。

Akka使用的特殊路徑

在路徑樹的根上是根監管者,所有的的actor都可以從通過它找到。在第二個層次上是以下這些:

  • "/user"是所有由用戶創建的頂級actor的監管者,用ActorSystem.actorOf創建的actor在其下一個層次 are found at the next level。
  • "/system" 是所有由系統創建的頂級actor(如日志監聽器或由配置指定在actor系統啟動時自動部署的actor)的監管者。
  • "/deadLetters" 是死信actor,所有發往已經終止或不存在的actor的消息會被送到這里。
  • "/temp"是所有系統創建的短時actor(i.e.那些用在ActorRef.ask的實現中的actor)的監管者。
  • "/remote" 是一個人造的路徑,用來存放所有其監管者是遠程actor引用的actor。

附錄-Actor模型概述:

Actor模型為編寫并發和分布式系統提供了一種更高的抽象級別。它將開發人員從顯式地處理鎖和線程管理的工作中解脫出來,使編寫并發和并行系統更加容易。Actor模型是在1973年Carl Hewitt的論文中提的,但是被Erlang語言采用后才變得流行起來,一個成功案例是愛立信使用Erlang非常成功地創建了高并發的可靠的電信系統。

Actor的樹形結構

像一個商業組織一樣,actor自然會形成樹形結構。程序中負責某一個功能的actor可能需要把它的任務分拆成更小的、更易管理的部分。為此它啟動子Actor并監管它們。要知道每個actor有且僅有一個監管者,就是創建它的那個actor。


Actor系統的精髓在于任務被分拆開來并進行委托,直到任務小到可以被完整地進行處理。 這樣做不僅使任務本身被清晰地劃分出結構,而且最終的actor也能按照它們“應該處理的消息類型”,“如何完成正常流程的處理”以及“失敗流程應如何處理”來進行解析。如果一個actor對某種狀況無法進行處理,它會發送相應的失敗消息給它的監管者請求幫助。這樣的遞歸結構使得失敗能夠在正確的層次進行處理。

可以將這與分層的設計方法進行比較。分層的設計方法最終很容易形成防御性編程,以防止任何失敗被泄露出來。把問題交由正確的人處理會是比將所有的事情“藏在深處”更好的解決方案。

現在,設計這種系統的難度在于如何決定誰應該監管什么。這當然沒有一個唯一的最佳方案,但是有一些可能會有幫助的原則:

  • 如果一個actort管理另一個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和一個監管策略。所有這些包含在一個Actor引用里。

狀態

Actor對象通常包含一些變量來反映actor所處的可能狀態。這可能是一個明確的狀態機,或是一個計數器,一組監聽器,待處理的請求,等等。這些數據使得actor有價值,并且必須將這些數據保護起來不被其它的actor所破壞。

好消息是在概念上每個Akka actor都有它自己的輕量線程,這個線程是完全與系統其它部分隔離的。這意味著你不需要使用鎖來進行資源同步,可以完全不必擔心并發性地來編寫你的actor代碼。

在幕后,Akka會在一組線程上運行一組Actor,通常是很多actor共享一個線程,對某一個actor的調用可能會在不同的線程上進行處理。Akka保證這個實現細節不影響處理actor狀態的單線程性。

由于內部狀態對于actor的操作是至關重要的,所以狀態不一致是致命的。當actor失敗并由其監管者重新啟動,狀態會進行重新創建,就象第一次創建這個actor一樣。這是為了實現系統的“自愈合”。

行為

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

郵箱

Actor的用途是處理消息,這些消息是從其它的actor(或者從actor系統外部)發送過來的。連接發送者與接收者的紐帶是actor的郵箱:每個actor有且僅有一個郵箱,所有的發來的消息都在郵箱里排隊。排隊按照發送操作的時間順序來進行,這意味著從不同的actor發來的消息在運行時沒有一個固定的順序,這是由于actor分布在不同的線程中。從另一個角度講,從同一個actor發送多個消息到相同的actor,則消息會按發送的順序排隊。

可以有不同的郵箱實現供選擇,缺省的是FIFO:actor處理消息的順序與消息入隊列的順序一致。這通常是一個好的選擇,但是應用可能需要對某些消息進行優先處理。在這種情況下,可以使用優先郵箱來根據消息優先級將消息放在某個指定的位置,甚至可能是隊列頭,而不是隊列末尾。如果使用這樣的隊列,消息的處理順序是由隊列的算法決定的,而不是FIFO。

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

子Actor

每個actor都是一個潛在的監管者:如果它創建了子actor來委托處理子任務,它會自動地監管它們。子actor列表維護在actor的上下文中,actor可以訪問它。對列表的更改是通過context.actorOf(...)創建或者context.stop(child)停止子actor來實現,并且這些更改會立刻生效。實際的創建和停止操作在幕后以異步的方式完成,這樣它們就不會“阻塞”其監管者。

監督策略

Actor的最后一部分是它用來處理其子actor錯誤狀況的機制。錯誤處理是由Akka透明地進行處理的。由于策略是actor系統組織結構的基礎,所以一旦actor被創建了它就不能被修改。

考慮對每個actor只有唯一的策略,這意味著如果一個actor的子actor們應用了不同的策略,這些子actor應該按照相同的策略來進行分組,生成中間的監管者,又一次傾向于根據任務到子任務的劃分來組織actor系統的結構。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容