【AKKA 官方文檔翻譯】第一部分:Actor架構

來源:互聯網
上載者:User
第一部分:Actor架構

akka版本2.5.8
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

Akka為你提供了建立actor系統的一些基礎功能,並且已經實現了控制基本行為必需的底層代碼。為了體會這一點,我們來看看你建立的actor角色與akka內部建立的管理角色的關係,順便瞭解下actor的生命週期和失敗處理方式。 akka actor的階層

akka裡的actor總是屬於其父母。通常你通過調用 context.actorOf() 建立一個actor。這種方式向現有的actor樹內加入了一個新的actor,這個actor的建立者就成為了這個actor的父actor。你可能會問,誰是你建立的第一個actor的父actor呢。

如下圖所示,所有actor都有一個共同的家長,稱為user guardian。可以通過調用 system.actorOf() 來建立屬於它新actor執行個體。正如我們在快速入門手冊中所介紹的,建立actor將返回一個有效URL的引用,因此,如果我們通過調用system.actorOf(…, "someActor")建立一個名為someActor的actor時,其引用將包含路徑/user/someActor。

實際上在你調用你建立actor的代碼之前,akka已經在系統中建立了三個actor。這些內建的actor名字都包含guardian,因為它們會監管它們各自路徑下的所有子actor。guardian actor包括:

/所謂的根監護人。這是系統中所有actor的父親,當系統被終止時,它也是最後一個被停止的。

/user 這是所有使用者建立的actor的父親。不要被user這個名字所迷惑,他與終端使用者沒有關係,也和使用者處理無關。你使用akka庫所建立的所有actor的路徑都將以/user/開頭
/system系統監護人

在Hello World樣本中,我們已經看到了如何使用system.actorOf()來建立一個在/user路徑下的actor。儘管它只是在使用者建立的層次的最進階actor,但是我們把它稱作頂級actor。在你的ActorSystem裡,你通常只有一個(或者非常少)頂級actor。我們在已有的actor裡通過調用context.actorOf()來建立新的非頂級actor,即子actor。context.actorOf()的方法簽名和system.actorOf()相同。

查看actor層級結構最簡單的方式就是列印ActorRef的執行個體。在這個小實驗中,我們建立了一個actor,列印了它的引用,為他建立了一個子actor,並列印其子actor的引用。我們從Hello World工程開始,如果你還沒有下載它,請從Lightbend Tech Hub下載Quickstart工程。

在你的Hello World工程裡,導航到com.lightbend.akka.sample包的位置,在這裡建立一個新Scala檔案ActorHierarchyExperiments.scala。複製粘貼下面的代碼到這個新檔案裡。儲存檔案並運行sbt "runMain com.lightbend.akka.sample.ActorHierarchyExperiments",然後觀察輸出。

package com.lightbend.akka.sampleimport akka.actor.{ Actor, Props, ActorSystem }import scala.io.StdInclass PrintMyActorRefActor extends Actor {  override def receive: Receive = {    case "printit" ⇒      val secondRef = context.actorOf(Props.empty, "second-actor")      println(s"Second: $secondRef")  }}object ActorHierarchyExperiments extends App {  val system = ActorSystem("testSystem")  val firstRef = system.actorOf(Props[PrintMyActorRefActor], "first-actor")  println(s"First: $firstRef")  firstRef ! "printit"  println(">>> Press ENTER to exit <<<")  try StdIn.readLine()  finally system.terminate()}

注意一點,即如何給第一個actor發送一個訊息來使它開始工作。我們使用父類引用來發送資訊:firstRef ! "printit"。當程式啟動並執行時候,輸出資訊會包含第一個actor的引用,也包含在printit中建立的子actor的引用。你的輸出應該看上去像這樣:

