摘要:是所有由系統(tǒng)創(chuàng)建的頂級(jí)的監(jiān)管者,如日志監(jiān)聽器,或由配置指定在系統(tǒng)啟動(dòng)時(shí)自動(dòng)部署的。所有其他被上升到根監(jiān)管者,然后整個(gè)系統(tǒng)將會(huì)關(guān)閉。監(jiān)管容錯(cuò)示例本示例主要演示在發(fā)生錯(cuò)誤時(shí),它的監(jiān)管者會(huì)根據(jù)相應(yīng)的監(jiān)管策略進(jìn)行不同的處理。
Akka作為一種成熟的生產(chǎn)環(huán)境并發(fā)解決方案,必須擁有一套完善的錯(cuò)誤異常處理機(jī)制,本文主要講講Akka中的監(jiān)管和容錯(cuò)。
監(jiān)管看過(guò)我上篇文章的同學(xué)應(yīng)該對(duì)Actor系統(tǒng)的工作流程有了一定的了解Akka系列(二):Akka中的Actor系統(tǒng),它的很重要的概念就是分而治之,既然我們把任務(wù)分配給Actor去執(zhí)行,那么我們必須去監(jiān)管相應(yīng)的Actor,當(dāng)Actor出現(xiàn)了失敗,比如系統(tǒng)環(huán)境錯(cuò)誤,各種異常,能根據(jù)我們制定的相應(yīng)監(jiān)管策略進(jìn)行錯(cuò)誤恢復(fù),就是后面我們會(huì)說(shuō)到的容錯(cuò)。
監(jiān)管者既然有監(jiān)管這一事件,那必然存在著監(jiān)管者這么一個(gè)角色,那么在ActorSystem中是如何確定這種角色的呢?
我們先來(lái)看下ActorSystem中的頂級(jí)監(jiān)管者:
一個(gè)actor系統(tǒng)在其創(chuàng)建過(guò)程中至少要啟動(dòng)三個(gè)actor,如上圖所示,下面來(lái)說(shuō)說(shuō)這三個(gè)Actor的功能:
1./: 根監(jiān)管者顧名思義,它是一個(gè)老大,它監(jiān)管著ActorSystem中所有的頂級(jí)Actor,頂級(jí)Actor有以下幾種:
/user: 是所有由用戶創(chuàng)建的頂級(jí)actor的監(jiān)管者;用ActorSystem.actorOf創(chuàng)建的actor在其下。
/system: 是所有由系統(tǒng)創(chuàng)建的頂級(jí)actor的監(jiān)管者,如日志監(jiān)聽器,或由配置指定在actor系統(tǒng)啟動(dòng)時(shí)自動(dòng)部署的actor。
/deadLetters: 是死信actor,所有發(fā)往已經(jīng)終止或不存在的actor的消息會(huì)被重定向到這里。
/temp:是所有系統(tǒng)創(chuàng)建的短時(shí)actor的監(jiān)管者,例如那些在ActorRef.ask的實(shí)現(xiàn)中用到的actor。
/remote: 是一個(gè)人造虛擬路徑,用來(lái)存放所有其監(jiān)管者是遠(yuǎn)程actor引用的actor。
跟我們平常打交道最多的就是/user,它是我們?cè)诔绦蛑杏肁ctorSystem.actorOf創(chuàng)建的actor的監(jiān)管者,下面的容錯(cuò)我們重點(diǎn)關(guān)心的就是它下面的失敗處理,其他幾種頂級(jí)Actor具體功能定義已經(jīng)給出,有興趣的也可以去了解一下。
根監(jiān)管者監(jiān)管著所有頂級(jí)Actor,對(duì)它們的各種失敗情況進(jìn)行處理,一般來(lái)說(shuō)如果錯(cuò)誤要上升到根監(jiān)管者,整個(gè)系統(tǒng)就會(huì)停止。
2./user: 頂級(jí)actor監(jiān)管者上面已經(jīng)講過(guò)/user是所有由用戶創(chuàng)建的頂級(jí)actor的監(jiān)管者,即用ActorSystem.actorOf創(chuàng)建的actor,我們可以自己制定相應(yīng)的監(jiān)管策略,但由于它是actor系統(tǒng)啟動(dòng)時(shí)就產(chǎn)生的,所以我們需要在相應(yīng)的配置文件里配置,具體的配置可以參考這里Akka配置
3./system: 系統(tǒng)監(jiān)管者/system所有由系統(tǒng)創(chuàng)建的頂級(jí)actor的監(jiān)管者,比如Akka中的日志監(jiān)聽器,因?yàn)樵贏kka中日志本身也是用Actor實(shí)現(xiàn)的,/system的監(jiān)管策略如下:對(duì)收到的除ActorInitializationException和ActorKilledException之外的所有Exception無(wú)限地執(zhí)行重啟,當(dāng)然這也會(huì)終止其所有子actor。所有其他Throwable被上升到根監(jiān)管者,然后整個(gè)actor系統(tǒng)將會(huì)關(guān)閉。
用戶創(chuàng)建的普通actor的監(jiān)管:
上一篇文章介紹了Actor系統(tǒng)的組織結(jié)構(gòu),它是一種樹形結(jié)構(gòu),其實(shí)這種結(jié)構(gòu)對(duì)actor的監(jiān)管是非常有利的,Akka實(shí)現(xiàn)的是一種叫“父監(jiān)管”的形式,每一個(gè)被創(chuàng)建的actor都由其父親所監(jiān)管,這種限制使得actor的監(jiān)管結(jié)構(gòu)隱式符合其樹形結(jié)構(gòu),所以我們可以得出一個(gè)結(jié)論:
監(jiān)管策略一個(gè)被創(chuàng)建的Actor肯定是一個(gè)被監(jiān)管者,也可能是一個(gè)監(jiān)管者,它監(jiān)管著它的子級(jí)Actor
上面我們對(duì)ActorSystem中的監(jiān)管角色有了一定的了解,那么到底是如何制定相應(yīng)的監(jiān)管策略呢?Akka中有以下4種策略:
恢復(fù)下屬,保持下屬當(dāng)前積累的內(nèi)部狀態(tài)
重啟下屬,清除下屬的內(nèi)部狀態(tài)
永久地停止下屬
升級(jí)失?。ㄑ乇O(jiān)管樹向上傳遞失?。纱耸∽约?/p>
這其實(shí)很好理解,下面是一個(gè)簡(jiǎn)單例子:
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { case _: ArithmeticException => Resume //恢復(fù) case _: NullPointerException => Restart //重啟 case _: IllegalArgumentException => Stop //停止 case _: Exception => Escalate //向上級(jí)傳遞 }
我們可以根據(jù)異常的不同使用不同監(jiān)管策略,在后面我會(huì)具體給出一個(gè)示例程序幫助大家理解。我們?cè)趯?shí)現(xiàn)自己的策略時(shí),需要復(fù)寫Actor中的supervisorStrategy,因?yàn)锳ctor的默認(rèn)監(jiān)管策略如下:
final val defaultDecider: Decider = { case _: ActorInitializationException ? Stop case _: ActorKilledException ? Stop case _: DeathPactException ? Stop case _: Exception ? Restart }
它對(duì)除了它指定的異常進(jìn)行停止,其他異常都是對(duì)下屬進(jìn)行重啟。
Akka中有兩種類型的監(jiān)管策略:OneForOneStrategy和AllForOneStrategy,它們的主要區(qū)別在于:
OneForOneStrategy: 該策略只會(huì)應(yīng)用到發(fā)生故障的子actor上。
AllForOneStrategy: 該策略會(huì)應(yīng)用到所有的子actor上。
我們一般都使用OneForOneStrategy來(lái)進(jìn)行制定相關(guān)監(jiān)管策略,當(dāng)然你也可以根據(jù)具體需求選擇合適的策略。另外我們可以給我們的策略配置相應(yīng)參數(shù),比如上面maxNrOfRetries,withinTimeRange等,這里的含義是每分鐘最多進(jìn)行10次重啟,若超出這個(gè)界限相應(yīng)的Actor將會(huì)被停止,當(dāng)然你也可以使用策略的默認(rèn)配置,具體配置信息可以參考源碼。
監(jiān)管容錯(cuò)示例本示例主要演示Actor在發(fā)生錯(cuò)誤時(shí),它的監(jiān)管者會(huì)根據(jù)相應(yīng)的監(jiān)管策略進(jìn)行不同的處理。源碼鏈接
因?yàn)檫@個(gè)例子比較簡(jiǎn)單,這里我直接貼上相應(yīng)代碼,后面根據(jù)具體的測(cè)試用例來(lái)解釋各種監(jiān)管策略所進(jìn)行的響應(yīng):
class Supervisor extends Actor { //監(jiān)管下屬,根據(jù)下屬拋出的異常進(jìn)行相應(yīng)的處理 override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) { case _: ArithmeticException => Resume case _: NullPointerException => Restart case _: IllegalArgumentException => Stop case _: Exception => Escalate } var childIndex = 0 //用于標(biāo)示下屬Actor的序號(hào) def receive = { case p: Props => childIndex += 1 //返回一個(gè)Child Actor的引用,所以Supervisor Actor是Child Actor的監(jiān)管者 sender() ! context.actorOf(p,s"child${childIndex}") } } class Child extends Actor { val log = Logging(context.system, this) var state = 0 def receive = { case ex: Exception => throw ex //拋出相應(yīng)的異常 case x: Int => state = x //改變自身狀態(tài) case s: Command if s.content == "get" => log.info(s"the ${s.self} state is ${state}") sender() ! state //返回自身狀態(tài) } } case class Command( //相應(yīng)命令 content: String, self: String )
現(xiàn)在我們來(lái)看看具體的測(cè)試用例:
首先我們先構(gòu)建一個(gè)測(cè)試環(huán)境:
class GuardianSpec(_system: ActorSystem) extends TestKit(_system) with WordSpecLike with Matchers with ImplicitSender { def this() = this(ActorSystem("GuardianSpec")) "A supervisor" must { "apply the chosen strategy for its child" in { code here... val supervisor = system.actorOf(Props[Supervisor], "supervisor") //創(chuàng)建一個(gè)監(jiān)管者 supervisor ! Props[Child] val child = expectMsgType[ActorRef] // 從 TestKit 的 testActor 中獲取回應(yīng) } } }
1.TestOne:正常運(yùn)行
child ! 50 // 將狀態(tài)設(shè)為 50 child ! Command("get",child.path.name) expectMsg(50)
正常運(yùn)行,測(cè)試通過(guò)。
2.TestTwo:拋出ArithmeticException
child ! new ArithmeticException // crash it child ! Command("get",child.path.name) expectMsg(50)
大家猜這時(shí)候測(cè)試會(huì)通過(guò)嗎?答案是通過(guò),原因是根據(jù)我們制定的監(jiān)管策略,監(jiān)管者在面對(duì)子級(jí)Actor拋出ArithmeticException異常時(shí),它會(huì)去恢復(fù)相應(yīng)出異常的Actor,并保持該Actor的狀態(tài),所以此時(shí)Actor的狀態(tài)值還是50,測(cè)試通過(guò)。
3.TestThree:拋出NullPointerException
child ! new NullPointerException // crash it harder child ! "get" expectMsg(50)
這種情況下測(cè)試還會(huì)通過(guò)嗎?答案是不通過(guò),原因是根據(jù)我們制定的監(jiān)管策略,監(jiān)管者在面對(duì)子級(jí)Actor拋出NullPointerException異常時(shí),它會(huì)去重啟相應(yīng)出異常的Actor,其狀態(tài)會(huì)被清除,所以此時(shí)Actor的狀態(tài)值應(yīng)該是0,測(cè)試不通過(guò)。
4.TestFour:拋出IllegalArgumentException
supervisor ! Props[Child] // create new child val child2 = expectMsgType[ActorRef] child2 ! 100 // 將狀態(tài)設(shè)為 100 watch(child) // have testActor watch “child” child ! new IllegalArgumentException // break it expectMsgPF() { case Terminated(`child`) => (println("the child stop")) } child2 ! Command("get",child2.path.name) expectMsg(100)
這里首先我們又創(chuàng)建了一個(gè)Child Actor為child2,并將它的狀態(tài)置為100,這里我們監(jiān)控前面創(chuàng)建的child1,然后給其發(fā)送一個(gè)IllegalArgumentException的消息,讓其拋出該異常,測(cè)試結(jié)果:
the child stop 測(cè)試通過(guò)
從結(jié)果中我們可以看出,child在拋出IllegalArgumentException后,會(huì)被其監(jiān)管著停止,但監(jiān)管者下的其他Actor還是正常工作。
5.TestFive:拋出一個(gè)自定義異常
watch(child2) child2 ! Command("get",child2.path.name) // verify it is alive expectMsg(100) supervisor ! Props[Child] // create new child val child3 = expectMsgType[ActorRef] child2 ! new Exception("CRASH") // escalate failure expectMsgPF() { case t @ Terminated(`child2`) if t.existenceConfirmed => ( println("the child2 stop") ) } child3 ! Command("get",child3.path.name) expectMsg(0)
這里首先我們又創(chuàng)建了一個(gè)Child Actor為child3,這里我們監(jiān)控前面創(chuàng)建的child2,然后給其發(fā)送一個(gè)Exception("CRASH")的消息,讓其拋出該異常,測(cè)試結(jié)果:
the child2 stop 測(cè)試不通過(guò)
很多人可能會(huì)疑惑為什么TestFour可以通過(guò),這里就通不過(guò)不了呢?因?yàn)檫@里錯(cuò)誤Actor拋出的異常其監(jiān)管者無(wú)法處理,只能將失敗上溯傳遞,而頂級(jí)actor的缺省策略是對(duì)所有的Exception情況(ActorInitializationException和ActorKilledException例外)進(jìn)行重啟. 由于缺省的重啟指令會(huì)停止所有的子actor,所以我們這里的child3也會(huì)被停止。導(dǎo)致測(cè)試不通過(guò)。當(dāng)然這里你也可以復(fù)寫默認(rèn)的重啟方法,比如:
override def preRestart(cause: Throwable, msg: Option[Any]) {}
這樣重啟相應(yīng)Actor時(shí)就不會(huì)停止其子級(jí)下的所有Actor了。
本文主要介紹了Actor系統(tǒng)中的監(jiān)管和容錯(cuò),這一部分內(nèi)容在Akka中也是很重要的,它與Actor的樹形組織結(jié)構(gòu)巧妙結(jié)合,本文大量參考了Akka官方文檔的相應(yīng)章節(jié),有興趣的同學(xué)可以點(diǎn)擊這里Akka docs。也可以下載我的示例程序,里面包含了一個(gè)官方的提供的容錯(cuò)示例。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/70014.html
摘要:模型作為中最核心的概念,所以在中的組織結(jié)構(gòu)也至關(guān)重要,本文主要介紹中系統(tǒng)。這里主要是演示可以根據(jù)配置文件的內(nèi)容去加載相應(yīng)的環(huán)境,并應(yīng)用到整個(gè)中,這對(duì)于我們配置環(huán)境來(lái)說(shuō)是非常方便的。路徑與地址熟悉類系統(tǒng)的同學(xué)應(yīng)該對(duì)路徑這個(gè)概念很熟悉了。 Actor模型作為Akka中最核心的概念,所以Actor在Akka中的組織結(jié)構(gòu)也至關(guān)重要,本文主要介紹Akka中Actor系統(tǒng)。 Actor系統(tǒng) Act...
摘要:原文鏈接解決了什么問題使用模型來(lái)克服傳統(tǒng)面向?qū)ο缶幊棠P偷木窒扌?,并?yīng)對(duì)高并發(fā)分布式系統(tǒng)所帶來(lái)的挑戰(zhàn)。在某些情況,這個(gè)問題可能會(huì)變得更糟糕,工作線程發(fā)生了錯(cuò)誤但是其自身卻無(wú)法恢復(fù)。 這段時(shí)間由于忙畢業(yè)前前后后的事情,拖更了很久,表示非常抱歉,回歸后的第一篇文章主要是看到了Akka最新文檔中寫的What problems does the actor model solve?,閱讀完后覺...
摘要:關(guān)于三者的一些概括總結(jié)離線分析框架,適合離線的復(fù)雜的大數(shù)據(jù)處理內(nèi)存計(jì)算框架,適合在線離線快速的大數(shù)據(jù)處理流式計(jì)算框架,適合在線的實(shí)時(shí)的大數(shù)據(jù)處理我是一個(gè)以架構(gòu)師為年之內(nèi)目標(biāo)的小小白。 整理自《架構(gòu)解密從分布式到微服務(wù)》第七章——聊聊分布式計(jì)算.做了相應(yīng)補(bǔ)充和修改。 [TOC] 前言 不管是網(wǎng)絡(luò)、內(nèi)存、還是存儲(chǔ)的分布式,它們最終目的都是為了實(shí)現(xiàn)計(jì)算的分布式:數(shù)據(jù)在各個(gè)計(jì)算機(jī)節(jié)點(diǎn)上流動(dòng),同...
摘要:是一個(gè)構(gòu)建在上,基于模型的的并發(fā)框架,為構(gòu)建伸縮性強(qiáng),有彈性的響應(yīng)式并發(fā)應(yīng)用提高更好的平臺(tái)。上述例子中的信件就相當(dāng)于中的消息,與之間只能通過(guò)消息通信。當(dāng)然模型比這要復(fù)雜的多,這里主要是簡(jiǎn)潔的闡述一下模型的概念。模型的出現(xiàn)解決了這個(gè)問題。 Akka是一個(gè)構(gòu)建在JVM上,基于Actor模型的的并發(fā)框架,為構(gòu)建伸縮性強(qiáng),有彈性的響應(yīng)式并發(fā)應(yīng)用提高更好的平臺(tái)。本文主要是個(gè)人對(duì)Akka的學(xué)習(xí)和應(yīng)...
閱讀 559·2023-04-25 17:26
閱讀 1561·2021-08-05 09:58
閱讀 2019·2019-08-30 13:17
閱讀 1015·2019-08-28 17:52
閱讀 1127·2019-08-26 18:27
閱讀 1467·2019-08-26 14:05
閱讀 3682·2019-08-26 14:05
閱讀 1684·2019-08-26 10:45