在上一篇讨论中我们谈到了监管:在Akka中就是一种直属父子监管树结构,父级Actor负责处理直属子级Actor产生的异常。当时我们把BackoffSupervisor作为父子监管方式的其中一种。实际上BackoffSupervisor与定义了supervisorStrategy的Actor有所不同。我们应该把BackoffSupervisor看作是一个一体化的Actor。当然,它的实现方式还是由一对父子Actor组成。监管策略(SupervisorStrategy)是在BackoffSupervisor的内部实现的。从外表上BackoffSupervisor就像是一个Actor,运算逻辑是在子级Actor中定义的,所谓的父级Actor除监管之外没有任何其它功能,我们甚至没有地方定义父级Actor的功能,它的唯一功能是转发收到的信息给子级,是嵌入BackoffSupervisor里的。所以我们虽然发送消息给BackoffSupervisor,但实际上是在与它的子级交流。我们看看下面这个例子:

  1. package backoffSupervisorDemo
  2. import akka.actor._
  3. import akka.pattern._
  4. import backoffSupervisorDemo.InnerChild.TestMessage
  5.  
  6. import scala.concurrent.duration._
  7.  
  8. object InnerChild {
  9. case class TestMessage(msg: String)
  10. class ChildException extends Exception
  11.  
  12. def props = Props[InnerChild]
  13. }
  14. class InnerChild extends Actor with ActorLogging {
  15. import InnerChild._
  16. override def receive: Receive = {
  17. case TestMessage(msg) => //模拟子级功能
  18. log.info(s"Child received message: ${msg}")
  19. }
  20. }
  21. object Supervisor {
  22. def props: Props = { //在这里定义了监管策略和child Actor构建
  23. def decider: PartialFunction[Throwable, SupervisorStrategy.Directive] = {
  24. case _: InnerChild.ChildException => SupervisorStrategy.Restart
  25. }
  26.  
  27. val options = Backoff.onFailure(InnerChild.props, "innerChild", second, seconds, 0.0)
  28. .withManualReset
  29. .withSupervisorStrategy(
  30. OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds)(
  31. decider.orElse(SupervisorStrategy.defaultDecider)
  32. )
  33. )
  34. BackoffSupervisor.props(options)
  35. }
  36. }
  37. //注意:下面是Supervisor的父级,不是InnerChild的父级
  38. object ParentalActor {
  39. case class SendToSupervisor(msg: InnerChild.TestMessage)
  40. case class SendToInnerChild(msg: InnerChild.TestMessage)
  41. case class SendToChildSelection(msg: InnerChild.TestMessage)
  42. def props = Props[ParentalActor]
  43. }
  44. class ParentalActor extends Actor with ActorLogging {
  45. import ParentalActor._
  46. //在这里构建子级Actor supervisor
  47. val supervisor = context.actorOf(Supervisor.props,"supervisor")
  48. supervisor ! BackoffSupervisor.getCurrentChild //要求supervisor返回当前子级Actor
  49. var innerChild: Option[ActorRef] = None //返回的当前子级ActorRef
  50. val selectedChild = context.actorSelection("/user/parent/supervisor/innerChild")
  51. override def receive: Receive = {
  52. case BackoffSupervisor.CurrentChild(ref) => //收到子级Actor信息
  53. innerChild = ref
  54. case SendToSupervisor(msg) => supervisor ! msg
  55. case SendToChildSelection(msg) => selectedChild ! msg
  56. case SendToInnerChild(msg) => innerChild foreach(child => child ! msg)
  57. }
  58.  
  59. }
  60. object BackoffSupervisorDemo extends App {
  61. import ParentalActor._
  62. val testSystem = ActorSystem("testSystem")
  63. val parent = testSystem.actorOf(ParentalActor.props,"parent")
  64.  
  65. Thread.sleep() //wait for BackoffSupervisor.CurrentChild(ref) received
  66.  
  67. parent ! SendToSupervisor(TestMessage("Hello message 1 to supervisor"))
  68. parent ! SendToInnerChild(TestMessage("Hello message 2 to innerChild"))
  69. parent ! SendToChildSelection(TestMessage("Hello message 3 to selectedChild"))
  70.  
  71. scala.io.StdIn.readLine()
  72.  
  73. testSystem.terminate()
  74.  
  75. }

