Akka官方文档2.5.17(三)——监督和监控

本文深入探讨了Akka框架中Actor模型的监督机制,包括不同类型的监督策略、重启与生命周期监控的意义,以及如何使用BackoffSupervisor模式进行延迟重启。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

监督意味着什么

顶级监督者

/user:Guardian Actor

/system:System Guardian

/:Root Guardian

重启意味着什么

生命周期监控意味着什么

使用BackoffSupervisor模式延迟重启

OneForOneStrategy和AllForOneStrategy


监督意味着什么

正如前面Actor系统所描述的一样,监督描述了Actor之间的依赖关系:监督者将任务委托给子Actor,所以必须对它们的失败作出响应。当一个子Actor侦测到错误(比如抛出一个异常),它会暂停(挂起)自己及其所有的子Actor,然后给监督者发送一条消息,表明发生了错误。根据被监督的工作的性质以及错误的性质,监督者有以下4种处理策略:

  1. 恢复子Actor,并保持其内部的状态
  2. 重启子Actor,清除其内部的状态
  3. 永久停止子Actor
  4. 向上传递错误

将每一个Actor视为监督层次结构的一部分是重要的,这解释了第4种处理策略的存在(作为一个监督者,它同时也是另一个监督者的子Actor),并且也对前三种策略有影响。恢复一个Actor及其所有子Actor重启一个Actor及其所有子Actor同样停止一个Actor意味着停止它的所有子Actor。值得注意的是,Actor生命周期preRestart方法的默认实现是在重启之前终止它所有的子Actor,这种默认机制可以在实现时进行重写;这种递归的重启适用于该方法执行过后留下的所有子Actor。

每个监督者都可以配置一个函数,将所有的错误原因(比如异常)转换为上面的四种策略之一。值得注意的是,这个函数不会把失败的Actor的identity作为输入。很容易想象这种结构可能不够灵活,比如:希望将不同的策略运用到不同的子Actor上。所以理解监督采用递归的错误处理结构是至关重要的。如果你尝试在某一层做太多,那将会很难推敲,因此在这种情况下推荐增加一层监督者。

Akka实现了一种称为“父类监督”的形式。Actors只能由其他Actors创建——其中顶层的Actors由Actor系统提供,每一个新创建的Actor都受其父Actor监督。这种限制使得Actor监督层次的形成是隐含的,并鼓励合理的设计决策。值得指出的是,这保证了Actor不会被孤立或者从系统外部连接到监督者。此外,这为Actor应用程序产生了一个自然且干净的关闭过程。

警告

与监督相关的父子通信是由特殊的系统消息产生,这些消息拥有自己的邮箱,而不是用户消息的邮箱。这意味着监督相关的事件相对于普通消息是没有确定性顺序的。通常情况下,用户不能影响普通消息和错误通知的顺序。

顶级监督者

一个Actor系统在创建中创建了至少三个Actor,如上图所示。

/user:Guardian Actor

可能与之交互最多的Actor就是用户创建的所有Actor的父Actor,名字叫做“/user”,通过使用system.actorOf()创建的Actor都是它的子Actor。这意味着当它终止时,所有的普通Actor都会被关闭。这也意味着它的监督策略决定了顶级的用户定义的Actor的监督方式。从Akka2.1开始,可以通过akka.actor.guardian-spervisor-strategy进行配置,该策略采用SupervisorStrategyConfigurator的完全限定名。当它向上传递错误时,Root Guardian的回应将是关闭它,这实际上也会关闭整个Actor系统。

/system:System Guardian

这个特殊的guardian的引入是为了实现有序的关闭,使得logging保持活动而所有普通的Actor被关闭,尽管logging本身也是由Actors实现的。这是通过让System Guardian监控User Guardian,并在接收到终止消息时启动自己的关闭策略来实现的。顶级的系统Actors使用某种策略进行监督,该策略在接收到所有Exception类型时(除了ActorInitializationExceptionActorKilledException),都会无限重启。而所有其他的Throwable则会向上传递,从而导致整个Actor系统的关闭。

/:Root Guardian

