如何观看多个Akka演员的解雇

时间:2018-07-06 22:35:54

标签: scala akka actor

我有一个akka系统,基本上是两个生产者参与者,它们将消息发送给一个消费者参与者。以简化的形式,我有这样的东西:

class ProducerA extends Actor {
    def receive = {
        case Produce => Consumer ! generateMessageA()
    }

    ... more code ...
}

class ProducerB extends Actor {
    def receive = {
        case Produce => Consumer ! generateMessageB()
    }

    ... more code ...
}

class Consumer extends Actor {
    def receive = {
        case A => handleMessageA(A)
        case B => handleMessageB(B)
    }

    ... more code ...
}

他们都是同一个akka系统的兄弟姐妹。

我试图弄清楚如何正常终止该系统。这意味着在关机时,我希望ProducerAProducerB立即停止,然后希望Consumer完成对消息队列中剩下的所有消息的处理,然后关机。

我想要的是Consumer演员能够监视ProducerAProducerB的终止。通常,似乎我想要的是在两个生产者都停止之后能够向PoisonPill发送Consumer消息。

https://alvinalexander.com/scala/how-to-monitor-akka-actor-death-with-watch-method

上面的教程很好地解释了一个演员如何监视另一个演员的终止,但是不确定一个演员如何监视多个演员的终止。

3 个答案:

答案 0 :(得分:1)

import akka.actor._
import akka.util.Timeout
import scala.concurrent.duration.DurationInt

class Producer extends Actor {
  def receive = {
    case _ => println("Producer received a message")
  }
}

case object KillConsumer

class Consumer extends Actor {

  def receive = {
    case KillConsumer =>
      println("Stopping Consumer After All Producers")
      context.stop(self)
    case _ => println("Parent received a message")
  }

  override def postStop(): Unit = {
    println("Post Stop Consumer")
    super.postStop()
  }
}

class ProducerWatchers(producerListRef: List[ActorRef], consumerRef: ActorRef) extends Actor {
  producerListRef.foreach(x => context.watch(x))
  context.watch(consumerRef)
  var producerActorCount = producerListRef.length
  implicit val timeout: Timeout = Timeout(5 seconds)
  override def receive: Receive = {
    case Terminated(x) if producerActorCount == 1 && producerListRef.contains(x) =>
      consumerRef ! KillConsumer

    case Terminated(x) if producerListRef.contains(x) =>
      producerActorCount -= 1

    case Terminated(`consumerRef`) =>
      println("Killing ProducerWatchers On Consumer End")
      context.stop(self)

    case _ => println("Dropping Message")
  }

  override def postStop(): Unit = {
    println("Post Stop ProducerWatchers")
    super.postStop()
  }
}

object ProducerWatchers {
  def apply(producerListRef: List[ActorRef], consumerRef: ActorRef) : Props = Props(new ProducerWatchers(producerListRef, consumerRef))
}

object AkkaActorKill {
  def main(args: Array[String]): Unit = {
    val actorSystem = ActorSystem("AkkaActorKill")
    implicit val timeout: Timeout = Timeout(10 seconds)

    val consumerRef = actorSystem.actorOf(Props[Consumer], "Consumer")
    val producer1 = actorSystem.actorOf(Props[Producer], name = "Producer1")
    val producer2 = actorSystem.actorOf(Props[Producer], name = "Producer2")
    val producer3 = actorSystem.actorOf(Props[Producer], name = "Producer3")

    val producerWatchers = actorSystem.actorOf(ProducerWatchers(List[ActorRef](producer1, producer2, producer3), consumerRef),"ProducerWatchers")

    producer1 ! PoisonPill
    producer2 ! PoisonPill
    producer3 ! PoisonPill

    Thread.sleep(5000)
    actorSystem.terminate
  }
}

可以使用 ProducerWatchers 演员来实现,该演员管理被杀死的生产者,一旦所有生产者被杀死,您就可以杀死Consumer演员,然后杀死ProducerWatchers演员。

答案 1 :(得分:1)

演员可以通过多次调用context.watch来观看多个演员,每次调用都传递不同的ActorRef。例如,您的Consumer演员可以通过以下方式观看Producer演员的终止:

case class WatchMe(ref: ActorRef)

class Consumer extends Actor {
  var watched = Set[ActorRef]()

  def receive = {
    case WatchMe(ref) =>
      context.watch(ref)
      watched = watched + ref
    case Terminated(ref) =>
      watched = watched - ref
      if (watched.isEmpty) self ! PoisonPill
    // case ...
  }
}

Producer的两个参与者都将各自的引用发送到Consumer,后者随后将监视Producer参与者的终止。当Producer个角色都终止时​​,Consumer将一个PoisonPill发送给自己。由于PoisonPill is treated like a normal message in an actor's mailboxConsumer将处理所有已经排队的消息,然后再处理PoisonPill并自行关闭。

Akka文档中提到的Derek Wyatt's "Shutdown Patterns in Akka 2" blog post中描述了类似的模式。

答案 2 :(得分:0)

所以我最终使用的解决方案受到Derek Wyatt's terminator pattern

的启发
val shutdownFut = Future.sequence(
  Seq(
    gracefulStop(producerA, ProducerShutdownWaitSeconds seconds),
    gracefulStop(producerB, ProducerShutdownWaitSeconds seconds),
  )
).flatMap(_ => gracefulStop(consumer, ConsumerShutdownWaitSeconds seconds))

Await.result(shutdownFut, (ProducerShutdownWaitSeconds seconds) + (ConsumerShutdownWaitSeconds seconds))

这或多或少正是我想要的。消费者关闭等待生产者根据期货的履行而关闭。此外,整个关机本身会带来一个未来,您可以等待,因此能够将线程保持足够长的时间,以便一切正常进行清理。