重新启动Akka Actor和Reactive Streams Integration

时间:2017-08-07 10:06:39

标签: akka

我有一个场景,我必须从父actor中重新启动一个子Actor。重启应遵循以下规则:

  1. 只有在完成停止后才会启动
  2. 停止和开始都应该异步发生
  3. 我现在有以下情况:

    在我的父Actor中,我有一个Monix Observable,它推送事件如下:

    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的Actor获取事件列表,当这些事件可用时,我将它传递给ParentActor(自我更新)。 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")
          }
      }
    

2 个答案:

答案 0 :(得分:1)

我建议如下:

// I want to stop the actor first by finding it from the actor system

要查找孩子,请使用val child = context.child(name)或浏览context.children

// If it exists, do a context.stop on the Actor instance

上面找到的孩子,你必须设置死亡监视(对下一步有用)并杀死它:

context.watch(child) // you could also consider using context.watchWith()
child ! PoisonPill

// Once it is stopped, I want to start it again by creating a new instance of this Actor

由于上面的deatch-watch设置,您的ParentActor将被通知孩子停止,只需添加一个消息处理程序:

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

// Once this new Actor instance is created, I want to signal my Monix Observer to send me the next event

你可以在调用context.actorOf之后立即发出信号,如果你想确定儿童演员设法启动(即,因为某些东西可能在初始化/开始时失败),你应该拥有你的{ {1}}期待来自新创建的孩子的信号。如果启动更复杂,您可以在ParentActor或其中一个消息处理程序中将该信号从子节点发送到父节点。

答案 1 :(得分:0)

基于Frederic A的回答,我在这里发布我的解决方案,展示了如何处理一个Actor的重启:

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")
          }
      }