Akka之BackoffSupervisor
一、背景
最近在开发一个项目,项目的各模块之间是使用akka grpc传输音频帧的,并且各模块中的actor分别都进行了persist。本周在开发过程中遇到了一个bug,就是音频帧在通行一段时间后,整个系统处于卡死状态,没有了反应。刚开始怀疑是akka grpc通信时,流中断了,或者没有传输过来,可是通过抓包和日志,发现流的每一帧已经接受到了。最后定位到问题时persist的原因,每次卡死之间都可以发现persit失败了。我们去看persist方法的源码可以发现上面的注解如下:
/**
* Asynchronously persists `event`. On successful persistence, `handler` is called with the
* persisted event. It is guaranteed that no new commands will be received by a persistent actor
* between a call to `persist` and the execution of its `handler`. This also holds for
* multiple `persist` calls per received command. Internally, this is achieved by stashing new
* commands and unstashing them when the `event` has been persisted and handled. The stash used
* for that is an internal stash which doesn't interfere with the inherited user stash.
*
* An event `handler` may close over persistent actor state and modify it. The `sender` of a persisted
* event is the sender of the corresponding command. This means that one can reply to a command
* sender within an event `handler`.
*
* Within an event handler, applications usually update persistent actor state using persisted event
* data, notify listeners and reply to command senders.
*
* If persistence of an event fails, [[#onPersistFailure]] will be invoked and the actor will
* unconditionally be stopped. The reason that it cannot resume when persist fails is that it
* is unknown if the event was actually persisted or not, and therefore it is in an inconsistent
* state. Restarting on persistent failures will most likely fail anyway, since the journal
* is probably unavailable. It is better to stop the actor and after a back-off timeout start
* it again.
*
* @param event event to be persisted
* @param handler handler for each persisted `event`
*/
def persist[A](event: A)(handler: A ⇒ Unit): Unit = {
internalPersist(event)(handler)
}
从注解我们可以发现,我们在使用akka的persist特性时,如果持久化失败,相应的actor就会被stop掉。因此,如果你继续往它发消息是没有任何反应。并且这个注解建议我们使用Backoff来重启这个Actor。(建议,我们在把actor持久化到本地或者使用redis等插件持久化到数据库时,最好选择持久化的方法,不然会使用java持久化,会出现WARN,因为默认的java持久化效率不是很好)。因此,我们来学习一下,这种BackOff重启的方式。
二、普通的监控和重启
我们都知道Actor之间是有父子关系的,如果子Actor出现了异常,是会进行上报,并且使用策略来进行相应的处理,其中最常用的策略就是OneForOneStrategy,也就是只针对发生异常的Actor施用策略,策略中定义了对下属子级发生的各种异常的处理方式。而默认的处理方式是这样的:
final val defaultDecider: Decider = {
case _: ActorInitializationException ⇒ Stop
case _: ActorKilledException ⇒ Stop
case _: DeathPactException ⇒ Stop
case _: Exception ⇒ Restart
}
但是,我们可以加上自己的一些特殊处理方式,例如
override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
case _: ArithmeticException => Resume
case _: MyException => Restart
case t =>
super.supervisorStrategy.decider.applyOrElse(t, (_: Any) => Escalate)
}
三、BackoffSupervisor
但是,如果我们的actor是通过system.actorOf进行启动的,我们就很难通过上述这个方式去自定义自己的异常处理方式。并且,如果我们想进行个细粒度的控制,例如在actor发生异常以后多久去处理它。这种情况我们就可以使用BackoffSupervisor去实现。
我们可以这样理解BackoffSupervisor。实际上BackoffSupervisor与定义了supervisorStrategy的Actor有所不同。我们应该把BackoffSupervisor看作是一个一体化的Actor。当然,它的实现方式还是由一对父子Actor组成。监管策略(SupervisorStrategy)是在BackoffSupervisor的内部实现的。从外表上BackoffSupervisor就像是一个Actor,运算逻辑是在子级Actor中定义的,所谓的父级Actor除监管之外没有任何其它功能,我们甚至没有地方定义父级Actor的功能,它的唯一功能是转发收到的信息给子级,是嵌入BackoffSupervisor里的。所以我们虽然发送消息给BackoffSupervisor,但实际上是在与它的子级交流。下面我们可以通过一个例子来看看怎么使用:
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", 1 second, 5 seconds, 0.0)
.withManualReset
.withSupervisorStrategy(
OneForOneStrategy(maxNrOfRetries = 5, withinTimeRange = 5 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(1000) //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()
}
运行结果如下:
[INFO] [10/13/2018 16:11:48.167] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/parent/supervisor/innerChild] Child received message: Hello message 1 to supervisor
[INFO] [10/13/2018 16:11:48.177] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/parent/supervisor/innerChild] Child received message: Hello message 2 to innerChild
[INFO] [10/13/2018 16:11:48.179] [testSystem-akka.actor.default-dispatcher-3] [akka://testSystem/user/parent/supervisor/innerChild] Child received message: Hello message 3 to selectedChild
从结果可以看出,虽然在上面的例子里我们分别向supervisor,innerChild,selectedChild发送消息。但最终所有消息都是由InnerChild响应的。
Akka之BackoffSupervisor的更多相关文章
- Akka(2):Actor生命周期管理 - 监控和监视
在开始讨论Akka中对Actor的生命周期管理前,我们先探讨一下所谓的Actor编程模式.对比起我们习惯的行令式(imperative)编程模式,Actor编程模式更接近现实中的应用场景和功能测试模式 ...
- Akka(3): Actor监管 - 细述BackoffSupervisor
在上一篇讨论中我们谈到了监管:在Akka中就是一种直属父子监管树结构,父级Actor负责处理直属子级Actor产生的异常.当时我们把BackoffSupervisor作为父子监管方式的其中一种.实际上 ...
- Akka(4): Routers - 智能任务分配
Actor模式最大的优点就是每个Actor都是一个独立的任务运算器.这种模式让我们很方便地把一项大型的任务分割成若干细小任务然后分配给不同的Actor去完成.优点是在设计时可以专注实现每个Actor的 ...
- Akka(25): Stream:对接外部系统-Integration
在现实应用中akka-stream往往需要集成其它的外部系统形成完整的应用.这些外部系统可能是akka系列系统或者其它类型的系统.所以,akka-stream必须提供一些函数和方法来实现与各种不同类型 ...
- Akka源码分析-Cluster-Sharding
个人觉得akka提供的cluster工具中,sharding是最吸引人的.当我们需要把actor分布在不同的节点上时,Cluster sharding非常有用.我们可以使用actor的逻辑标识符与ac ...
- Akka.net路径里的user
因为经常买双色球,嫌每次对彩票号麻烦,于是休息的时候做了个双色球兑奖的小程序,做完了发现业务还挺复杂的,于是改DDD重做设计,拆分服务,各种折腾...,不过这和本随笔没多大关系,等差不多了再总结一下, ...
- Aaron Stannard谈Akka.NET 1.1
Akka.NET 1.1近日发布,带来新特性和性能提升.InfoQ采访了Akka.net维护者Aaron Stannard,了解更多有关Akka.Streams和Akka.Cluster的信息.Aar ...
- Akka.NET v1.0 已发布,支持Mono
Akka.NET 是Java/Scala 流行框架Akka的一个 .NET 开源移植.可用于构建高并发,分布式和容错事件驱动的应用在 .NET 和 Mono 平台之上.Akka.NET 经过一年多的努 ...
- AKKA 笔记 - 有限状态机 -2
AKKA 笔记 - 有限状态机 -2 原文地址: http://rerun.me/2016/05/22/akka-notes-finite-state-machines-2/ 在上一节的Akka FS ...
随机推荐
- WPF学习笔记(8):DataGrid单元格数字为空时避免验证问题的解决
原文:WPF学习笔记(8):DataGrid单元格数字为空时避免验证问题的解决 如下图,在凭证编辑窗体中,有的单元格不需要数字,但如果录入数字后再删除,会触发数字验证,单元格显示红色框线,导致不能执行 ...
- kubernetes大概的工作原理
先放一张Kubernetes的架构图: 整体来看,是一个老大,多个干活的这种结构,基本上所有的分布式系统都是这样,但是里面的组件名称就纷繁复杂,下面将一一解析. 1.元数据存储与集群维护 作为一个集群 ...
- jquery实现轮播插件
这几天用jquery写了两个轮播的插件,功能很简单.第一次尝试写插件,有很多不足的地方,代码如下: 注:图片链接请替换掉,配置信息必须加上图片width和height. <!DOCTYPE ht ...
- Redis 配置登录密码
1. 通过配置文件进行配置 打开 redis.conf,找到 #requirepass foobared 去掉行前的注释,并修改密码为所需的密码,保存文件 重启redis sudo service r ...
- Goole Search Auto Complete
这个项目就九章算法大数据课程的一个项目.主要分为两步: 第一步是 offline 建立 数据库 我们用两个map reduce 的data pipline 来实现. 第二步是 online显示把数据里 ...
- 微信公众平台OAuth2.0网页授权
微信公众平台最近新推出微信认证,认证后可以获得高级接口权限,其中一个是OAuth2.0网页授权,很多朋友在使用这个的时候失败了或者无法理解其内容,希望我出个教程详细讲解一下,于是便有了这篇文章. 一. ...
- golang 高级
下面的EmployeeByID函数将根据给定的员工ID返回对应的员工信息结构体的指针.我们可以使用点操作符来访问它里面的成员: func EmployeeByID(id int) *Employee ...
- String类型的XML文件的格式化
在接收到的xml报文中,未经过格式化,不好看 package org.zln.xml.format; import org.dom4j.Document; import org.dom4j.Docum ...
- GDOI2018 爆零记,Challenge Impossibility
蒟蒻的GDOI又双叒叕考挂啦...... Day 0 && Day -1 学校月考,貌似考的还不错? 然而考完试再坐船去中山实在是慢啊......晚上10点才到酒店 wifi差评... ...
- error: ‘to_string’ was not declared in this scope
错误: error: ‘to_string’ was not declared in this scope 原因: to_string是C++11引入的新功能,旧版本编译器可能不支持它,所以要给编译器 ...