在上面的例子里我们分别向supervisor,innerChild,selectedChild发送消息。但所有消息都是由InnerChild响应的,如下:

  1. [INFO] [// ::48.167] [testSystem-akka.actor.default-dispatcher-] [akka://testSystem/user/parent/supervisor/innerChild] Child received message: Hello message 1 to supervisor
  2. [INFO] [// ::48.177] [testSystem-akka.actor.default-dispatcher-] [akka://testSystem/user/parent/supervisor/innerChild] Child received message: Hello message 2 to innerChild
  3. [INFO] [// ::48.179] [testSystem-akka.actor.default-dispatcher-] [akka://testSystem/user/parent/supervisor/innerChild] Child received message: Hello message 3 to selectedChild

上面我们向supervisor发送了一个BackoffSupervisor.GetCurrentChild消息用来获取子级Actor。BackoffSupervisor是这样处理下面几个特殊消息的:

  1. private[akka] trait HandleBackoff { this: Actor
  2. def childProps: Props
  3. def childName: String
  4. def reset: BackoffReset
  5.  
  6. var child: Option[ActorRef] = None
  7. var restartCount =
  8.  
  9. import BackoffSupervisor._
  10. import context.dispatcher
  11.  
  12. override def preStart(): Unit = startChild()
  13.  
  14. def startChild(): Unit = {
  15. if (child.isEmpty) {
  16. child = Some(context.watch(context.actorOf(childProps, childName)))
  17. }
  18. }
  19.  
  20. def handleBackoff: Receive = {
  21. case StartChild
  22. startChild()
  23. reset match {
  24. case AutoReset(resetBackoff)
  25. val _ = context.system.scheduler.scheduleOnce(resetBackoff, self, ResetRestartCount(restartCount))
  26. case _ // ignore
  27. }
  28.  
  29. case Reset
  30. reset match {
  31. case ManualReset restartCount =
  32. case msg unhandled(msg)
  33. }
  34.  
  35. case ResetRestartCount(current)
  36. if (current == restartCount) {
  37. restartCount =
  38. }
  39.  
  40. case GetRestartCount
  41. sender() ! RestartCount(restartCount)
  42.  
  43. case GetCurrentChild
  44. sender() ! CurrentChild(child)
  45.  
  46. case msg if child.contains(sender())
  47. // use the BackoffSupervisor as sender
  48. context.parent ! msg
  49.  
  50. case msg child match {
  51. case Some(c) c.forward(msg)
  52. case None context.system.deadLetters.forward(msg)
  53. }
  54. }
  55. }

在handleBackoff函数里可以找到这些消息的处理方式。

在构建上面例子里的Supervisor的Props时定义了监管策略(SupervisorStrategy)对InnerChild产生的异常ChildException进行Restart处理。我们调整一下InnerChild代码来随机产生一些异常:

  1. object InnerChild {
  2. case class TestMessage(msg: String)
  3. class ChildException(val errmsg: TestMessage) extends Exception
  4. object CException { //for pattern match of class with parameter
  5. def apply(msg: TestMessage) = new ChildException(msg)
  6. def unapply(cex: ChildException) = Some(cex.errmsg)
  7. }
  8. def props = Props[InnerChild]
  9. }
  10. class InnerChild extends Actor with ActorLogging {
  11. import InnerChild._
  12. context.parent ! BackoffSupervisor.Reset //reset backoff counts
  13. override def receive: Receive = {
  14. case TestMessage(msg) => //模拟子级功能
  15. if (Random.nextBoolean()) //任意产生异常
  16. throw new ChildException(TestMessage(msg))
  17. else
  18. log.info(s"Child received message: ${msg}")
  19. }
  20. }

我们用Random.nextBoolean来任意产生一些异常。注意:我们同时把ChildException改成了一个带参数的class,因为我们可能需要在重启之前获取造成异常的消息,如下:

  1. def decider: PartialFunction[Throwable, SupervisorStrategy.Directive] = {
  2. case InnerChild.CException(tmsg) =>
  3. println(s"Message causing exception: ${tmsg.msg}") //we can extract message here
  4. SupervisorStrategy.Restart
  5. }

所有信息发给supervisor就行了:

  1. class ParentalActor extends Actor with ActorLogging {
  2. import ParentalActor._
  3. //在这里构建子级Actor supervisor
  4. val supervisor = context.actorOf(Supervisor.props,"supervisor")
  5. override def receive: Receive = {
  6. case msg@ _ => supervisor ! msg
  7. }
  8.  
  9. }
  10. object BackoffSupervisorDemo extends App {
  11. import ParentalActor._
  12. import InnerChild._
  13. val testSystem = ActorSystem("testSystem")
  14. val parent = testSystem.actorOf(ParentalActor.props,"parent")
  15.  
  16. parent ! TestMessage("Hello message 1 to supervisor")
  17. parent ! TestMessage("Hello message 2 to supervisor")
  18. parent ! TestMessage("Hello message 3 to supervisor")
  19. parent ! TestMessage("Hello message 4 to supervisor")
  20. parent ! TestMessage("Hello message 5 to supervisor")
  21. parent ! TestMessage("Hello message 6 to supervisor")
  22.  
  23. scala.io.StdIn.readLine()
  24.  
  25. testSystem.terminate()
  26.  
  27. }

运行后发现在出现异常后所有消息都变成了DeadLetter:

  1. [INFO] [// ::11.689] [testSystem-akka.actor.default-dispatcher-] [akka://testSystem/user/parent/supervisor/innerChild] Message [backoffSupervisorDemo.InnerChild$TestMessage] from Actor[akka://testSystem/user/parent#2140150413] to Actor[akka://testSystem/user/parent/supervisor/innerChild#-1047097634] 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. ....

这也证明了BackoffSupervisor具有不同的Restart处理方式,好像是直接终止InnerChild而非正常的挂起,销毁了ActorRef和邮箱,所以在完成启动之前发给InnerChild的消息都被导入DeadLetter队列了。也就是说不但错过造成异常的消息,而是跳过了下面启动时间段内所有的消息。

下面我们来解决失踪消息的问题:首先是如何重新发送造成异常的消息,我们可以在监管策略中重启前发送:

  1. def decider: PartialFunction[Throwable, SupervisorStrategy.Directive] = {
  2. case InnerChild.CException(tmsg) =>
  3. println(s"Message causing exception: ${tmsg.msg}") //we can extract message here
  4. BackoffSupervisorDemo.sendToParent(tmsg) //resend message
  5. SupervisorStrategy.Restart
  6. }

在BackoffSupervisorDemo里先声明sendToParent函数:

  1. def sendToParent(msg: TestMessage) = parent ! msg

然后再想办法把DeadLetter捞出来。我们可以用Akka的eventStream来订阅DeadLetter类型消息:

  1. object DeadLetterMonitor {
  2. def props(parentRef: ActorRef) = Props(new DeadLetterMonitor(parentRef))
  3. }
  4. class DeadLetterMonitor(receiver: ActorRef) extends Actor with ActorLogging {
  5. import InnerChild._
  6. import context.dispatcher
  7. override def receive: Receive = {
  8. case DeadLetter(msg,sender,_) =>
  9. //wait till InnerChild finishes restart then resend
  10. context.system.scheduler.scheduleOnce( second,receiver,msg.asInstanceOf[TestMessage])
  11. }
  12. }
  13. object BackoffSupervisorDemo extends App {
  14. import ParentalActor._
  15. import InnerChild._
  16.  
  17. def sendToParent(msg: TestMessage) = parent ! msg
  18.  
  19. val testSystem = ActorSystem("testSystem")
  20. val parent = testSystem.actorOf(ParentalActor.props,"parent")
  21.  
  22. val deadLetterMonitor = testSystem.actorOf(DeadLetterMonitor.props(parent),"dlmonitor")
  23. testSystem.eventStream.subscribe(deadLetterMonitor,classOf[DeadLetter]) //listen to DeadLetter
  24.  
  25. parent ! TestMessage("Hello message 1 to supervisor")
  26. parent ! TestMessage("Hello message 2 to supervisor")
  27. parent ! TestMessage("Hello message 3 to supervisor")
  28. parent ! TestMessage("Hello message 4 to supervisor")
  29. parent ! TestMessage("Hello message 5 to supervisor")
  30. parent ! TestMessage("Hello message 6 to supervisor")
  31.  
  32. scala.io.StdIn.readLine()
  33.  
  34. testSystem.terminate()
  35.  
  36. }

试运算后显示InnerChild成功处理了所有6条消息。

下面是本次讨论的完整示范代码:

  1. package backoffSupervisorDemo
  2. import akka.actor._
  3. import akka.pattern._
  4. import scala.util.Random
  5.  
  6. import scala.concurrent.duration._
  7.  
  8. object InnerChild {
  9. case class TestMessage(msg: String)
  10. class ChildException(val errmsg: TestMessage) extends Exception
  11. object CException { //for pattern match of class with parameter
  12. def apply(msg: TestMessage) = new ChildException(msg)
  13. def unapply(cex: ChildException) = Some(cex.errmsg)
  14. }
  15. def props = Props[InnerChild]
  16. }
  17. class InnerChild extends Actor with ActorLogging {
  18. import InnerChild._
  19. context.parent ! BackoffSupervisor.Reset //reset backoff counts
  20. override def receive: Receive = {
  21. case TestMessage(msg) => //模拟子级功能
  22. if (Random.nextBoolean()) //任意产生异常
  23. throw new ChildException(TestMessage(msg))
  24. else
  25. log.info(s"Child received message: ${msg}")
  26. }
  27. }
  28. object Supervisor {
  29. def props: Props = { //在这里定义了监管策略和child Actor构建
  30. def decider: PartialFunction[Throwable, SupervisorStrategy.Directive] = {
  31. case InnerChild.CException(tmsg) =>
  32. println(s"Message causing exception: ${tmsg.msg}") //we can extract message here
  33. BackoffSupervisorDemo.sendToParent(tmsg) //resend message
  34. SupervisorStrategy.Restart
  35. }
  36.  
  37. val options = Backoff.onFailure(InnerChild.props, "innerChild", second, seconds, 0.0)
  38. .withManualReset
  39. .withSupervisorStrategy(
  40. OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds)(
  41. decider.orElse(SupervisorStrategy.defaultDecider)
  42. )
  43. )
  44. BackoffSupervisor.props(options)
  45. }
  46. }
  47. //注意:下面是Supervisor的父级,不是InnerChild的父级
  48. object ParentalActor {
  49. case class SendToSupervisor(msg: InnerChild.TestMessage)
  50. case class SendToInnerChild(msg: InnerChild.TestMessage)
  51. case class SendToChildSelection(msg: InnerChild.TestMessage)
  52. def props = Props[ParentalActor]
  53. }
  54. class ParentalActor extends Actor with ActorLogging {
  55. import ParentalActor._
  56. //在这里构建子级Actor supervisor
  57. val supervisor = context.actorOf(Supervisor.props,"supervisor")
  58. override def receive: Receive = {
  59. case msg@ _ => supervisor ! msg
  60. }
  61.  
  62. }
  63. object DeadLetterMonitor {
  64. def props(parentRef: ActorRef) = Props(new DeadLetterMonitor(parentRef))
  65. }
  66. class DeadLetterMonitor(receiver: ActorRef) extends Actor with ActorLogging {
  67. import InnerChild._
  68. import context.dispatcher
  69. override def receive: Receive = {
  70. case DeadLetter(msg,sender,_) =>
  71. //wait till InnerChild finishes restart then resend
  72. context.system.scheduler.scheduleOnce( second,receiver,msg.asInstanceOf[TestMessage])
  73. }
  74. }
  75. object BackoffSupervisorDemo extends App {
  76. import ParentalActor._
  77. import InnerChild._
  78.  
  79. def sendToParent(msg: TestMessage) = parent ! msg
  80.  
  81. val testSystem = ActorSystem("testSystem")
  82. val parent = testSystem.actorOf(ParentalActor.props,"parent")
  83.  
  84. val deadLetterMonitor = testSystem.actorOf(DeadLetterMonitor.props(parent),"dlmonitor")
  85. testSystem.eventStream.subscribe(deadLetterMonitor,classOf[DeadLetter]) //listen to DeadLetter
  86.  
  87. parent ! TestMessage("Hello message 1 to supervisor")
  88. parent ! TestMessage("Hello message 2 to supervisor")
  89. parent ! TestMessage("Hello message 3 to supervisor")
  90. parent ! TestMessage("Hello message 4 to supervisor")
  91. parent ! TestMessage("Hello message 5 to supervisor")
  92. parent ! TestMessage("Hello message 6 to supervisor")
  93.  
  94. scala.io.StdIn.readLine()
  95.  
  96. testSystem.terminate()
  97.  
  98. }

Akka(3): Actor监管 - 细述BackoffSupervisor的更多相关文章

  1. 二 Akka学习 - actor介绍

    一个actorSystem 是一个重量级的结构.它会分配N个线程.所以对于每一个应用来说只用创建一个ActorSystem. Actor是种可怜的“生物”,它们不能独自存活.Akka中的每一个Acto ...

  2. 细述 Java垃圾回收机制→Types of Java Garbage Collectors

    细述 Java垃圾回收机制→Types of Java Garbage Collectors 转自:https://segmentfault.com/a/1190000006214497 本文非原创, ...

  3. [翻译]AKKA笔记 -ACTOR SUPERVISION - 8

    失败更像是分布式系统的一个特性.因此Akka用一个容忍失败的模型,在你的业务逻辑与失败处理逻辑(supervision逻辑)中间你能有一个清晰的边界.只需要一点点工作,这很赞.这就是我们要讨论的主题. ...

  4. [翻译]AKKA笔记 - ACTOR MESSAGING - REQUEST AND RESPONSE -3

    上次我们看Actor消息机制,我们看到开火-忘记型消息发出(意思是我们只要发个消息给Actor但是不期望有响应). 技术上来讲, 我们发消息给Actors就是要它的副作用. 这就是这么设计的.除了不响 ...

  5. 【Akka】Actor模型探索

    Akka是什么 Akka就是为了改变编写高容错性和强可扩展性的并发程序而生的.通过使用Actor模型我们提升了抽象级别,为构建正确的可扩展并发应用提供了一个更好的平台.在容错性方面我们採取了" ...

  6. [翻译]AKKA笔记 - ACTOR生命周期 - 基本 -5

    原文地址:http://rerun.me/2014/10/21/akka-notes-actor-lifecycle-basic/ (请注意这了讨论的生命周期并不包括 preRestart 或者pos ...

  7. 翻译:AKKA笔记 - Actor消息 -1(二)

    消息 我们只是让QuoteRequest到ActorRef去但是我们根本没见过消息类! 它是这样的: (一个最佳实践是把你的消息类包装在一个完整的对象里以利于更好的组织) TeacherProtoco ...

  8. 翻译:AKKA笔记 - Actor消息 -1(一)

    从第一篇Akka笔记的介绍中,我们是从很高的高度去观察Akka工具箱中的Actors.在这篇笔记的第二篇,我们会看一下Actors中的消息部分.而且延续上一次的例子,我们还会使用同样的学生与老师的例子 ...

  9. Akka的Actor模型及使用实例

    本文的绝大部分内容转载自rerun.me这一blog,老外写的东西就是好啊. ACTORS介绍 Anyone who has done multithreading in the past won't ...

随机推荐

  1. 介绍三个Android支持库控件:TabLayout+ViewPager+RecyclerView

    本文主要介绍如下三个Android支持库控件的配合使用: TabLayout:android.support.design.widget.TabLayout ViewPager:android.sup ...

  2. 浩哥解析MyBatis源码(九)——Type类型模块之类型处理器注册器(TypeHandlerRegistry)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6709157.html 1.回顾 上一篇研究的是类型别名注册器TypeAliasRegist ...

  3. 秒懂JS对象、构造器函数和原型对象之间的关系

    学习JS的过程中,想要掌握面向对象的程序设计风格,对象模型(原型和继承)是其中的重点和难点,拜读了各类经典书籍和各位前辈的技术文章,感觉都太过高深,花费了不少时间才搞明白(个人智商是硬伤/(ㄒoㄒ)/ ...

  4. 安装node-saas包报错问题

    项目中用到一些sass写的样式,但是每次一编译就报错 按照它上面的提醒,npm rebuild node-sass --force,还是一样有错.不过仔细看看他的错误信息我发现了其中这条: gyp v ...

  5. [ext4]010 磁盘布局 - 如何查找inode的磁盘位置

    在linux系统中,任何一个文件,都有一个inode与其对应,也就是说,在一个文件系统中,一个文件都有唯一的ino来标示他,那么在ext4系统中,ino是如何确定的哪? 当我们新创建的文件或目录时,会 ...

  6. hdu1151有向图的最小顶点覆盖

    有向图的最小路径覆盖=V-二分图最大匹配. Consider a town where all the streets are one-way and each street leads from o ...

  7. PHP的学习记录

    这是我的第一次写博客,是一个PHP的初学者,刚刚开始之前是一点儿的都不懂,现在开始通过买些书籍回来学习,废话少说,开始记录笔记吧. 函数:函数的英文名为function,也就是功能的意思,在自定义函数 ...

  8. path sum i

    Problem Statement:  Path sum i Given a binary tree and a sum, determine if the tree has a root-to-le ...

  9. Hive篇之安装

    1,安装 hive的版本的选择,是选择内置的数据库保存元数据,还是用外部的mysql之类的数据库保存元数据,同时,如果使用外置的mysql,需要注意对mysql远程访问的配置. 再就是关于文件的配置了 ...

  10. OpenStack Newton版本Ceph集成部署记录

    2017年2月,OpenStack Ocata版本正式release,就此记录上一版本 Newton 结合Ceph Jewel版的部署实践.宿主机操作系统为CentOS 7.2 . 初级版: 192. ...