First: Actor[akka://testSystem/user/first-actor#1053618476]

Second: Actor[akka://testSystem/user/first-actor/second-actor#-1544706041]

請注意引用結構:

兩個路徑都以akka://testSystem/開頭,所有的actor引用都是合法的URL。akka://是協議欄位。

接下來,就像在World Wide Web上一樣,URL是一個系統的標識。在這個樣本裡,系統的名稱為testSystem,它也可以被命名為任意名字。如果開啟了多系統間的遠程通訊,URL裡就會包含主機名稱,以便其他系統可以在網路上找到它。

因為第二個actor引用的路徑裡包含/first-actor/,這表明它是第一個actor的子actor。

actor引用的最後一段,#1053618476 和 #-1544706041是一個actor的唯一識別碼uid,你在大多數情境下可以忽略它的存在。

現在你似乎瞭解了actor系統的階層,你可能會想:為什麼我們需要這個階層。他是幹什麼用的呢。

階層的一個很重要的功能是來安全地管理actor的生命週期,接下來讓我們思考一下怎麼利用它來寫出優秀的代碼。 actor的生命週期

actor在被建立後存在,並且在使用者請求關閉時消失。當actor被關閉後,其所有的子actor都將被遞迴地關閉。這個特性極大簡化了我們的資源清理工作,並且防止資源流失(通訊端或者檔案等)。實際上,在進行底層多線程編程編程時,我們經常會小看對各種並發資源生命週期管理的難度。

為了關閉actor,我們推薦在actor內部調用context.stop(self)來自我關閉,這個代碼經常放在使用者定義的結束資訊回應裡,或者在actor已經做完了其工作後自行調用。停止其他的actor在技術上是允許的,可以通過調用context.stop(actorRef)來實現。但是用這種方式來停止一個actor是一個壞習慣,但是你可以給這個actor發送PoisonPill或者自訂關閉訊息來關閉它。

Akka的actor提供了很對生命週期API,你可以在實現actor時重載這些方法。最常用的是preStart()和postStop()。

preStart()會在actor啟動後,並在它處理第一個訊息之前被調用

postStop()會在actor將要被關閉前被調用,在它之後,actor不會再處理任何訊息了

讓我們簡單實驗下如何使用preStart()和postStop()生命週期鉤子去觀察actor關閉時的行為。首先,在你的工程裡添加以下兩個actor類:

class StartStopActor1 extends Actor {  override def preStart(): Unit = {    println("first started")    context.actorOf(Props[StartStopActor2], "second")  }  override def postStop(): Unit = println("first stopped")  override def receive: Receive = {    case "stop" ⇒ context.stop(self)  }}class StartStopActor2 extends Actor {  override def preStart(): Unit = println("second started")  override def postStop(): Unit = println("second stopped")  // Actor.emptyBehavior 是一個有用的預留位置  // 在我們不想用這個actor處理任何資訊是使用它  override def receive: Receive = Actor.emptyBehavior}

像之前一樣建立一個主類(main),負責建立actor並且在之後給它們發送"stop"訊息。

val first = system.actorOf(Props[StartStopActor1], "first")first ! "stop"

你可以再次使用sbt來啟動這個程式,得到的輸出應該是這樣的:

first started

second started

second stopped

first stopped

當我們停止firstactor時,它在自己被關閉前關閉了它的子actor:second。這個順序是嚴格被執行的,所有子actor的postStop()鉤子都將在它們的父actorpostStop()調用之前被調用。

Akka參考手冊中Actor生命週期部分提供了有關整個生命週期鉤子的詳細資料。 失敗處理

父actor和子actor在整個聲明周期內都保持著聯絡。當一個actor失敗了(拋出異常或者在receive裡冒出一個未處理的異常),他會被暫時地掛起。就像之前提到的一樣,失敗資訊會被傳遞到父actor中,然後由父actor來決定如何處理這個子actor產生的異常。在這種方式下,父actor就是子actor的監管者,預設的監管策略就是停止並且重啟子actor。如果你沒有修改預設的監管策略,那麼所有的失敗都會導致重啟actor。

讓我們在一個簡單的實驗中來測試下預設的監管策略。就像之前一樣,在你的工程裡添加以下的類。

class SupervisingActor extends Actor {  val child = context.actorOf(Props[SupervisedActor], "supervised-actor")  override def receive: Receive = {    case "failChild" ⇒ child ! "fail"  }}class SupervisedActor extends Actor {  override def preStart(): Unit = println("supervised actor started")  override def postStop(): Unit = println("supervised actor stopped")  override def receive: Receive = {    case "fail" ⇒      println("supervised actor fails now")      throw new Exception("I failed!")  }}

並且在mian裡用以下代碼運行:

val supervisingActor = system.actorOf(Props[SupervisingActor], "supervising-actor")supervisingActor ! "failChild"

你應該會看到類似如下輸出:

supervised actor startedsupervised actor fails nowsupervised actor stoppedsupervised actor started[ERROR] [03/29/2017 10:47:14.150] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/supervising-actor/supervised-actor] I failed!java.lang.Exception: I failed!        at tutorial_1.SupervisedActor$$anonfun$receive$4.applyOrElse(ActorHierarchyExperiments.scala:57)        at akka.actor.Actor$class.aroundReceive(Actor.scala:513)        at tutorial_1.SupervisedActor.aroundReceive(ActorHierarchyExperiments.scala:47)        at akka.actor.ActorCell.receiveMessage(ActorCell.scala:519)        at akka.actor.ActorCell.invoke(ActorCell.scala:488)        at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)        at akka.dispatch.Mailbox.run(Mailbox.scala:224)        at akka.dispatch.Mailbox.exec(Mailbox.scala:234)        at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)        at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)        at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)        at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

我們看到在actor失敗後,被監管的actor被立即停止並重啟,我們也看到了一條這個異常被處理的日誌。在測試中,我們使用preStart()和postStop()鉤子,這些鉤子可以在actor被重啟前後被調用,所以我們不能用它來區分actor是第一次啟動還是被重啟。重啟actor在大多數情況是正確的,重啟可以讓actor恢複到一個已知的正確狀態上,也就是啟動時的乾淨狀態。在內部真正發生的是:preRestart()和postRestart()方法被調用,如果它們沒有被重載,則它們分別會調用postStop()和preStart()你可以嘗試重載這些方法,看看輸出的改變。

如果你已經不耐煩了,那麼你可以在監督參考頁面裡獲得更多深入的細節。 總結

我們已經學到了Akka是如何管理actor的,actor是有層級結構的,並且父actor監管它們的子actor,並處理子actor的異常。我們看到了如何建立一個非常簡單的actor和子actor。接下來,我們會通過通訊建模將這些知識應用在我們的執行個體裡,以便從裝置actor裡擷取資訊。之後,我們會學到如何在組裡管理actor。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.