重新启动Akka Actor和反应流集成
我有一个场景,我必须从父演员中重新启动子演员。应按照以下规则重新启动: 只有在停止完成后才能启动 停止和启动都应该异步进行 我现在有以下场景: 在我的父Actor中,我有一个Monix Observable,它正在推送事件,如下所示:重新启动Akka Actor和反应流集成,akka,Akka,我有一个场景,我必须从父演员中重新启动子演员。应按照以下规则重新启动: 只有在停止完成后才能启动 停止和启动都应该异步进行 我现在有以下场景: 在我的父Actor中,我有一个Monix Observable,它正在推送事件,如下所示: class ParentActor extends Actor { ... override def preStart(): Unit = { super.preStart() // Observable to stream eve
class ParentActor extends Actor {
...
override def preStart(): Unit = {
super.preStart()
// Observable to stream events regarding PowerPlant's
val powerPlantEventObservable =
// For every config.database.refreshInterval in seconds
Observable.interval(config.database.refreshInterval)
// We ask the actor for the latest messages
.map(_ => (dbServiceActor ? DBServiceActor.PowerPlantEvents).mapTo[PowerPlantEventsSeq])
.concatMap(Observable.fromFuture(_))
.concatMap(Observable.fromIterable(_))
// Subscriber that pipes the messages to this Actor
cancelable := powerPlantEventObservable.subscribe { update =>
(self ? update).map(_ => Continue)
}
}
}
所以上面发生的事情是,我向另一个名为DBServiceActor的参与者询问事件列表,当这些事件可用时,我会将其传递给父参与者自己?使现代化ParentActor的receive方法如下所示,这就是我要重新启动子actor的地方。异步:
override def receive: Receive = {
case PowerPlantUpdateEvent(id, powerPlantCfg) =>
log.info(s"Re-starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
// I want to stop the actor first by finding it from the actor system
// If it exists, do a context.stop on the Actor instance
// Once it is stopped, I want to start it again by creating a new instance of this Actor
// Once this new Actor instance is created, I want to signal my Monix Observer to send me the next event
}
有什么建议吗
编辑:根据以下帖子的建议,这里是新的接收方法:
def receive: Receive = {
// TODO: When I restart, I need the powerPlantCfg!!! How to get it?
case Terminated(actorRef) =>
context.unwatch(actorRef)
case PowerPlantCreateEvent(id, powerPlantCfg) =>
log.info(s"Starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
// Start the PowerPlant, and pipe the message to self
startPowerPlant(id, powerPlantCfg).pipeTo(self)
case PowerPlantUpdateEvent(id, powerPlantCfg) =>
log.info(s"Re-starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
actorRef ! PoisonPill
case None =>
log.warning(s"No running actor instance found for id $id :: Creating a new instance")
self ! PowerPlantCreateEvent(id, powerPlantCfg)
}
case PowerPlantDeleteEvent(id, powerPlantCfg) => // TODO
log.info(s"Stopping PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
actorRef ! PoisonPill
case None =>
log.warning(s"No running actor instance found for id $id")
}
}
我建议如下:
// I want to stop the actor first by finding it from the actor system
要查找子级,请使用val child=context.childname或浏览context.childrename
对于上面找到的孩子,您必须设置死亡观察,以便下一步使用并杀死它:
context.watch(child) // you could also consider using context.watchWith()
child ! PoisonPill
由于上面的deatch watch设置,您的家长参与者将收到通知,孩子已停止,只需添加一个消息处理程序:
override def receive: Receive = {
case Terminated(child) => // you might want to check which specific child was terminated if you care
context.unwatch(child)
context.actorOf(...) // recreate the child actor
您可以在调用context.actorOf后立即发出创建信号,如果您想确保子参与者成功启动,即,因为在初始化/启动过程中可能会出现故障,那么您应该让您的父参与者期望来自新创建的子参与者的信号。您可以在启动前将该信号从子级发送到父级,如果启动更复杂,也可以在其消息处理程序中发送。因此,根据Frederic A的回答,我在这里发布了我的解决方案,展示了如何处理参与者的重新启动:
def waitForRestart(source: ActorRef, newMessage: Option[PowerPlantCreateEvent[PowerPlantConfig]]): Receive = {
case Terminated(actor) =>
context.unwatch(actor)
newMessage match {
case Some(powerPlantCreateEvent) =>
self ! powerPlantCreateEvent
case _ =>
// just ignore
}
// Now unstash all of the messages
unstashAll()
case someDamnThing =>
log.error(s"Unexpected message $someDamnThing :: " +
s"received while waiting for an actor to be stopped")
stash()
}
以下是接收方法:
def receive: Receive = {
case Terminated(actorRef) =>
context.unwatch(actorRef)
case PowerPlantCreateEvent(id, powerPlantCfg) =>
log.info(s"Starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
// Start the PowerPlant, and pipe the message to self
startPowerPlant(id, powerPlantCfg).pipeTo(self)
case PowerPlantUpdateEvent(id, powerPlantCfg) =>
log.info(s"Re-starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
// We first kill
actorRef ! PoisonPill
// We wait asynchronously until this Actor is re-started
context.become(
waitForRestart(
actorRef,
Some(PowerPlantCreateEvent(id, powerPlantCfg))
)
)
case None =>
log.warning(s"No running actor instance found for id $id :: Creating a new instance")
self ! PowerPlantCreateEvent(id, powerPlantCfg)
}
case PowerPlantDeleteEvent(id, powerPlantCfg) =>
log.info(s"Stopping PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
actorRef ! Kill
case None =>
log.warning(s"No running actor instance found for id $id")
}
}
我在哪里可以再次取消跟踪?我看到的问题是,当前消息在发送毒药后立即完成,而在那里,发送给Monix观察者的信号完成,这意味着观察者将把下一条消息推送到我的接收中。但是我想避免这种情况,只有在我重新启动这个新实例后,像上面Terminatedchild块中那样,我才让Monix observer推送下一条消息。我在我的原始帖子中添加了这个块,显示了我是如何将消息从我的Monix observer传送到此Actor的。对于您的第一条评论,我编辑了我的答案。对于其余部分,我建议您将消息隐藏起来,直到孩子重新启动。查看akka的文档以获取隐藏消息。隐藏可能有效!谢谢你的建议!你帮了我!
// Once this new Actor instance is created, I want to signal my Monix Observer to send me the next event
def waitForRestart(source: ActorRef, newMessage: Option[PowerPlantCreateEvent[PowerPlantConfig]]): Receive = {
case Terminated(actor) =>
context.unwatch(actor)
newMessage match {
case Some(powerPlantCreateEvent) =>
self ! powerPlantCreateEvent
case _ =>
// just ignore
}
// Now unstash all of the messages
unstashAll()
case someDamnThing =>
log.error(s"Unexpected message $someDamnThing :: " +
s"received while waiting for an actor to be stopped")
stash()
}
def receive: Receive = {
case Terminated(actorRef) =>
context.unwatch(actorRef)
case PowerPlantCreateEvent(id, powerPlantCfg) =>
log.info(s"Starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
// Start the PowerPlant, and pipe the message to self
startPowerPlant(id, powerPlantCfg).pipeTo(self)
case PowerPlantUpdateEvent(id, powerPlantCfg) =>
log.info(s"Re-starting PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
// We first kill
actorRef ! PoisonPill
// We wait asynchronously until this Actor is re-started
context.become(
waitForRestart(
actorRef,
Some(PowerPlantCreateEvent(id, powerPlantCfg))
)
)
case None =>
log.warning(s"No running actor instance found for id $id :: Creating a new instance")
self ! PowerPlantCreateEvent(id, powerPlantCfg)
}
case PowerPlantDeleteEvent(id, powerPlantCfg) =>
log.info(s"Stopping PowerPlant actor with id = $id and type ${powerPlantCfg.powerPlantType}")
context.child(s"$simulatorActorNamePrefix$id") match {
case Some(actorRef) =>
context.watch(actorRef)
actorRef ! Kill
case None =>
log.warning(s"No running actor instance found for id $id")
}
}