Root Guardian是所有被称为“顶层”Actor的父Actor,并且使用SupervisorStrategy.stoppingStrategy监督所有的特殊Actors。它的目的是终止任何抛出Exception的子Actor,而所有其他的Throwable则会向上传递,不过传递给谁?因为每一个真实的Actor都会有一个监督者,而root guardian的监督者不可能是一个真实的Actor。这意味着它“out of the bubble”,被称为"bubble-walker"。这是一个假的ActorRef,它实际上会在遇到麻烦时停掉其子Actor,并在Root Guardian完全停止后(所有子Actor递归停止)后立即将Actor系统的isTerminated状态设置为true。

重启意味着什么

当一个Actor在处理特定的消息失败时,失败的原因分为三类:

  • 接收到指定消息的系统(即编程)错误
  • 处理消息期间使用到的某些外部资源的故障
  • Actor内部的错误状态

除非错误被明确的识别,否则不能排除第三个原因,这导致得出了需要清除内部状态的结论。如果监督者认定它的其他子Actor或者它本身不会受到该错误的影响——比如有意识的运用了error kernel pattern——所以最好重启子Actor。这通过重新创建一个底层的Actor并替换掉ActorRef中原来的错误Actor来实现;能够这样做的原因是因为将Actor封装在了特殊的引用当中。然后新的Actor继续处理邮箱中的消息,这意味着重启Actor对于外部是不可见的。有一个值得注意的点是,它不会重复处理发生故障时的消息

重启时的事件顺序如下:

  1. 暂停(挂起)Actor,这意味着它不会处理普通消息,直到恢复,并递归的挂起所有子Actor
  2. 调用旧Actor实例的preRestart方法(默认是发送终止请求给所有子Actor),然后调用postStop方法
  3. 等待在preRestart期间被要求停止(使用context.stop())的所有Actor真正被停掉,这像所有其他Actor操作一样,是非阻塞的,来自最后一个被停掉的子Actor的通知会使得进入到下一步
  4. 通过调用最初提供的工厂方法创建新的Actor
  5. 调用新的Actor对象的postRestart(默认是调用preStart方法)
  6. 发送重启请求给所有没有在3中停掉的Actor;重启子Actor的过程是递归的,从步骤2开始
  7. 恢复Actor

生命周期监控意味着什么

note

生命周期监控通常被称为DeathWatch

与上述父子Actor的特殊关系相反,每个Actor可以监控其他Actor。因为Actor从创建到完全建立以及重启对于除监督者外的其他对象是不可见的,所以能用于监控的唯一状态是从活动到终止。因此,监控是用于将两个Actor绑定起来,使得可以对一个Actor的终止作出反应。这与对错误作出反应的监督相反。

生命周期监控是通过接收一个Terminated消息来实现的,其默认行为是在没有另外处理的情况下抛出一个DeathPactException。为了侦听Terminated消息,需要调用ActorContext.watch(actorRef)。停止侦听,则调用ActorContext.unwatch(actorRef)。一个重要的特性是无论监控的请求是在目标Actor的终止前或终止后,消息都将会被传递。也即,即使在注册时目标Actor已经消亡,侦听Actor也会收到消息

当监督者不能重启并想终止掉子Actor,监控机制就显得特别有用。比如:在Actor初始化期间出错。在这种情况下,它应该监控这些子Actor然后重新创建它们或者在一段时间后重试。

另一个常见的使用情况是,当请求不到外部资源时,一个Actor应该失败。如果第三方通过context.stop(actorRef)方法或者发送PoisonPill终止某个Actor时,它的监督者也会被影响。

使用BackoffSupervisor模式延迟重启

作为内置模块提供的akka.pattern.BackoffSupervisor实现了所谓的指数back-off监督策略,当一个子Actor失败时重启它,每次重启之间的时间延迟增加。

这个模式在因为外部某个资源不可用而导致Actor启动失败时特别有效,我们需要给它一段时间重启。其中一个有用的主要的示例是PersistentActor因为持久化错误而失败,这表明数据库可能宕机或者过载。在这种情况下, 在persistent actor启动之前,给它多一点时间恢复是有意义的。

失败可以用两种不同的方式表示; Actor停止或崩溃。

下面的Scala片段展示了如何创建一个back-off监督者,它将在因为失败而停止后启动给定的echo Actor,增加3,6,12,24和最后30秒的间隔:

val childProps = Props(classOf[EchoActor])

val supervisor = BackoffSupervisor.props(
  Backoff.onStop(
    childProps,
    childName = "myEcho",
    minBackoff = 3.seconds,
    maxBackoff = 30.seconds,
    randomFactor = 0.2, // adds 20% "noise" to vary the intervals slightly
    maxNrOfRetries = -1
  ))

system.actorOf(supervisor, name = "echoSupervisor")

强烈建议使用randomFactor为back-off的间隔增加一些额外的方差,以避免多个Actor在相同的时间点重启。比如,由于共享同一个资源(比如数据库),数据库宕机时,Actor也会停止,然后在相同的时间间隔内重新启动。通过向重启的间隔增加额外的随机性,Actor会在稍微不同的时间点启动,从而避免大量的流量冲击刚刚恢复的共享数据库或者其它共享的资源。

akka.pattern.BackoffSupervisor Actor同样可以配置当Actor崩溃,并且监督策略决定应该重启该Actor时在一定的延迟后重启该Actor。

下面的Scala片段展示了如何创建一个back-off监督者,它将在由于某些异常而导致崩溃后启动给定的echo Actor,增加3,6,12,24和最后30秒的间隔:

val childProps = Props(classOf[EchoActor])

val supervisor = BackoffSupervisor.props(
  Backoff.onFailure(
    childProps,
    childName = "myEcho",
    minBackoff = 3.seconds,
    maxBackoff = 30.seconds,
    randomFactor = 0.2, // adds 20% "noise" to vary the intervals slightly
    maxNrOfRetries = -1
  ))

system.actorOf(supervisor, name = "echoSupervisor")

akka.pattern.BackoffOptions可用于自定义back-off监督者Actor的行为,下面是一些示例:
 

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

上面的代码设置了一个back-off监督者,它要求子Actor在成功处理消息时向其父节点发送akka.pattern.BackoffSupervisor.Reset消息,重置back-off。 它还使用默认停止策略,任何异常都会导致子Actor停止。

val supervisor = BackoffSupervisor.props(
  Backoff.onFailure(
    childProps,
    childName = "myEcho",
    minBackoff = 3.seconds,
    maxBackoff = 30.seconds,
    randomFactor = 0.2, // adds 20% "noise" to vary the intervals slightly
    maxNrOfRetries = -1
  ).withAutoReset(10.seconds) // reset if the child does not throw any errors within 10 seconds
    .withSupervisorStrategy(
      OneForOneStrategy() {
        case _: MyException ⇒ SupervisorStrategy.Restart
        case _              ⇒ SupervisorStrategy.Escalate
      }))

如果抛出了MyException,则会设置了一个back-off监督者进行重启,其他的异常则会向上传递。 如果在10秒内没有抛出任何错误,则自动重置back-off。

OneForOneStrategy和AllForOneStrategy

Akka有两类监督策略:OneForOneStrategy和AllForOneStrategy。两者都配置了从异常类型到监督指令的映射(见上文),并且限制了在终止前允许失败的次数。它们之间的区别在于前者仅将获得的指令应用于失败的子节点,而后者也将其应用于所有兄弟节点。 通常,应该使用OneForOneStrategy,如果没有显式的指定,同时它也是默认值。

AllForOneStrategy适用于子Actor之间存在紧密依赖关系的情况,一个子Actor的失败会影响其他子Actor的功能,也即它们是密不可分的。由于重启不会清除邮箱,因此通常最好在故障时终止子节点并从监督者那明确的重新创建它们(通过监控子节点的生命周期),否则你必须确保任何一个Actor不会在重启之前接收到消息而在重启后对其进行处理。

通常,当采用AllForOneStrategy时,停止一个子Actor不会自动停止其他子Actor;这可以通过监控他们的生命周期来完成:如果监督者没有处理Terminated消息,那么它会抛出DeathPactException并重启(取决于它的监督者),而默认的preRestart方法会终止所有子Actor。当然,这也可以显式的进行处理。

请注意,在AllForOneStrategy中创建的临时Actor导致的失败,该临时Actor抛出的错误也将会影响到其它永久的Actor。如果这不是你想要的,创建一个中间监督者,这可以为worker声明一个大小为1的路由来完成,参见路由

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值