Akka系列(三):监管与容错
前言......
Akka作为一种成熟的生产环境并发解决方案,必须拥有一套完善的错误异常处理机制,本文主要讲讲Akka中的监管和容错。
监管
看过我上篇文章的同学应该对Actor系统的工作流程有了一定的了解 Akka系列(二):Akka中的Actor系统,它的很重要的概念就是分而治之,既然我们把任务分配给Actor去执行,那么我们必须去监管相应的Actor,当Actor出现了失败,比如系统环境错误,各种异常,能根据我们制定的相应监管策略进行错误恢复,就是后面我们会说到的容错。
监管者
既然有监管这一事件,那必然存在着监管者这么一个角色,那么在ActorSystem中是如何确定这种角色的呢?
我们先来看下ActorSystem中的顶级监管者:
一个actor系统在其创建过程中至少要启动三个actor,如上图所示,下面来说说这三个Actor的功能:
1./
: 根监管者
顾名思义,它是一个老大,它监管着ActorSystem中所有的顶级Actor,顶级Actor有以下几种:
/user
: 是所有由用户创建的顶级actor的监管者;用ActorSystem.actorOf创建的actor在其下。/system
: 是所有由系统创建的顶级actor的监管者,如日志监听器,或由配置指定在actor系统启动时自动部署的actor。/deadLetters
: 是死信actor,所有发往已经终止或不存在的actor的消息会被重定向到这里。/temp
:是所有系统创建的短时actor的监管者,例如那些在ActorRef.ask的实现中用到的actor。/remote
: 是一个人造虚拟路径,用来存放所有其监管者是远程actor引用的actor。
跟我们平常打交道最多的就是/user
,它是我们在程序中用ActorSystem.actorOf创建的actor的监管者,下面的容错我们重点关心的就是它下面的失败处理,其他几种顶级Actor具体功能定义已经给出,有兴趣的也可以去了解一下。
根监管者监管着所有顶级Actor,对它们的各种失败情况进行处理,一般来说如果错误要上升到根监管者,整个系统就会停止。
2./user
: 顶级actor监管者
上面已经讲过/user
是所有由用户创建的顶级actor的监管者,即用ActorSystem.actorOf创建的actor,我们可以自己制定相应的监管策略,但由于它是actor系统启动时就产生的,所以我们需要在相应的配置文件里配置,具体的配置可以参考这里Akka配置
3./system
: 系统监管者
/system
所有由系统创建的顶级actor的监管者,比如Akka中的日志监听器,因为在Akka中日志本身也是用Actor实现的,/system
的监管策略如下:对收到的除ActorInitializationException
和ActorKilledException
之外的所有Exception
无限地执行重启,当然这也会终止其所有子actor。所有其他Throwable被上升到根监管者,然后整个actor系统将会关闭。
用户创建的普通actor的监管:
上一篇文章介绍了Actor系统的组织结构,它是一种树形结构,其实这种结构对actor的监管是非常有利的,Akka实现的是一种叫“父监管”的形式,每一个被创建的actor都由其父亲所监管,这种限制使得actor的监管结构隐式符合其树形结构,所以我们可以得出一个结论:
- 一个被创建的Actor肯定是一个被监管者,也可能是一个监管者,它监管着它的子级Actor
监管策略
上面我们对ActorSystem中的监管角色有了一定的了解,那么到底是如何制定相应的监管策略呢?Akka中有以下4种策略:
- 恢复下属,保持下属当前积累的内部状态
- 重启下属,清除下属的内部状态
- 永久地停止下属
- 升级失败(沿监管树向上传递失败),由此失败自己
这其实很好理解,下面是一个简单例子:
- override val supervisorStrategy =
- OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
- case _: ArithmeticException => Resume //恢复
- case _: NullPointerException => Restart //重启
- case _: IllegalArgumentException => Stop //停止
- case _: Exception => Escalate //向上级传递
- }
我们可以根据异常的不同使用不同监管策略,在后面我会具体给出一个示例程序帮助大家理解。我们在实现自己的策略时,需要复写Actor中的supervisorStrategy
,因为Actor的默认监管策略如下:
- final val defaultDecider: Decider = {
- case _: ActorInitializationException ⇒ Stop
- case _: ActorKilledException ⇒ Stop
- case _: DeathPactException ⇒ Stop
- case _: Exception ⇒ Restart
- }
它对除了它指定的异常进行停止,其他异常都是对下属进行重启。
Akka中有两种类型的监管策略:OneForOneStrategy
和AllForOneStrategy
,它们的主要区别在于:
OneForOneStrategy
: 该策略只会应用到发生故障的子actor上。AllForOneStrategy
: 该策略会应用到所有的子actor上。
我们一般都使用OneForOneStrategy
来进行制定相关监管策略,当然你也可以根据具体需求选择合适的策略。另外我们可以给我们的策略配置相应参数,比如上面maxNrOfRetries,withinTimeRange等,这里的含义是每分钟最多进行10次重启,若超出这个界限相应的Actor将会被停止,当然你也可以使用策略的默认配置,具体配置信息可以参考源码。
监管容错示例
本示例主要演示Actor在发生错误时,它的监管者会根据相应的监管策略进行不同的处理。源码链接
因为这个例子比较简单,这里我直接贴上相应代码,后面根据具体的测试用例来解释各种监管策略所进行的响应:
- class Supervisor extends Actor {
- //监管下属,根据下属抛出的异常进行相应的处理
- override val supervisorStrategy =
- OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
- case _: ArithmeticException => Resume
- case _: NullPointerException => Restart
- case _: IllegalArgumentException => Stop
- case _: Exception => Escalate
- }
- var childIndex = 0 //用于标示下属Actor的序号
- def receive = {
- case p: Props =>
- childIndex += 1
- //返回一个Child Actor的引用,所以Supervisor Actor是Child Actor的监管者
- sender() ! context.actorOf(p,s"child${childIndex}")
- }
- }
- class Child extends Actor {
- val log = Logging(context.system, this)
- var state = 0
- def receive = {
- case ex: Exception => throw ex //抛出相应的异常
- case x: Int => state = x //改变自身状态
- case s: Command if s.content == "get" =>
- log.info(s"the ${s.self} state is ${state}")
- sender() ! state //返回自身状态
- }
- }
- case class Command( //相应命令
- content: String,
- self: String
- )
现在我们来看看具体的测试用例: 首先我们先构建一个测试环境:
- class GuardianSpec(_system: ActorSystem)
- extends TestKit(_system)
- with WordSpecLike
- with Matchers
- with ImplicitSender {
- def this() = this(ActorSystem("GuardianSpec"))
- "A supervisor" must {
- "apply the chosen strategy for its child" in {
- code here...
- val supervisor = system.actorOf(Props[Supervisor], "supervisor") //创建一个监管者
- supervisor ! Props[Child]
- val child = expectMsgType[ActorRef] // 从 TestKit 的 testActor 中获取回应
- }
- }
- }
1.TestOne:正常运行
- child ! 50 // 将状态设为 50
- child ! Command("get",child.path.name)
- expectMsg(50)
正常运行,测试通过。
2.TestTwo:抛出ArithmeticException
- child ! new ArithmeticException // crash it
- child ! Command("get",child.path.name)
- expectMsg(50)
大家猜这时候测试会通过吗?答案是通过,原因是根据我们制定的监管策略,监管者在面对子级Actor抛出ArithmeticException
异常时,它会去恢复相应出异常的Actor,并保持该Actor的状态,所以此时Actor的状态值还是50,测试通过。
3.TestThree:抛出NullPointerException
- child ! new NullPointerException // crash it harder
- child ! "get"
- expectMsg(50)
这种情况下测试还会通过吗?答案是不通过,原因是根据我们制定的监管策略,监管者在面对子级Actor抛出NullPointerException
异常时,它会去重启相应出异常的Actor,其状态会被清除,所以此时Actor的状态值应该是0,测试不通过。
4.TestFour:抛出IllegalArgumentException
- supervisor ! Props[Child] // create new child
- val child2 = expectMsgType[ActorRef]
- child2 ! 100 // 将状态设为 100
- watch(child) // have testActor watch “child”
- child ! new IllegalArgumentException // break it
- expectMsgPF() {
- case Terminated(`child`) => (println("the child stop"))
- }
- child2 ! Command("get",child2.path.name)
- expectMsg(100)
这里首先我们又创建了一个Child Actor为child2,并将它的状态置为100,这里我们监控前面创建的child1,然后给其发送一个IllegalArgumentException
的消息,让其抛出该异常,测试结果: the child stop 测试通过
从结果中我们可以看出,child在抛出IllegalArgumentException
后,会被其监管着停止,但监管者下的其他Actor还是正常工作。
5.TestFive:抛出一个自定义异常
- watch(child2)
- child2 ! Command("get",child2.path.name) // verify it is alive
- expectMsg(100)
- supervisor ! Props[Child] // create new child
- val child3 = expectMsgType[ActorRef]
- child2 ! new Exception("CRASH") // escalate failure
- expectMsgPF() {
- case t @ Terminated(`child2`) if t.existenceConfirmed => (
- println("the child2 stop")
- )
- }
- child3 ! Command("get",child3.path.name)
- expectMsg(0)
这里首先我们又创建了一个Child Actor为child3,这里我们监控前面创建的child2,然后给其发送一个Exception("CRASH")
的消息,让其抛出该异常,测试结果: the child2 stop 测试不通过
很多人可能会疑惑为什么TestFour可以通过,这里就通不过不了呢?因为这里错误Actor抛出的异常其监管者无法处理,只能将失败上溯传递,而顶级actor的缺省策略是对所有的Exception情况(ActorInitializationException和ActorKilledException例外)进行重启. 由于缺省的重启指令会停止所有的子actor,所以我们这里的child3也会被停止。导致测试不通过。当然这里你也可以复写默认的重启方法,比如:
- override def preRestart(cause: Throwable, msg: Option[Any]) {}
这样重启相应Actor时就不会停止其子级下的所有Actor了。
本文主要介绍了Actor系统中的监管和容错,这一部分内容在Akka中也是很重要的,它与Actor的树形组织结构巧妙结合,本文大量参考了Akka官方文档的相应章节,有兴趣的同学可以点击这里Akka docs。也可以下载我的示例程序,里面包含了一个官方的提供的容错示例。
来源于:https://godpan.me/2017/04/15/learning-akka-3.html
Akka系列(三):监管与容错的更多相关文章
- Akka系列(二):Akka中的Actor系统
前言......... Actor模型作为Akka中最核心的概念,所以Actor在Akka中的组织结构是至关重要,本文主要介绍Akka中Actor系统. 1.Actor系统 Actor作为一种封装状态 ...
- Storm系列三: Storm消息可靠性保障
Storm系列三: Storm消息可靠性保障 在上一篇 Storm系列二: Storm拓扑设计 中我们已经设计了一个稍微复杂一点的拓扑. 而本篇就是在上一篇的基础上再做出一定的调整. 在这里先大概提一 ...
- 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gulp专家
系列目录 前端构建大法 Gulp 系列 (一):为什么需要前端构建 前端构建大法 Gulp 系列 (二):为什么选择gulp 前端构建大法 Gulp 系列 (三):gulp的4个API 让你成为gul ...
- Web 开发人员和设计师必读文章推荐【系列三十】
<Web 前端开发精华文章推荐>2014年第9期(总第30期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...
- MyBatis学习系列三——结合Spring
目录 MyBatis学习系列一之环境搭建 MyBatis学习系列二——增删改查 MyBatis学习系列三——结合Spring MyBatis在项目中应用一般都要结合Spring,这一章主要把MyBat ...
- MySQL并发复制系列三:MySQL和MariaDB实现对比
http://blog.itpub.net/28218939/viewspace-1975856/ 并发复制(Parallel Replication) 系列三:MySQL 5.7 和MariaDB ...
- WCF编程系列(三)地址与绑定
WCF编程系列(三)地址与绑定 地址 地址指定了接收消息的位置,WCF中地址以统一资源标识符(URI)的形式指定.URI由通讯协议和位置路径两部分组成,如示例一中的: http://loc ...
- 【JAVA编码专题】 JAVA字符编码系列三:Java应用中的编码问题
这两天抽时间又总结/整理了一下各种编码的实际编码方式,和在Java应用中的使用情况,在这里记录下来以便日后参考. 为了构成一个完整的对文字编码的认识和深入把握,以便处理在Java开发过程中遇到的各种问 ...
- SQL Server 2008空间数据应用系列三:SQL Server 2008空间数据类型
原文:SQL Server 2008空间数据应用系列三:SQL Server 2008空间数据类型 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server ...
随机推荐
- 使用SQL批量插入数据到数据库 以及一些SQL函数的语法
批量插入100条记录 set nocount on declare @i int=1; while @i<=100 begin Insert into Client(id,ClientCode, ...
- 29.连续子数组的最大和(python)
题目描述 HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决.但是,如果向量 ...
- JDK、JRE和JVM的区别与相互之间的联系
工作这么久,好多时间都在研究一些并发.多线程.分布式.框架等这些东西,但是前几天突然被人问到jdk.jre.jvm的区别与联系,瞬间觉得一脸懵逼,感觉还是有必要重新整理一下一些比较基本的java知识了 ...
- DOM添加
㈠添加元素的步骤 ⑴创建空元素 ⑵设置关键属性 ⑶将元素添加到DOM树 ㈡创建空元素 var elem = document.createElement('table'); 示例: var t ...
- CF 25 E 三个字符串 KMP模板
Test Time Limit: 2000MS Memory Limit: 262144KB 64bit IO Format: %I64d & %I64u Submit Status ...
- [负数在内存中的存储] 0x80000000 = -2147483648
https://blog.csdn.net/youyou362/article/details/72667951/ 1. 十进制负数以其补码存储在内存上 例子:-8 在内存中表示为:1111 1111 ...
- hive on tez 任务失败
最近再hue 集群查询任务经常失败,经过几天的观察,终于找到原因,报错如下 Status: FailedVertex failed, vertexName=Map 1, vertexId=vertex ...
- [CSP-S模拟测试]:你相信引力吗(单调栈)
题目传送门(内部题124) 输入格式 第一行一个整数$n$代表环的长度. 第二行$n$个整数表示每个冰锥的高度. 输出格式 一行一个整数表示有多少对冰锥是危险的. 样例 样例输入1: 51 2 4 5 ...
- CountDownLatch和CyclicBarrier的比较
1.CountDownLatch是线程组之间的等待,即一个(或多个)线程等待N个线程完成某件事情之后再执行:而CyclicBarrier则是线程组内的等待,即每个线程相互等待,即N个线程都被拦截之后, ...
- 第九周学习总结&实验报告(7)
完成火车站售票程序的模拟. 要求: (1)总票数1000张; (2)10个窗口同时开始卖票; (3) 卖票过程延时1秒钟; (4)不能出现一票多卖或卖出负数号票的情 况 实验代码: package y ...