Akka手冊(cè)譯(一)——主管與監(jiān)測

本章概述了監(jiān)督的原語語義和背后的概念。而如何轉(zhuǎn)化為實(shí)際代碼,請(qǐng)參閱相應(yīng)的章節(jié)Scala和Java api。

主管的含義是什么

正如前文所言在Actor系統(tǒng)中主管描述了Actor之間的依賴關(guān)系:主管把任務(wù)委托給下屬,因此必須響應(yīng)它們的故障。在下屬檢測到故障時(shí)(即拋出異常)時(shí)它會(huì)暫停自已和它的下屬并向主管發(fā)送單一的故障消息。按被監(jiān)管的作用類別和故障類型,主管有四種選項(xiàng):

  1. 恢復(fù)下屬,保持其積累的內(nèi)部狀態(tài);
  2. 重新啟動(dòng)下屬,清理其積累的內(nèi)部狀態(tài);
  3. 永久停止下屬;
  4. 提升故障,使自已產(chǎn)生故障。

把Actor視為一個(gè)主管層次的一部分很重要,這也揭示了第四項(xiàng)存在的意義(做為一個(gè)主管同樣隸屬于級(jí)別更高的主管)及對(duì)前三項(xiàng)的影響:恢復(fù)Actor就會(huì)恢復(fù)它的所有下屬,重啟時(shí)也就重啟它的所有下屬(下面詳細(xì)說明),同樣停止也就停止它和所有下屬。應(yīng)該注意的是,默認(rèn)行為preRestart會(huì)使Actror類所有子集時(shí)在重新啟動(dòng)之前終止,不過它是可以覆蓋。在這個(gè)鉤子執(zhí)行后,遞歸重啟所有的子集。

每個(gè)主管都配置了一個(gè)函數(shù)將所有可能的故障原因(即異常)轉(zhuǎn)換成上面給出的四個(gè)選項(xiàng)之一。值得注意的是,這個(gè)函數(shù)并不需要失敗Actor的身份作為輸入。很容易想出例子的結(jié)構(gòu),這看起來可能不足夠靈活,比如希望策略適用于不同的下屬。主管形成一個(gè)遞歸的故障處理結(jié)構(gòu)這一點(diǎn)上是至關(guān)重要的。如果你在一個(gè)層級(jí)上試圖做太多,它將成為難以思考,因此在這種情況下,推薦添加一個(gè)主管的層級(jí)解決這樣問題。

Akka實(shí)現(xiàn)特定的形式稱為“父級(jí)的監(jiān)督”。Actor僅能被更高級(jí)的Actor創(chuàng)建,且被它所監(jiān)管。這個(gè)限制使得Actor的形成層次隱式監(jiān)督,鼓勵(lì)正確的設(shè)計(jì)。應(yīng)該注意的是,這也保證Actor不能孤立或從外部監(jiān)管,否則可能會(huì)捕捉不到意料之外的內(nèi)容。此外,這也會(huì)清除和停止Actor的子樹。

警告

主管關(guān)聯(lián)父子通信發(fā)生時(shí)通過特殊的系統(tǒng)消息,這些消息與用戶消息分離使用自已的郵箱。這就使主管的相關(guān)的事件與普通消息相比存在不確定性。一般來說用戶不能影響普通消息的順序與故障通知。詳細(xì)的討論和例子看到:消息排序部分。

頂級(jí)主管

top-level.png

Actor系統(tǒng)在啟動(dòng)創(chuàng)建三個(gè)Actor,看上圖。更多的信息見Actor路徑影響見Actor路徑高級(jí)范圍。

/user: 警衛(wèi)Actor

所有用戶創(chuàng)建的Actor都受一個(gè)父Actor影響,這個(gè)警衛(wèi)Actor命名為“/user”。這個(gè)Actor的子集用 system.actorOf() 創(chuàng)建。這就表示當(dāng)這個(gè)警衛(wèi)Actor終止時(shí)系統(tǒng)中所有普通的Actor也將被停止。這也就表示這個(gè)警衛(wèi)的主管策略決定了普通高級(jí)Actor被怎么監(jiān)管。從Akka 2.1可以配置使用設(shè)置akka.actor.guardian-supervisor-strategy,它的完整類名是SupervisorStrategyConfigurator。當(dāng)警衛(wèi)提升故障,根警衛(wèi)的響應(yīng)將是終止警衛(wèi),這會(huì)停止整個(gè)Actor系統(tǒng)。

