Akka(3): Actor监管 - 细述BackoffSupervisor
在上一篇讨论中我们谈到了监管:在Akka中就是一种直属父子监管树结构,父级Actor负责处理直属子级Actor产生的异常。当时我们把BackoffSupervisor作为父子监管方式的其中一种。实际上BackoffSupervisor与定义了supervisorStrategy的Actor有所不同。我们应该把BackoffSupervisor看作是一个一体化的Actor。当然,它的实现方式还是由一对父子Actor组成。监管策略(SupervisorStrategy)是在BackoffSupervisor的内部实现的。从外表上BackoffSupervisor就像是一个Actor,运算逻辑是在子级Actor中定义的,所谓的父级Actor除监管之外没有任何其它功能,我们甚至没有地方定义父级Actor的功能,它的唯一功能是转发收到的信息给子级,是嵌入BackoffSupervisor里的。所以我们虽然发送消息给BackoffSupervisor,但实际上是在与它的子级交流。我们看看下面这个例子:
package backoffSupervisorDemo
import akka.actor._
import akka.pattern._
import backoffSupervisorDemo.InnerChild.TestMessage import scala.concurrent.duration._ object InnerChild {
case class TestMessage(msg: String)
class ChildException extends Exception def props = Props[InnerChild]
}
class InnerChild extends Actor with ActorLogging {
import InnerChild._
override def receive: Receive = {
case TestMessage(msg) => //模拟子级功能
log.info(s"Child received message: ${msg}")
}
}
object Supervisor {
def props: Props = { //在这里定义了监管策略和child Actor构建
def decider: PartialFunction[Throwable, SupervisorStrategy.Directive] = {
case _: InnerChild.ChildException => SupervisorStrategy.Restart
} val options = Backoff.onFailure(InnerChild.props, "innerChild", second, seconds, 0.0)
.withManualReset
.withSupervisorStrategy(
OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds)(
decider.orElse(SupervisorStrategy.defaultDecider)
)
)
BackoffSupervisor.props(options)
}
}
//注意:下面是Supervisor的父级,不是InnerChild的父级
object ParentalActor {
case class SendToSupervisor(msg: InnerChild.TestMessage)
case class SendToInnerChild(msg: InnerChild.TestMessage)
case class SendToChildSelection(msg: InnerChild.TestMessage)
def props = Props[ParentalActor]
}
class ParentalActor extends Actor with ActorLogging {
import ParentalActor._
//在这里构建子级Actor supervisor
val supervisor = context.actorOf(Supervisor.props,"supervisor")
supervisor ! BackoffSupervisor.getCurrentChild //要求supervisor返回当前子级Actor
var innerChild: Option[ActorRef] = None //返回的当前子级ActorRef
val selectedChild = context.actorSelection("/user/parent/supervisor/innerChild")
override def receive: Receive = {
case BackoffSupervisor.CurrentChild(ref) => //收到子级Actor信息
innerChild = ref
case SendToSupervisor(msg) => supervisor ! msg
case SendToChildSelection(msg) => selectedChild ! msg
case SendToInnerChild(msg) => innerChild foreach(child => child ! msg)
} }
object BackoffSupervisorDemo extends App {
import ParentalActor._
val testSystem = ActorSystem("testSystem")
val parent = testSystem.actorOf(ParentalActor.props,"parent") Thread.sleep() //wait for BackoffSupervisor.CurrentChild(ref) received parent ! SendToSupervisor(TestMessage("Hello message 1 to supervisor"))
parent ! SendToInnerChild(TestMessage("Hello message 2 to innerChild"))
parent ! SendToChildSelection(TestMessage("Hello message 3 to selectedChild")) scala.io.StdIn.readLine() testSystem.terminate() }
在上面的例子里我们分别向supervisor,innerChild,selectedChild发送消息。但所有消息都是由InnerChild响应的,如下:
[INFO] [// ::48.167] [testSystem-akka.actor.default-dispatcher-] [akka://testSystem/user/parent/supervisor/innerChild] Child received message: Hello message 1 to supervisor
[INFO] [// ::48.177] [testSystem-akka.actor.default-dispatcher-] [akka://testSystem/user/parent/supervisor/innerChild] Child received message: Hello message 2 to innerChild
[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是这样处理下面几个特殊消息的:
private[akka] trait HandleBackoff { this: Actor ⇒
  def childProps: Props
  def childName: String
  def reset: BackoffReset
  var child: Option[ActorRef] = None
  var restartCount = 
  import BackoffSupervisor._
  import context.dispatcher
  override def preStart(): Unit = startChild()
  def startChild(): Unit = {
    if (child.isEmpty) {
      child = Some(context.watch(context.actorOf(childProps, childName)))
    }
  }
  def handleBackoff: Receive = {
    case StartChild ⇒
      startChild()
      reset match {
        case AutoReset(resetBackoff) ⇒
          val _ = context.system.scheduler.scheduleOnce(resetBackoff, self, ResetRestartCount(restartCount))
        case _ ⇒ // ignore
      }
    case Reset ⇒
      reset match {
        case ManualReset ⇒ restartCount =
        case msg         ⇒ unhandled(msg)
      }
    case ResetRestartCount(current) ⇒
      if (current == restartCount) {
        restartCount =
      }
    case GetRestartCount ⇒
      sender() ! RestartCount(restartCount)
    case GetCurrentChild ⇒
      sender() ! CurrentChild(child)
    case msg if child.contains(sender()) ⇒
      // use the BackoffSupervisor as sender
      context.parent ! msg
    case msg ⇒ child match {
      case Some(c) ⇒ c.forward(msg)
      case None    ⇒ context.system.deadLetters.forward(msg)
    }
  }
}
在handleBackoff函数里可以找到这些消息的处理方式。
在构建上面例子里的Supervisor的Props时定义了监管策略(SupervisorStrategy)对InnerChild产生的异常ChildException进行Restart处理。我们调整一下InnerChild代码来随机产生一些异常:
object InnerChild {
  case class TestMessage(msg: String)
  class ChildException(val errmsg: TestMessage) extends Exception
  object CException {  //for pattern match of class with parameter
    def apply(msg: TestMessage) = new ChildException(msg)
    def unapply(cex: ChildException) = Some(cex.errmsg)
  }
  def props = Props[InnerChild]
}
class InnerChild extends Actor with ActorLogging {
  import InnerChild._
  context.parent ! BackoffSupervisor.Reset  //reset backoff counts
  override def receive: Receive = {
    case TestMessage(msg) => //模拟子级功能
      if (Random.nextBoolean())   //任意产生异常
        throw new ChildException(TestMessage(msg))
      else
        log.info(s"Child received message: ${msg}")
  }
}
我们用Random.nextBoolean来任意产生一些异常。注意:我们同时把ChildException改成了一个带参数的class,因为我们可能需要在重启之前获取造成异常的消息,如下:
    def decider: PartialFunction[Throwable, SupervisorStrategy.Directive] = {
      case InnerChild.CException(tmsg) =>
        println(s"Message causing exception: ${tmsg.msg}") //we can extract message here
        SupervisorStrategy.Restart
    }
所有信息发给supervisor就行了:
class ParentalActor extends Actor with ActorLogging {
  import ParentalActor._
  //在这里构建子级Actor supervisor
  val supervisor = context.actorOf(Supervisor.props,"supervisor")
  override def receive: Receive = {
    case msg@ _ => supervisor ! msg
  }
}
object BackoffSupervisorDemo extends App {
  import ParentalActor._
  import InnerChild._
  val testSystem = ActorSystem("testSystem")
  val parent = testSystem.actorOf(ParentalActor.props,"parent")
  parent ! TestMessage("Hello message 1 to supervisor")
  parent ! TestMessage("Hello message 2 to supervisor")
  parent ! TestMessage("Hello message 3 to supervisor")
  parent ! TestMessage("Hello message 4 to supervisor")
  parent ! TestMessage("Hello message 5 to supervisor")
  parent ! TestMessage("Hello message 6 to supervisor")
  scala.io.StdIn.readLine()
  testSystem.terminate()
}
运行后发现在出现异常后所有消息都变成了DeadLetter:
[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'.
....
这也证明了BackoffSupervisor具有不同的Restart处理方式,好像是直接终止InnerChild而非正常的挂起,销毁了ActorRef和邮箱,所以在完成启动之前发给InnerChild的消息都被导入DeadLetter队列了。也就是说不但错过造成异常的消息,而是跳过了下面启动时间段内所有的消息。
下面我们来解决失踪消息的问题:首先是如何重新发送造成异常的消息,我们可以在监管策略中重启前发送:
    def decider: PartialFunction[Throwable, SupervisorStrategy.Directive] = {
      case InnerChild.CException(tmsg) =>
        println(s"Message causing exception: ${tmsg.msg}") //we can extract message here
        BackoffSupervisorDemo.sendToParent(tmsg)  //resend message
        SupervisorStrategy.Restart
    }
在BackoffSupervisorDemo里先声明sendToParent函数:
def sendToParent(msg: TestMessage) = parent ! msg
然后再想办法把DeadLetter捞出来。我们可以用Akka的eventStream来订阅DeadLetter类型消息:
object DeadLetterMonitor {
  def props(parentRef: ActorRef) = Props(new DeadLetterMonitor(parentRef))
}
class DeadLetterMonitor(receiver: ActorRef) extends Actor with ActorLogging {
  import InnerChild._
  import context.dispatcher
  override def receive: Receive = {
    case DeadLetter(msg,sender,_) =>
      //wait till InnerChild finishes restart then resend
      context.system.scheduler.scheduleOnce( second,receiver,msg.asInstanceOf[TestMessage])
  }
}
object BackoffSupervisorDemo extends App {
  import ParentalActor._
  import InnerChild._
  def sendToParent(msg: TestMessage) = parent ! msg
  val testSystem = ActorSystem("testSystem")
  val parent = testSystem.actorOf(ParentalActor.props,"parent")
  val deadLetterMonitor = testSystem.actorOf(DeadLetterMonitor.props(parent),"dlmonitor")
  testSystem.eventStream.subscribe(deadLetterMonitor,classOf[DeadLetter]) //listen to DeadLetter
  parent ! TestMessage("Hello message 1 to supervisor")
  parent ! TestMessage("Hello message 2 to supervisor")
  parent ! TestMessage("Hello message 3 to supervisor")
  parent ! TestMessage("Hello message 4 to supervisor")
  parent ! TestMessage("Hello message 5 to supervisor")
  parent ! TestMessage("Hello message 6 to supervisor")
  scala.io.StdIn.readLine()
  testSystem.terminate()
}
试运算后显示InnerChild成功处理了所有6条消息。
下面是本次讨论的完整示范代码:
package backoffSupervisorDemo
import akka.actor._
import akka.pattern._
import scala.util.Random import scala.concurrent.duration._ object InnerChild {
case class TestMessage(msg: String)
class ChildException(val errmsg: TestMessage) extends Exception
object CException { //for pattern match of class with parameter
def apply(msg: TestMessage) = new ChildException(msg)
def unapply(cex: ChildException) = Some(cex.errmsg)
}
def props = Props[InnerChild]
}
class InnerChild extends Actor with ActorLogging {
import InnerChild._
context.parent ! BackoffSupervisor.Reset //reset backoff counts
override def receive: Receive = {
case TestMessage(msg) => //模拟子级功能
if (Random.nextBoolean()) //任意产生异常
throw new ChildException(TestMessage(msg))
else
log.info(s"Child received message: ${msg}")
}
}
object Supervisor {
def props: Props = { //在这里定义了监管策略和child Actor构建
def decider: PartialFunction[Throwable, SupervisorStrategy.Directive] = {
case InnerChild.CException(tmsg) =>
println(s"Message causing exception: ${tmsg.msg}") //we can extract message here
BackoffSupervisorDemo.sendToParent(tmsg) //resend message
SupervisorStrategy.Restart
} val options = Backoff.onFailure(InnerChild.props, "innerChild", second, seconds, 0.0)
.withManualReset
.withSupervisorStrategy(
OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds)(
decider.orElse(SupervisorStrategy.defaultDecider)
)
)
BackoffSupervisor.props(options)
}
}
//注意:下面是Supervisor的父级,不是InnerChild的父级
object ParentalActor {
case class SendToSupervisor(msg: InnerChild.TestMessage)
case class SendToInnerChild(msg: InnerChild.TestMessage)
case class SendToChildSelection(msg: InnerChild.TestMessage)
def props = Props[ParentalActor]
}
class ParentalActor extends Actor with ActorLogging {
import ParentalActor._
//在这里构建子级Actor supervisor
val supervisor = context.actorOf(Supervisor.props,"supervisor")
override def receive: Receive = {
case msg@ _ => supervisor ! msg
} }
object DeadLetterMonitor {
def props(parentRef: ActorRef) = Props(new DeadLetterMonitor(parentRef))
}
class DeadLetterMonitor(receiver: ActorRef) extends Actor with ActorLogging {
import InnerChild._
import context.dispatcher
override def receive: Receive = {
case DeadLetter(msg,sender,_) =>
//wait till InnerChild finishes restart then resend
context.system.scheduler.scheduleOnce( second,receiver,msg.asInstanceOf[TestMessage])
}
}
object BackoffSupervisorDemo extends App {
import ParentalActor._
import InnerChild._ def sendToParent(msg: TestMessage) = parent ! msg val testSystem = ActorSystem("testSystem")
val parent = testSystem.actorOf(ParentalActor.props,"parent") val deadLetterMonitor = testSystem.actorOf(DeadLetterMonitor.props(parent),"dlmonitor")
testSystem.eventStream.subscribe(deadLetterMonitor,classOf[DeadLetter]) //listen to DeadLetter parent ! TestMessage("Hello message 1 to supervisor")
parent ! TestMessage("Hello message 2 to supervisor")
parent ! TestMessage("Hello message 3 to supervisor")
parent ! TestMessage("Hello message 4 to supervisor")
parent ! TestMessage("Hello message 5 to supervisor")
parent ! TestMessage("Hello message 6 to supervisor") scala.io.StdIn.readLine() testSystem.terminate() }
Akka(3): Actor监管 - 细述BackoffSupervisor的更多相关文章
- 二 Akka学习 - actor介绍
		一个actorSystem 是一个重量级的结构.它会分配N个线程.所以对于每一个应用来说只用创建一个ActorSystem. Actor是种可怜的“生物”,它们不能独自存活.Akka中的每一个Acto ... 
- 细述 Java垃圾回收机制→Types of Java Garbage Collectors
		细述 Java垃圾回收机制→Types of Java Garbage Collectors 转自:https://segmentfault.com/a/1190000006214497 本文非原创, ... 
- [翻译]AKKA笔记 -ACTOR SUPERVISION - 8
		失败更像是分布式系统的一个特性.因此Akka用一个容忍失败的模型,在你的业务逻辑与失败处理逻辑(supervision逻辑)中间你能有一个清晰的边界.只需要一点点工作,这很赞.这就是我们要讨论的主题. ... 
- [翻译]AKKA笔记 - ACTOR MESSAGING - REQUEST AND RESPONSE -3
		上次我们看Actor消息机制,我们看到开火-忘记型消息发出(意思是我们只要发个消息给Actor但是不期望有响应). 技术上来讲, 我们发消息给Actors就是要它的副作用. 这就是这么设计的.除了不响 ... 
- 【Akka】Actor模型探索
		Akka是什么 Akka就是为了改变编写高容错性和强可扩展性的并发程序而生的.通过使用Actor模型我们提升了抽象级别,为构建正确的可扩展并发应用提供了一个更好的平台.在容错性方面我们採取了" ... 
- [翻译]AKKA笔记 - ACTOR生命周期 - 基本 -5
		原文地址:http://rerun.me/2014/10/21/akka-notes-actor-lifecycle-basic/ (请注意这了讨论的生命周期并不包括 preRestart 或者pos ... 
- 翻译:AKKA笔记 - Actor消息 -1(二)
		消息 我们只是让QuoteRequest到ActorRef去但是我们根本没见过消息类! 它是这样的: (一个最佳实践是把你的消息类包装在一个完整的对象里以利于更好的组织) TeacherProtoco ... 
- 翻译:AKKA笔记 - Actor消息 -1(一)
		从第一篇Akka笔记的介绍中,我们是从很高的高度去观察Akka工具箱中的Actors.在这篇笔记的第二篇,我们会看一下Actors中的消息部分.而且延续上一次的例子,我们还会使用同样的学生与老师的例子 ... 
- Akka的Actor模型及使用实例
		本文的绝大部分内容转载自rerun.me这一blog,老外写的东西就是好啊. ACTORS介绍 Anyone who has done multithreading in the past won't ... 
随机推荐
- 使用mysql索引技巧及注意事项
			一.索引的作用 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,所以查询语句的优化显然是重中之重. 在数据 ... 
- jQuery  animate()动画效果
			1.jQuery动画效果 jQuery animate()方法用于创建自定义动画 $(selector).animate({params},speed,callback); //必需的 params ... 
- Hadoop之HDFS原理及文件上传下载源码分析(下)
			上篇Hadoop之HDFS原理及文件上传下载源码分析(上)楼主主要介绍了hdfs原理及FileSystem的初始化源码解析, Client如何与NameNode建立RPC通信.本篇将继续介绍hdfs文 ... 
- GPIO的配置过程
			今天看到一篇很好的博文,,看这里:http://www.cnblogs.com/crazyxu/archive/2011/10/14/2212337.html 下面总结一下,加深一下理解. 要使用GP ... 
- Oracle数据库时间类型悬疑案
			这次遇到的问题小Alan其实一年半前做证券行业项目就已经遇到过,但是一直没有去思考是什么原因导致的这样的悬疑案,悬疑案是什么呢?其实很简单,我想有不少童鞋都有用到Oracle数据库,情形是这样子的,这 ... 
- C语言精要总结-指针系列(一)
			考虑到指针内容繁多,这里将指针作为一个系列,从简入繁,一点一点深挖并掌握这C语言的精华.初步计划如下 此文为指针系列第一篇: C语言精要总结-指针系列(一) 内存与地址 我们可以把内存看做一排连续的房 ... 
- windows下使用IIS的ARR实现站点的负载均衡
			1) 目的: 访问localhost:18066 对下边两个端口负载 localhost:18098 localhost:18099 2) 手段: 1.通过nginx 2.通过iis的AR ... 
- TCP/IP协议精华笔记
			1.简介 TCP/IP协议并非单指TCP协议.IP协议,它是一组包括TCP协议和IP协议,UDP(User Datagram Protocol)协议.ICMP(Internet Control Mes ... 
- OA办公系统,一个沉淀企业文化的容器
			资源是会枯竭的,唯有文化才会生生不息.一切工业产品都是人类智慧创造的.随着公司规模的扩大,企业中两大根本"人和规则"面临诸多挑战,OA办公系统是一个全员使用的办公软件产品,员工可通 ... 
- 使用java API操作hdfs--通过filesystem API 来读取数据
			上面的Path的包是导入错误了,nio中的包是抽象类,是无法创建的,所以换地方更改. 修改之后,指定jar包之后,编译成功,如下,并进行文件的读取操作,依然是成功啦: 
