故障更像是分布式系统中的功能。 借助Akka的容错崩溃模型,您可以在业务逻辑和故障处理逻辑(监督逻辑)之间实现清晰的分离。 只需很少的努力。 太神奇了 这是我们现在讨论的主题。
演员监督
假设有一个方法调用堆栈,而堆栈中最顶层的方法将引发异常。 堆栈中的方法可以做什么?
- 可以捕获并处理异常以进行恢复
- 可能会捕获到异常,将其记录下来并保持安静。
- 堆栈下方的方法还可以选择完全避开异常(或可能被捕获并重新抛出)
想象一下,直到主方法之前的所有方法都没有处理异常。 在这种情况下,程序会在写完针对控制台的异常的文章后退出。
您还可以将相同的方案与产生线程进行比较。 如果子线程引发异常,并且run
或call
方法未处理该异常,则无论如何,该异常均应由父线程或主线程处理。 如果主线程不处理它,则系统退出。
让我们再做一次-如果使用context.actorOf
创建的子Actor失败并带有Exception,则父actor(也称为主管)可能更愿意处理子actor的任何失败。 如果是这样,它可能更喜欢处理它并进行恢复( Restart
/ Resume
)。 否则,将异常( Escalate
) Escalate
给它的父级。 另外,它也可以Stop
儿童演员-这就是那个孩子的故事的结尾。 我为什么要说父母(又名主管) ? 仅仅因为Akka的监督方式是父母监督 -这意味着只有演员的创作者才能监督他们。
而已 !! 我们几乎涵盖了所有监管Directives
(可以对失败采取什么措施)。
策略
啊,我忘了提到这一点了:您已经知道Akka Actor可以创建子代,并且他们可以根据需要创建任意数量的子代。
现在,考虑两种情况:
1. OneForOne策略
您的Actor会产生多个子actor,并且这些子actor中的每一个都连接到不同的数据源。 假设您正在运行的应用程序可以将英语单词翻译成多种语言。
假设一个儿童演员失败了,您可以跳过最终结果中的结果,您想做什么? 关闭服务? 不,您可能只想重新启动/停止该儿童演员。 是不是 现在,在Akka监管策略术语中称为OneForOneStrategy
–如果一个演员失败了,就一个人处理。
根据业务异常,您可能希望对不同的异常做出不同的反应( Stop
, Restart
, Escalate
, Resume
)。 要配置自己的策略,只需覆盖Actor类中的supervisorStrategy
。
OneForOneStrategy
的示例声明为
import akka.actor.Actor
import akka.actor.ActorLogging
import akka.actor.OneForOneStrategy
import akka.actor.SupervisorStrategy.Stop
class TeacherActorOneForOne extends Actor with ActorLogging {
...
...
override val supervisorStrategy=OneForOneStrategy() {
case _: MinorRecoverableException => Restart
case _: Exception => Stop
}
...
...
2. AllForOne策略
假设您正在执行外部排序 (又一个示例来证明我的创造力很烂!),并且您的每个块都由不同的Actor处理。 突然,一个Actor未能引发异常。 继续处理其余的块没有任何意义,因为最终结果将不正确。 因此, Stop
〜ALL〜参与者是合乎逻辑的。
为什么在上一行中说Stop
而不是Restart
? 因为对于这种用例来说, Restart
也没有任何意义,因为考虑到这些Actor中每个角色的邮箱都不会在重新启动时清除。 因此,如果我们重新启动,其余的块仍将被处理。 那不是我们想要的。 用闪亮的新邮箱重新创建Actor是正确的方法。
同样,就像OneForOneStrategy
,你只覆盖supervisorStrategy
与实现AllForOneStrategy
例子是
import akka.actor.{Actor, ActorLogging}
import akka.actor.AllForOneStrategy
import akka.actor.SupervisorStrategy.Escalate
import akka.actor.SupervisorStrategy.Stop
class TeacherActorAllForOne extends Actor with ActorLogging {
...
override val supervisorStrategy = AllForOneStrategy() {
case _: MajorUnRecoverableException => Stop
case _: Exception => Escalate
}
...
...
指令
AllForOneStrategy
和OneForOneStrategy
的构造函数都接受一个称为Decider
的PartialFunction[Throwable,Directive]
,将Throwable
映射到一个Directive
如您在此处看到的:
case _: MajorUnRecoverableException => Stop
有简单的只有4种指令- Stop
, Resume
, Escalate
和Restart
停止
子actor在发生异常的情况下会停止,并且发送给被停止actor的任何消息显然都会进入deadLetters队列。
恢复
子参与者仅忽略引发异常的消息,然后继续处理队列中的其余消息。
重新开始
子演员停止,并初始化一个全新的演员。 邮箱中其余消息的处理继续。 由于将相同的ActorRef附加到新的Actor上,因此世界其他地方都不知道发生了这种情况。
升级主管避开故障并让其主管处理异常。
默认策略
如果我们的演员没有指定任何策略但创建了子演员,该怎么办。 他们如何处理? Actor
特性中声明了一个默认策略,该策略如下所示:
override val supervisorStrategy=OneForOneStrategy() {
case _: ActorInitializationException=> Stop
case _: ActorKilledException => Stop
case _: DeathPactException => Stop
case _: Exception => Restart
}
因此,实质上,默认策略处理四种情况:
1. ActorInitializationException =>停止
当Actor无法初始化时,它将引发ActorInitializationException
。 演员将被停止。 让我们通过在preStart
回调中引发异常来模拟它:
package me.rerun.akkanotes.supervision
import akka.actor.{ActorSystem, Props}
import me.rerun.akkanotes.protocols.TeacherProtocol.QuoteRequest
import akka.actor.Actor
import akka.actor.ActorLogging
object ActorInitializationExceptionApp extends App{
val actorSystem=ActorSystem("ActorInitializationException")
val actor=actorSystem.actorOf(Props[ActorInitializationExceptionActor], "initializationExceptionActor")
actor!"someMessageThatWillGoToDeadLetter"
}
class ActorInitializationExceptionActor extends Actor with ActorLogging{
override def preStart={
throw new Exception("Some random exception")
}
def receive={
case _=>
}
}
运行ActorInitializationExceptionApp
会生成一个ActorInitializationException
(duh !!),然后将所有消息移到deadLetters
Actor的消息队列中:
日志记录
[ERROR] [11/10/2014 16:08:46.569] [ActorInitializationException-akka.actor.default-dispatcher-2] [akka://ActorInitializationException/user/initializationExceptionActor] Some random exception
akka.actor.ActorInitializationException: exception during creation
at akka.actor.ActorInitializationException$.apply(Actor.scala:164)
...
...
Caused by: java.lang.Exception: Some random exception
at me.rerun.akkanotes.supervision.ActorInitializationExceptionActor.preStart(ActorInitializationExceptionApp.scala:17)
...
...
[INFO] [11/10/2014 16:08:46.581] [ActorInitializationException-akka.actor.default-dispatcher-4] [akka://ActorInitializationException/user/initializationExceptionActor] Message from Actor[akka://ActorInitializationException/deadLetters] to Actor[akka://ActorInitializationException/user/initializationExceptionActor#-1290470495] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
2. ACTORKILLEDEXCEPTION =>停止
当使用Kill
消息杀死Actor时,它将抛出ActorKilledException
。 如果默认策略将引发子Actor异常,它将停止。 起初,似乎没有必要停止已经被杀死的Actor。 但是,请考虑以下事项:
-
ActorKilledException
只会传播给主管。 我们在DeathWatch中看到的该Actor的生命周期观察者或死亡 观察者 呢 ? 直到演员被Stopped
,观察者才知道什么。 - 向演员发送
Kill
只会影响主管所知道的那个特定演员。 但是,使用Stop
处理会暂停该Actor的邮箱,暂停其子Actor的邮箱,停止其子Actor,将Terminated
发送给所有子Actor观察者,将Terminated
发送给所有直接失败的Actor的观察者并最终停止演员本身。 (哇,真棒!)
package me.rerun.akkanotes.supervision
import akka.actor.{ActorSystem, Props}
import me.rerun.akkanotes.protocols.TeacherProtocol.QuoteRequest
import akka.actor.Actor
import akka.actor.ActorLogging
import akka.actor.Kill
object ActorKilledExceptionApp extends App{
val actorSystem=ActorSystem("ActorKilledExceptionSystem")
val actor=actorSystem.actorOf(Props[ActorKilledExceptionActor])
actor!"something"
actor!Kill
actor!"something else that falls into dead letter queue"
}
class ActorKilledExceptionActor extends Actor with ActorLogging{
def receive={
case message:String=> log.info (message)
}
}
日志记录
日志只是说一旦ActorKilledException
出现,主管就会停止该actor,然后消息进入deadLetters
队列。
INFO m.r.a.s.ActorKilledExceptionActor - something
ERROR akka.actor.OneForOneStrategy - Kill
akka.actor.ActorKilledException: Kill
INFO akka.actor.RepointableActorRef - Message from Actor[akka://ActorKilledExceptionSystem/deadLetters] to Actor[akka://ActorKilledExceptionSystem/user/$a#-1569063462] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
3. DeathPactException =>停止
在DeathWatch中 ,您知道当Actor 监视子Actor时,它会处理其receive
的Terminated
消息。 如果没有,该怎么办? 您会收到DeathPactException
该代码表明,监事watches
的儿童演员创作之后,但不处理Terminated
从孩子的消息。
package me.rerun.akkanotes.supervision
import akka.actor.{ActorSystem, Props}
import me.rerun.akkanotes.protocols.TeacherProtocol.QuoteRequest
import akka.actor.Actor
import akka.actor.ActorLogging
import akka.actor.Kill
import akka.actor.PoisonPill
import akka.actor.Terminated
object DeathPactExceptionApp extends App{
val actorSystem=ActorSystem("DeathPactExceptionSystem")
val actor=actorSystem.actorOf(Props[DeathPactExceptionParentActor])
actor!"create_child" //Throws DeathPactException
Thread.sleep(2000) //Wait until Stopped
actor!"someMessage" //Message goes to DeadLetters
}
class DeathPactExceptionParentActor extends Actor with ActorLogging{
def receive={
case "create_child"=> {
log.info ("creating child")
val child=context.actorOf(Props[DeathPactExceptionChildActor])
context.watch(child) //Watches but doesnt handle terminated message. Throwing DeathPactException here.
child!"stop"
}
case "someMessage" => log.info ("some message")
//Doesnt handle terminated message
//case Terminated(_) =>
}
}
class DeathPactExceptionChildActor extends Actor with ActorLogging{
def receive={
case "stop"=> {
log.info ("Actor going to stop and announce that it's terminated")
self!PoisonPill
}
}
}
日志记录
日志告诉我们DeathPactException
进入,主管停止该DeathPactException
,然后消息进入deadLetters
队列
INFO m.r.a.s.DeathPactExceptionParentActor - creating child
INFO m.r.a.s.DeathPactExceptionChildActor - Actor going to stop and announce that it's terminated
ERROR akka.actor.OneForOneStrategy - Monitored actor [Actor[akka://DeathPactExceptionSystem/user/$a/$a#-695506341]] terminated
akka.actor.DeathPactException: Monitored actor [Actor[akka://DeathPactExceptionSystem/user/$a/$a#-695506341]] terminated
INFO akka.actor.RepointableActorRef - Message from Actor[akka://DeathPactExceptionSystem/deadLetters] to Actor[akka://DeathPactExceptionSystem/user/$a#-1452955980] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
4.异常=>重新启动
对于所有其他异常,默认Directive
是Restart
Actor。 检查以下应用程序。 仅为了证明Actor已重新启动, OtherExceptionParentActor
会使子级引发异常并立即发送消息。 该消息到达邮箱,并且子actor重新启动时,该消息将得到处理。 不错!
package me.rerun.akkanotes.supervision
import akka.actor.Actor
import akka.actor.ActorLogging
import akka.actor.ActorSystem
import akka.actor.OneForOneStrategy
import akka.actor.Props
import akka.actor.SupervisorStrategy.Stop
object OtherExceptionApp extends App{
val actorSystem=ActorSystem("OtherExceptionSystem")
val actor=actorSystem.actorOf(Props[OtherExceptionParentActor])
actor!"create_child"
}
class OtherExceptionParentActor extends Actor with ActorLogging{
def receive={
case "create_child"=> {
log.info ("creating child")
val child=context.actorOf(Props[OtherExceptionChildActor])
child!"throwSomeException"
child!"someMessage"
}
}
}
class OtherExceptionChildActor extends akka.actor.Actor with ActorLogging{
override def preStart={
log.info ("Starting Child Actor")
}
def receive={
case "throwSomeException"=> {
throw new Exception ("I'm getting thrown for no reason")
}
case "someMessage" => log.info ("Restarted and printing some Message")
}
override def postStop={
log.info ("Stopping Child Actor")
}
}
日志记录
该程序的日志非常简洁。
- 引发异常。 我们看到了痕迹
- 子级重新启动– Stop和Start被调用(我们将很快看到
preRestart
和postRestart
回调) - 重新启动之前发送给子actor的消息得到处理。
INFO m.r.a.s.OtherExceptionParentActor - creating child
INFO m.r.a.s.OtherExceptionChildActor - Starting Child Actor
ERROR akka.actor.OneForOneStrategy - I'm getting thrown for no reason
java.lang.Exception: I'm getting thrown for no reason
at me.rerun.akkanotes.supervision.OtherExceptionChildActor$$anonfun$receive$2.applyOrElse(OtherExceptionApp.scala:39) ~[classes/:na]
at akka.actor.Actor$class.aroundReceive(Actor.scala:465) ~[akka-actor_2.11-2.3.4.jar:na]
...
...
INFO m.r.a.s.OtherExceptionChildActor - Stopping Child Actor
INFO m.r.a.s.OtherExceptionChildActor - Starting Child Actor
INFO m.r.a.s.OtherExceptionChildActor - Restarted and printing some Message
升级并恢复
我们看到了通过defaultStrategy
Stop
和Restart
示例。 现在,让我们快速看一下Escalate
。
Resume
只忽略该异常,并继续处理邮箱中的下一条消息。 这更像是捕获异常,而对此不执行任何操作。 很棒的东西,但是在那儿谈论不多。
逐步升级通常意味着异常情况很关键,直接主管将无法处理该异常情况。 因此,它要求主管提供帮助。 让我们举个例子。
考虑三个Actor – EscalateExceptionTopLevelActor
, EscalateExceptionParentActor
和EscalateExceptionChildActor
。 如果子actor抛出异常,并且如果父actor无法处理该异常,则它可以将其Escalate
为顶级Actor。 顶级演员可以选择对任何指令做出反应。 在我们的示例中,我们只是Stop
。 Stop
将停止直接子级(即EscalateExceptionParentActor
)。 如您所知,当在Actor上执行Stop
时,其所有子级也会在Actor本身停止之前被停止。
package me.rerun.akkanotes.supervision
import akka.actor.Actor
import akka.actor.ActorLogging
import akka.actor.ActorSystem
import akka.actor.OneForOneStrategy
import akka.actor.Props
import akka.actor.SupervisorStrategy.Escalate
import akka.actor.SupervisorStrategy.Stop
import akka.actor.actorRef2Scala
object EscalateExceptionApp extends App {
val actorSystem = ActorSystem("EscalateExceptionSystem")
val actor = actorSystem.actorOf(Props[EscalateExceptionTopLevelActor], "topLevelActor")
actor ! "create_parent"
}
class EscalateExceptionTopLevelActor extends Actor with ActorLogging {
override val supervisorStrategy = OneForOneStrategy() {
case _: Exception => {
log.info("The exception from the Child is now handled by the Top level Actor. Stopping Parent Actor and its children.")
Stop //Stop will stop the Actor that threw this Exception and all its children
}
}
def receive = {
case "create_parent" => {
log.info("creating parent")
val parent = context.actorOf(Props[EscalateExceptionParentActor], "parentActor")
parent ! "create_child" //Sending message to next level
}
}
}
class EscalateExceptionParentActor extends Actor with ActorLogging {
override def preStart={
log.info ("Parent Actor started")
}
override val supervisorStrategy = OneForOneStrategy() {
case _: Exception => {
log.info("The exception is ducked by the Parent Actor. Escalating to TopLevel Actor")
Escalate
}
}
def receive = {
case "create_child" => {
log.info("creating child")
val child = context.actorOf(Props[EscalateExceptionChildActor], "childActor")
child ! "throwSomeException"
}
}
override def postStop = {
log.info("Stopping parent Actor")
}
}
class EscalateExceptionChildActor extends akka.actor.Actor with ActorLogging {
override def preStart={
log.info ("Child Actor started")
}
def receive = {
case "throwSomeException" => {
throw new Exception("I'm getting thrown for no reason.")
}
}
override def postStop = {
log.info("Stopping child Actor")
}
}
日志记录
从日志中可以看到,
- 子演员抛出异常。
- 直接主管(
EscalateExceptionParentActor
)将该异常升级为主管(EscalateExceptionTopLevelActor
) -
EscalateExceptionTopLevelActor
的结果指令是停止Actor。 按照顺序,子演员将首先停止。 - 父演员接下来将停止(仅在通知观察者之后)
INFO m.r.a.s.EscalateExceptionTopLevelActor - creating parent
INFO m.r.a.s.EscalateExceptionParentActor - Parent Actor started
INFO m.r.a.s.EscalateExceptionParentActor - creating child
INFO m.r.a.s.EscalateExceptionChildActor - Child Actor started
INFO m.r.a.s.EscalateExceptionParentActor - The exception is ducked by the Parent Actor. Escalating to TopLevel Actor
INFO m.r.a.s.EscalateExceptionTopLevelActor - The exception from the Child is now handled by the Top level Actor. Stopping Parent Actor and its children.
ERROR akka.actor.OneForOneStrategy - I'm getting thrown for no reason.
java.lang.Exception: I'm getting thrown for no reason.
at me.rerun.akkanotes.supervision.EscalateExceptionChildActor$$anonfun$receive$3.applyOrElse(EscalateExceptionApp.scala:71) ~[classes/:na]
...
...
INFO m.r.a.s.EscalateExceptionChildActor - Stopping child Actor
INFO m.r.a.s.EscalateExceptionParentActor - Stopping parent Actor
请注意,发出的任何指令仅适用于升级的直系子女。 假设,如果在preStart
发布了Restart
,则仅父级将被重新启动,并且其构造函数/ preStart
任何内容都将被执行。 如果在构造函数中创建了父级角色的子级,则也会创建它们。 但是,通过发给父级演员的消息创建的子代仍处于“已Terminated
状态。
琐事
实际上,您可以控制是否preStart
调用preStart
。 我们将在下一个小篇幅文章中对此进行介绍。 如果您好奇,只需看看Actor的postRestart
方法。
def postRestart(reason: Throwable): Unit = {
preStart()
}
码
和往常一样,代码在github上 。
(我的.gitignore
不适用于该项目,请立即安装。抱歉) 。
翻译自: https://www.javacodegeeks.com/2014/11/akka-notes-actor-supervision-8.html