/system:系統(tǒng)警衛(wèi)

這個(gè)特別的警衛(wèi)是為了實(shí)現(xiàn)有序關(guān)閉序列日志,在所有普通Actor終止時(shí)日志仍是活躍的,盡管日志是由Actor使用。這實(shí)現(xiàn)了通過系統(tǒng)警衛(wèi)觀測用戶警衛(wèi)并實(shí)現(xiàn)初始化在收到終止消息時(shí)自我關(guān)閉。頂級(jí)Actor采用的策略在子集出現(xiàn)問題時(shí)無限重啟除了拋出 ActorInitializationException和ActorKilledException外的所有異常. 其它可扔出的升級(jí)時(shí),關(guān)閉整個(gè)Actor系統(tǒng)。

/ :根警衛(wèi)

根警衛(wèi)是祖父級(jí)、所謂的頂級(jí)Actor和主管所有在Actor路徑的頂級(jí)范圍使用SupervisorStrategy.stoppingStrategy,它的目的是終止所有子集的所有異常。所有可拋出的都會(huì)被升級(jí),但交給誰?由于每個(gè)真實(shí)的Actor都有個(gè)主管,而根警衛(wèi)的主管不是一個(gè)真實(shí)的Actor。因?yàn)樗鼘儆凇坝蛲馓摶保虼吮环Q為“虛化穿越者”;一個(gè)綜合的實(shí)體ActorRef 會(huì)在它的子集出現(xiàn)麻煩時(shí)關(guān)閉,并設(shè)置Actor系統(tǒng)isTerminated狀態(tài)為true然后根警衛(wèi)將完全終止(所有子集遞歸停止)。

重啟表示什么

當(dāng)一個(gè)Actor處理時(shí)產(chǎn)生的故障分為三類:

  • 收到體系內(nèi)(如,程序上的)特定錯(cuò)誤消息;
  • 處理消息時(shí)(瞬間)產(chǎn)生的外部資源故障;
  • 有誤的Actor內(nèi)部狀態(tài)。

除非故障明確地可辨認(rèn),否則第三方引起內(nèi)部狀態(tài)將被強(qiáng)制的被清除。假如主管認(rèn)為它的子集或自身沒有受到錯(cuò)誤的影響,比如由應(yīng)用程序的內(nèi)核引起,因此最好選擇重啟子集。由通過創(chuàng)建底層的Actor實(shí)例實(shí)現(xiàn),替換其中的錯(cuò)誤實(shí)例并刷新了子集的ActorRef;這樣做的目的是通過指定的引用封裝Actor。新的Actor恢復(fù)處理它的郵件,這就表示重啟對(duì)外部的Actor不可見且消息處理時(shí)產(chǎn)生的明顯的異常不會(huì)再次處理。
重啟期間精確的事件序列如下:

  1. 掛起Actor(這就使它在恢復(fù)前不會(huì)處理普通的消息),并且遞歸掛起它的子集;
  2. 回調(diào)舊實(shí)例的preRestart(默認(rèn)向所有子集發(fā)送終止請(qǐng)求并調(diào)用postStop);
  3. preRestart時(shí)到最終停止時(shí)等待所有被要求停止的子集(使用context.stop());像所有的Actor操作那樣,這是非阻塞的,最后被殺死的子對(duì)象的終止提示會(huì)進(jìn)行下一步的處理;
  4. 用最初提供的工廠再一次創(chuàng)建Actor新實(shí)例;
  5. 在新實(shí)例上調(diào)用postRestart(默認(rèn)時(shí)稱為preStart);
  6. 向所有沒有在第三步被殺死的子集發(fā)送重啟請(qǐng)求;重啟的子集會(huì)用同樣的方法從第2步啟遞歸調(diào)用。
  7. 恢復(fù)Actor。

生命周期監(jiān)測是什么意思

注意
在Akka中生命周期監(jiān)測被稱為“臨終看護(hù)”

