带有永久邮箱的Akka无状态演员

时间:2019-07-15 13:07:42

标签: akka actor akka-cluster akka-persistence

我想创建一个具有数千名演员的Akka群集。每个演员都会收到一条消息,进行一些计算,并将结果写入一个专门的Kafka主题。

它应该部署在集群中,例如Kubernetes。

我的理解是-如果actor被终止的原因是什么(JVM崩溃,重新部署或其他原因),那么其邮箱的内容以及当前正在处理的消息都将丢失!

在我的情况下,这是完全不可接受的,因此我想实现一种拥有永久邮箱的方法。请注意,参与者本身是无状态的,他们不需要重播消息或重建状态。我需要做的就是在演员终止后不要丢失消息。

问题是:推荐的方法是什么?他们建议实施持久性参与者的Herehere。但是就像我说的那样,我不需要坚持并恢复参与者的任何状态。我应该实现基于持久性存储(例如SQL数据库)的自定义邮箱吗?

我还看到,在某些版本的Akka支持“耐用”邮箱之前,这似乎是我所需要的。但是出于某种原因,他们将其删除了,这很令人困惑...

2 个答案:

答案 0 :(得分:2)

client 上使用持久性参与者是对此类需求的建议。我了解您说的是接收方不需要持久性/状态性,但是通过在客户端上使用持久性,您可以重试接收方是否终止,或者使用开箱即用的保证消息传递功能来确保已处理。本质上,正在使用持久性(在客户端)持久化所发出的请求,以便客户端可以在必要时重新发送消息以“重建邮箱”。

使用客户端持久性是:

  • 性能比持久邮箱
  • 针对更多的故障情况提供保护(例如,在网络层丢弃的消息,应用程序逻辑中的故障)
  • 更灵活并支持更多类型的恢复(例如:仅需要恢复某些消息的方案)

这就是为什么将永久邮箱从Akka中删除的原因:Akka永久/至少保证一次交付在本质上比永久邮箱在所有方面都是更好的解决方案。

stikkos使用Kafka的答案也是可行的。我只是担心引入Kafka会增加很多复杂性。当然,任何持久性存储都会增加复杂性,所以我想这仅取决于您已经具备的功能。

答案 1 :(得分:1)

您可以使用Kafka实现所需的功能。 Kafka主题是持久性的(如果将Kafka中的保留时间设置为forever或对某个主题启用日志压缩,则数据将“一直”保留,或者您可以在Kafka之外存储偏移量)。

使用Akka Streams,您将在广播所产生的消息(在产生主题上)之后提交(在接收主题上)所接收到的消息,从而给您“至少一次”的传递语义。(对于“一次”,您可以查看Kafka Transactions)

这是Alpakka Kafka文档中的示例:

Consumer.DrainingControl<Done> control =
    Consumer.committableSource(consumerSettings, Subscriptions.topics(topic))
        .map(
            msg ->
                ProducerMessage.single(
                    new ProducerRecord<>(targetTopic, msg.record().key(), msg.record().value()),
                    msg.committableOffset() // the passThrough
                    ))
        .via(Producer.flexiFlow(producerSettings))
        .map(m -> m.passThrough())
        .toMat(Committer.sink(committerSettings), Keep.both())
        .mapMaterializedValue(Consumer::createDrainingControl)
        .run(materializer);

您可以通过几种方式将其与Actor(集群池)集成。最简单的方法是使用Ask模式。在这种情况下,流会将消息传递给必须在预定时间内回复的演员(可能是self())。收到回复后,将在提交原始消息之前在目标流上广播。

这看起来像:

Consumer.DrainingControl<Done> control =
    Consumer.committableSource(consumerSettings, Subscriptions.topics(topic))
            .mapAsync(1, msg -> 
                Patterns.ask(actor, msg, Duration.ofSeconds(5))
                    .thenApply(done ->
                        ProducerMessage.single(
                                new ProducerRecord<>(targetTopic, done.key(), done.value()),
                                msg.committableOffset() // the passThrough
                        )
                    )
            )
            .via(Producer.flexiFlow(producerSettings))
            .map(m -> m.passThrough())
            .toMat(Committer.sink(committerSettings), Keep.both())
            .mapMaterializedValue(Consumer::createDrainingControl)
            .run(materializer);

如果您有多个可同时处理消息的参与者,则还可以增加mapAsync调用的并行度。