Scala 从主管处重新启动后向参与者发送消息
我正在使用BackoffSupervisor策略创建一个必须处理某些消息的儿童演员。我想实现一个非常简单的重启策略,在异常情况下:Scala 从主管处重新启动后向参与者发送消息,scala,akka,actor,Scala,Akka,Actor,我正在使用BackoffSupervisor策略创建一个必须处理某些消息的儿童演员。我想实现一个非常简单的重启策略,在异常情况下: 子进程将失败消息传播给主管 主管重新启动子进程并再次发送失败消息 主管在重试3次后放弃 Akka持久性不是一种选择 到目前为止,我得到的是: 主管定义: val childProps = Props(new SenderActor()) val supervisor = BackoffSupervisor.props( Backoff.onFailure(
val childProps = Props(new SenderActor())
val supervisor = BackoffSupervisor.props(
Backoff.onFailure(
childProps,
childName = cmd.hashCode.toString,
minBackoff = 1.seconds,
maxBackoff = 2.seconds,
randomFactor = 0.2
)
.withSupervisorStrategy(
OneForOneStrategy(maxNrOfRetries = 3, loggingEnabled = true) {
case msg: MessageException => {
println("caught specific message!")
SupervisorStrategy.Restart
}
case _: Exception => SupervisorStrategy.Restart
case _ ⇒ SupervisorStrategy.Escalate
})
)
val sup = context.actorOf(supervisor)
sup ! cmd
本应发送电子邮件但失败(引发某些异常)并将异常传播回主管的子参与者:
class SenderActor() extends Actor {
def fakeSendMail():Unit = {
Thread.sleep(1000)
throw new Exception("surprising exception")
}
override def receive: Receive = {
case cmd: NewMail =>
println("new mail received routee")
try {
fakeSendMail()
} catch {
case t => throw MessageException(cmd, t)
}
}
}
在上面的代码中,我将任何异常包装到自定义类MessageException中,该类被传播到SupervisorStrategy,但是如何将其进一步传播到新的子级以强制重新处理?这是正确的方法吗
编辑。我试图在preRestart
hook上向参与者重新发送消息,但不知何故,没有触发hook:
class SenderActor() extends Actor {
def fakeSendMail():Unit = {
Thread.sleep(1000)
// println("mail sent!")
throw new Exception("surprising exception")
}
override def preStart(): Unit = {
println("child starting")
}
override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
reason match {
case m: MessageException => {
println("aaaaa")
message.foreach(self ! _)
}
case _ => println("bbbb")
}
}
override def postStop(): Unit = {
println("child stopping")
}
override def receive: Receive = {
case cmd: NewMail =>
println("new mail received routee")
try {
fakeSendMail()
} catch {
case t => throw MessageException(cmd, t)
}
}
}
这给了我类似于以下输出的东西:
new mail received routee
caught specific message!
child stopping
[ERROR] [01/26/2018 10:15:35.690]
[example-akka.actor.default-dispatcher-2]
[akka://example/user/persistentActor-4-scala/$a/1962829645] Could not
process message sample.persistence.MessageException:
Could not process message <stacktrace>
child starting
routee收到的新邮件
捕捉到特定的消息!
儿童停车
[错误][01/26/2018 10:15:35.690]
[示例akka.actor.default-dispatcher-2]
[akka://example/user/persistentActor-4-scala/$a/1962829645]无法
处理消息sample.persistence.MessageException:
无法处理消息
儿童启动
但是没有来自
重新启动前的日志
hook失败的子参与者在您的主管策略中作为发送者可用。引述:
如果战略是在监督参与者内部宣布的(相反
它的决策者有权访问所有内部对象
以线程安全方式显示参与者的状态,包括获取
对当前失败的子项的引用(可作为
失败消息)
在您的情况下,使用某些第三方软件发送电子邮件是一种危险的操作。为什么不应用模式并完全跳过发送者角色呢?此外,您还可以在其中设置一个参与者(带有一些退避管理器)和断路器(如果对您有意义的话)。未调用孩子的
重新启动前钩子的原因是退避。onFailure
在盖子下面使用,它将默认重启行为替换为与退避策略一致的停止和延迟启动行为。换句话说,当使用Backoff.onFailure
时,当子级重新启动时,不会调用子级的preRestart
方法,因为底层主管实际上会停止子级,然后再重新启动它。(使用可以触发孩子的预重启
钩子,但这与当前的讨论无关。)
BackoffSupervisor
API不支持在主管的子级重新启动时自动重新发送消息:您必须自己实现此行为。重试消息的一个想法是让BackoffSupervisor
的主管处理它。例如:
val supervisor = BackoffSupervisor.props(
Backoff.onFailure(
...
).withReplyWhileStopped(ChildIsStopped)
).withSupervisorStrategy(
OneForOneStrategy(maxNrOfRetries = 3, loggingEnabled = true) {
case msg: MessageException =>
println("caught specific message!")
self ! Error(msg.cmd) // replace cmd with whatever the property name is
SupervisorStrategy.Restart
case ...
})
)
val sup = context.actorOf(supervisor)
def receive = {
case cmd: NewMail =>
sup ! cmd
case Error(cmd) =>
timers.startSingleTimer(cmd.id, Replay(cmd), 10.seconds)
// We assume that NewMail has an id field. Also, adjust the time as needed.
case Replay(cmd) =>
sup ! cmd
case ChildIsStopped =>
println("child is stopped")
}
在上面的代码中,MessageException
中嵌入的NewMail
消息被包装在一个定制的case类中(以便容易地将其与“普通”//newNewMail
消息区分开来),并发送到self
。在此上下文中,self
是创建BackoffSupervisor
的参与者。然后,这个封闭的参与者使用a在某个点重播原始消息。这一时间点在将来应该足够远,以便BackoffSupervisor
可能会耗尽SenderActor
的重新启动尝试,以便孩子有足够的机会在收到重新发送消息之前进入“良好”状态。显然,这个示例只涉及一条消息的重新发送,而不考虑子重新启动的次数
另一个想法是为每个NewMail
消息创建BackoffSupervisor
-senderator
对,并让senderator
在preStart
钩子中将NewMail
消息发送给自己。这种方法的一个问题是清理资源;i、 例如,当处理成功或子系统重新启动时,关闭BackoffSupervisors
(这将依次关闭其各自的SenderActor
子系统)。在这种情况下,NewMail
id到(ActorRef,Int)
元组的映射(其中ActorRef
是对BackoffSupervisor
参与者的引用,Int
是重新启动尝试的次数)将非常有用:
class Overlord extends Actor {
var state = Map[Long, (ActorRef, Int)]() // assuming the mail id is a Long
def receive = {
case cmd: NewMail =>
val childProps = Props(new SenderActor(cmd, self))
val supervisor = BackoffSupervisor.props(
Backoff.onFailure(
...
).withSupervisorStrategy(
OneForOneStrategy(maxNrOfRetries = 3, loggingEnabled = true) {
case msg: MessageException =>
println("caught specific message!")
self ! Error(msg.cmd)
SupervisorStrategy.Restart
case ...
})
)
val sup = context.actorOf(supervisor)
state += (cmd.id -> (sup, 0))
case ProcessingDone(cmdId) =>
state.get(cmdId) match {
case Some((backoffSup, _)) =>
context.stop(backoffSup)
state -= cmdId
case None =>
println(s"${cmdId} not found")
}
case Error(cmd) =>
val cmdId = cmd.id
state.get(cmdId) match {
case Some((backoffSup, numRetries)) =>
if (numRetries == 3) {
println(s"${cmdId} has already been retried 3 times. Giving up.")
context.stop(backoffSup)
state -= cmdId
} else
state += (cmdId -> (backoffSup, numRetries + 1))
case None =>
println(s"${cmdId} not found")
}
case ...
}
}
请注意,上述示例中的SenderActor
将NewMail
和ActorRef
作为构造函数参数。后一个参数允许SenderActor
向封闭的参与者发送自定义ProcessingDone
消息:
class SenderActor(cmd: NewMail, target: ActorRef) extends Actor {
override def preStart(): Unit = {
println(s"child starting, sending ${cmd} to self")
self ! cmd
}
def fakeSendMail(): Unit = ...
def receive = {
case cmd: NewMail => ...
}
}
显然,SenderActor
的当前实现设置为每次都失败。我将在SenderActor
中留下实现快乐路径所需的额外更改,在快乐路径中,SenderActor
将ProcessingDone
消息发送给target
。在@chunjef提供的良好解决方案中,他提醒在退避主管启动工人之前安排作业重新发送的风险
然后,这个封闭的参与者使用一个计时器在某个点上重播原始消息。这个时间点在将来应该足够长,这样BackoffSupervisor就有可能耗尽SenderActor的重新启动尝试,这样子级就有足够的机会在收到重新发送的消息之前进入“良好”状态
如果发生这种情况,情况将是工作一文不值,不会有进一步的进展。
我已经把小提琴简化了
所以,时间表应该推迟