當(dāng)上文描述的特殊的父子關(guān)系相反,每一個(gè)Actor都可能其它的Actor。不管Actor從創(chuàng)建到完整的生存期還是受主管影響下不可見的對(duì)外重啟,從生存到死亡僅僅是狀態(tài)能被監(jiān)控到。監(jiān)控用來綁定一個(gè)Actor到另一個(gè)上以便響應(yīng)其它Actor的終止,這與主管的響應(yīng)故障不同。

生命周期監(jiān)控是通過監(jiān)控Actor對(duì)Terminated消息的接收而實(shí)現(xiàn),它的默認(rèn)行為是在沒有其它處理方式拋出DeathPactException。通過調(diào)用ActorContext.watch(targetActorRef)開啟對(duì)Terminated消息的監(jiān)聽。調(diào)用ActorContext.unwatch(targetActorRef)停止監(jiān)聽。有一個(gè)重要的特性是消息在監(jiān)控請(qǐng)求與終止發(fā)生時(shí)被收到的次序無關(guān),比如,在受關(guān)注的目標(biāo)已經(jīng)死時(shí)仍然會(huì)收到消息事件。

在主管不能簡單的重啟或終止它的子集時(shí)監(jiān)控特別管用。例如,在Actor初始化時(shí)產(chǎn)生錯(cuò)誤,在這種情況下監(jiān)控子集或重建它們或安排它在未來某個(gè)時(shí)間重試。

另一個(gè)常見的用例是Actor在沒有外部資源的情況下需要失敗,這可能也是自身的子集。如果第三方終止子集通過system.stop(child)方法或發(fā)送PoisonPill,主管可能會(huì)受到影響。

BackoffSupervisor模式推遲重啟

內(nèi)建的akka.pattern.BackoffSupervisor實(shí)現(xiàn)了所謂指數(shù)補(bǔ)償主管策略,即在啟動(dòng)子Actor失敗后下一次間隔更長的時(shí)間重啟。

這種模式在Actor啟動(dòng)失敗時(shí)非常有用【1】,因?yàn)橐恍┩獠抠Y源無效,而我們需要給它一些時(shí)間再次啟動(dòng)。比如說 PersistentActor在做持久化(停止)時(shí)失敗,原因是數(shù)據(jù)庫停止或超載,在這種情況下最合適的做方法是給它一些時(shí)候恢復(fù)在持久化的Actor運(yùn)行前。
【1】有兩種方法引起故障,在Actor停止和崩潰。
下例Scala代碼顯示了如何建立一個(gè)補(bǔ)償主管在Actor故障后啟動(dòng),增加間隔從3,6,12,24直到30秒。

1. val childProps = Props(classOf[EchoActor])
2. 
3.val supervisor = BackoffSupervisor.props(
4.  Backoff.onFailure(
5.    childProps,
6.    childName = "myEcho",
7.    minBackoff = 3.seconds,
8.    maxBackoff = 30.seconds,
9.    randomFactor = 0.2 // adds 20% "noise" to vary the intervals slightly
10.  ))
11. 
12.system.actorOf(supervisor, name = "echoSupervisor")

相對(duì)應(yīng)的JAVA代碼是
1. import scala.concurrent.duration.Duration;

1. final Props childProps = Props.create(EchoActor.class);
2. 
3. final Props  supervisorProps = BackoffSupervisor.props(
4.  Backoff.onStop(
5.    childProps,
6.    "myEcho",
7.    Duration.create(3, TimeUnit.SECONDS),
8.    Duration.create(30, TimeUnit.SECONDS),
9.    0.2)); // adds 20% "noise" to vary the intervals slightly
10. 
11. system.actorOf(supervisorProps, "echoSupervisor");

強(qiáng)烈建議用randomFactor增加些額外的補(bǔ)償方差,以避免在多個(gè)Actor精確的在同一個(gè)時(shí)刻重啟。如果他們因?yàn)橐粋€(gè)共享資源如數(shù)據(jù)庫停上又配置了同樣的間隔。通過添加隨機(jī)數(shù)使它們啟動(dòng)時(shí)不在同一個(gè)時(shí)點(diǎn),避免從剛恢復(fù)的數(shù)據(jù)庫或其它資源上有大流量的訪問。
akka.pattern.BackoffSupervisor能被配置為延時(shí)啟動(dòng),當(dāng)Actor崩潰或主管決定它需要重啟。
下例Scala代碼顯示了如何建立一個(gè)補(bǔ)償主管在Actor故障后啟動(dòng),增加間隔從3,6,12,24直到30秒

