Akka笔记–演员监督– 8

故障更像是分布式系统中的功能。 借助Akka的容错崩溃模型,您可以在业务逻辑和故障处理逻辑(监督逻辑)之间实现清晰的分离。 只需很少的努力。 太神奇了 这是我们现在讨论的主题。

演员监督

假设有一个方法调用堆栈,而堆栈中最顶层的方法将引发异常。 堆栈中的方法可以做什么?

  1. 可以捕获并处理异常以进行恢复
  2. 可能会捕获到异常,将其记录下来并保持安静。
  3. 堆栈下方的方法还可以选择完全避开异常(或可能被捕获并重新抛出)

想象一下,直到主方法之前的所有方法都没有处理异常。 在这种情况下,程序会在写完针对控制台的异常的文章后退出。

Akka笔记–演员监督– 8_第1张图片

您还可以将相同的方案与产生线程进行比较。 如果子线程引发异常,并且runcall方法未处理该异常,则无论如何,该异常均应由父线程或主线程处理。 如果主线程不处理它,则系统退出。

让我们再做一次-如果使用context.actorOf创建的子Actor失败并带有Exception,则父actor(也称为主管)可能更愿意处理子actor的任何失败。 如果是这样,它可能更喜欢处理它并进行恢复( Restart / Resume )。 否则,将异常( EscalateEscalate给它的父级。 另外,它也可以Stop儿童演员-这就是那个孩子的故事的结尾。 我为什么要说父母(又名主管) 仅仅因为Akka的监督方式是父母监督 -这意味着只有演员的创作者才能监督他们。

而已 !! 我们几乎涵盖了所有监管Directives (可以对失败采取什么措施)。

策略

啊,我忘了提到这一点了:您已经知道Akka Actor可以创建子代,并且他们可以根据需要创建任意数量的子代。

现在,考虑两种情况:

1. OneForOne策略

您的Actor会产生多个子actor,并且这些子actor中的每一个都连接到不同的数据源。 假设您正在运行的应用程序可以将英语单词翻译成多种语言。

Akka笔记–演员监督– 8_第2张图片

假设一个儿童演员失败了,您可以跳过最终结果中的结果,您想做什么? 关闭服务? 不,您可能只想重新启动/停止该儿童演员。 是不是 现在,在Akka监管策略术语中称为OneForOneStrategy –如果一个演员失败了,就一个人处理。

根据业务异常,您可能希望对不同的异常做出不同的反应( StopRestartEscalateResume )。 要配置自己的策略,只需覆盖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〜参与者是合乎逻辑的。

Akka笔记–演员监督– 8_第3张图片

为什么在上一行中说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

  }
  ...
  ...

指令

AllForOneStrategyOneForOneStrategy的构造函数都接受一个称为DeciderPartialFunction[Throwable,Directive] ,将Throwable映射到一个Directive如您在此处看到的:

case _: MajorUnRecoverableException => Stop

有简单的只有4种指令- StopResumeEscalateRestart

停止

子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。 但是,请考虑以下事项:

  1. ActorKilledException只会传播给主管。 我们在DeathWatch中看到的该Actor的生命周期观察者死亡 观察者 呢 ? 直到演员被Stopped ,观察者才知道什么。
  2. 向演员发送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时,它会处理其receiveTerminated消息。 如果没有,该怎么办? 您会收到DeathPactException
Akka笔记–演员监督– 8_第4张图片

该代码表明,监事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.异常=>重新启动

对于所有其他异常,默认DirectiveRestart Actor。 检查以下应用程序。 仅为了证明Actor已重新启动, OtherExceptionParentActor会使子级引发异常并立即发送消息。 该消息到达邮箱,并且子actor重新启动时,该消息将得到处理。 不错!

Akka笔记–演员监督– 8_第5张图片

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")
  }

}

日志记录

该程序的日志非常简洁。

  1. 引发异常。 我们看到了痕迹
  2. 子级重新启动– Stop和Start被调用(我们将很快看到preRestartpostRestart回调)
  3. 重新启动之前发送给子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 StopRestart示例。 现在,让我们快速看一下Escalate

Resume只忽略该异常,并继续处理邮箱中的下一条消息。 这更像是捕获异常,而对此不执行任何操作。 很棒的东西,但是在那儿谈论不多。

逐步升级通常意味着异常情况很关键,直接主管将无法处理该异常情况。 因此,它要求主管提供帮助。 让我们举个例子。

考虑三个Actor – EscalateExceptionTopLevelActorEscalateExceptionParentActorEscalateExceptionChildActor 如果子actor抛出异常,并且如果父actor无法处理该异常,则它可以将其Escalate为顶级Actor。 顶级演员可以选择对任何指令做出反应。 在我们的示例中,我们只是Stop Stop将停止直接子级(即EscalateExceptionParentActor )。 如您所知,当在Actor上执行Stop时,其所有子级也会在Actor本身停止之前被停止。

Akka笔记–演员监督– 8_第6张图片

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")
  }
}

日志记录

从日志中可以看到,

  1. 子演员抛出异常。
  2. 直接主管( EscalateExceptionParentActor )将该异常升级为主管( EscalateExceptionTopLevelActor
  3. EscalateExceptionTopLevelActor的结果指令是停止Actor。 按照顺序,子演员将首先停止。
  4. 父演员接下来将停止(仅在通知观察者之后)
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

你可能感兴趣的:(堆栈,java,log4j,exception,log4net)