我有一个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系统的兄弟姐妹。
我试图弄清楚如何正常终止该系统。这意味着在关机时,我希望ProducerA
和ProducerB
立即停止,然后希望Consumer
完成对消息队列中剩下的所有消息的处理,然后关机。
我想要的是Consumer
演员能够监视ProducerA
和ProducerB
的终止。通常,似乎我想要的是在两个生产者都停止之后能够向PoisonPill
发送Consumer
消息。
https://alvinalexander.com/scala/how-to-monitor-akka-actor-death-with-watch-method
上面的教程很好地解释了一个演员如何监视另一个演员的终止,但是不确定一个演员如何监视多个演员的终止。
答案 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 mailbox,Consumer
将处理所有已经排队的消息,然后再处理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))
这或多或少正是我想要的。消费者关闭等待生产者根据期货的履行而关闭。此外,整个关机本身会带来一个未来,您可以等待,因此能够将线程保持足够长的时间,以便一切正常进行清理。