1. final Props childProps = Props.create(EchoActor.class);
2. 
3.final Props  supervisorProps = BackoffSupervisor.props(
4.  Backoff.onFailure(
5.    childProps,
6.    "myEcho",
7.    Duration.create(3, TimeUnit.SECONDS),
8.    Duration.create(30, TimeUnit.SECONDS),
9.    0.2)); // adds 20% "noise" to vary the intervals slightly
10. 
11.system.actorOf(supervisorProps, "echoSupervisor");

akka.pattern.BackoffOptions被用在定義補(bǔ)償主管的自定義行為,如下所示:

 1. val supervisor = BackoffSupervisor.props(
2.  Backoff.onStop(
3.  childProps,
4.  childName = "myEcho",
5.  minBackoff = 3.seconds,
6.  maxBackoff = 30.seconds,
7.  randomFactor = 0.2 // adds 20% "noise" to vary the intervals slightly
8. ).withManualReset // the child must send BackoffSupervisor.Reset to its parent
9.  .withDefaultStoppingStrategy // Stop at any Exception thrown
10. )

上面的代碼設(shè)置了一個(gè)補(bǔ)償主這需要子Actor 發(fā)送一個(gè)akka.pattern.BackoffSupervisor.Reset消息到父級(jí)在消息成功處理后并重置補(bǔ)償。他也使用默認(rèn)的停止策略,任何異常都會(huì)引起子集的停止。

 1. val supervisor = BackoffSupervisor.props(
2.  Backoff.onFailure(
3.    childProps,
4.    childName = "myEcho",
5.    minBackoff = 3.seconds,
6.    maxBackoff = 30.seconds,
7.    randomFactor = 0.2 // adds 20% "noise" to vary the intervals slightly
8.  ).withAutoReset(10.seconds) // the child must send BackoffSupervisor.Reset to its parent
9.    .withSupervisorStrategy(
10.      OneForOneStrategy() {
11.        case _: MyException => SupervisorStrategy.Restart
12.        case _              => SupervisorStrategy.Escalate
13.      }))

上面的代碼設(shè)置了一個(gè)補(bǔ)償主管在補(bǔ)償后在拋出MyException后重置子集,其它異常會(huì)提升錯(cuò)誤等級(jí)。子集在沒有拋出異常10秒后補(bǔ)償會(huì)自動(dòng)重置。

一對(duì)一策略和全對(duì)一策略

Akka中有兩類主管策略:OneForOneStrategyAllForOneStrategy。兩者都配置了從異常類型到主管指令(見上文)的映射并限制的子集在終止前失敗的頻率。不同的是前者獲得的指令只適用于失敗的子集,后者適用于它和它的兄弟節(jié)點(diǎn)。默認(rèn)采用OneForOneStrategy,這是一個(gè)非顯性的聲明。

AllForOneStrategy適用于全體子集有相當(dāng)緊密的聯(lián)系,即一個(gè)故障會(huì)影響其它功能。因?yàn)橹貑]有清理郵箱,最好在子集出現(xiàn)錯(cuò)誤后停止并由主管明確的重建它們(通過觀察生命周期);否則必須確保在重啟前處理以后的從任一Actor接收到入隊(duì)列的消息是正確的。

通常停止一個(gè)子節(jié)點(diǎn)(即不是響應(yīng)失?。┰谌珜?duì)一策略中不會(huì)終止其它子節(jié)點(diǎn),這可通過它們的觀察生命周期。如果Terminated消息不是由主管發(fā)起,它會(huì)拋出一個(gè)DeathPactException異常(由主管決定)重啟它。默認(rèn)preRestart 的行為會(huì)停止所有子集。當(dāng)然,這也可以顯式地處理。

請(qǐng)注意創(chuàng)建一次性的Actor從全對(duì)一的主管需要通過臨時(shí)Actor提升故障會(huì)有永久影響。如果不想發(fā)生這種情況,安裝一個(gè)中間的主管,并聲明路由大小為1。

上一篇
下一篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容