Reliable delivery

警告
此模块当前被标记为可能会更改,因为它是一项新功能,需要根据实际使用反馈的情况确定最终API。这意味着API或语义可以更改,而不会发出警告或弃用期限。还不建议您在生产中立即使用此模块。

模块信息

要使用可靠的交付,请将模块添加到您的项目中:

介绍

正常的消息传递最多只能传递一次,这意味着消息可能会丢失。虽然很少出见,但仍然可能。

对于某些actors之间的交互,这是不可接受的,并且需要至少一次交付或有效处理一次。此处介绍的可靠交付工具有助于实现这一目标。没有应用程序的协作,它不可能自动实现。这是因为确认消息何时已被完全处理是业务层面的问题。仅确保将其通过网络传输或传递到actors的邮箱是不够的,因为actor可能在处理消息之前就崩溃了。

检测到丢失的消息,需要重新发送消息、删除重复消息。此外,消息发送需要流控,以避免生产者快速生产,淹没较慢的消费者,或以消息的发送速率超过网络传输的消息速度。这可能是actors交互中的常见问题,会导致致命错误,例如OutOfMemoryError,因为actor的邮箱中排队的消息过多。丢失消息的检测和流控是由用户方驱动的,这意味着生产方的发送速度不能快于消费方的要求。除非消费者方要求,否则生产者方不会重复推送。

支持三种模式,以下各节中进行了介绍:

  • 点对点
  • Work pulling
  • 分片

点对点

此模式在发送消息的单个生产者actor和接收消息的单个消费者actor之间,实现点对点的可靠传递。

消息从生产者发送到ProducerController并通过ConsumerController actor发送,后者处理目标消费者actor的传递和确认。

Reliable delivery_第1张图片

生产者actor将通过向ProducerController发送ProducerController.Start消息来开始流程。

ProducerController将RequestNext发送给生产者,然后允许生产者发送一条消息到ProducerController。此后,生产者再发送一条消息前,生产者将收到新的RequestNext。

Producer和ProducerController actor必须位于本地,以便快速且有保证地传递消息。通过运行时检查,强制执行此要求。

类似地,在消费者方,目标消费者actor将通过向ConsumerController发送初始的ConsumerController.Start消息来开始流程。

为了使ProducerController知道将消息发送到哪里,必须将它与ConsumerController连接。这可以通过ProducerController.RegisterConsumer或ConsumerController.RegisterToProducerController消息来完成。使用点对点模式时,应用程序有责任将它们连接在一起。例如,可以通过以普通消息的形式发送ActorRef到另一端,或者通过在接收者注册ActorRef以便在另一端找到它。

如果任何一方崩溃,您还必须采取措施重新连接它们,例如,观察它是否终止。

生产者发送的消息包装在onsumerController.Delivery中。当消费者接收到传递消息时,消费者在处理完消息后,应使用ConsumerController.Confirmed进行答复。

在前一条消息确认前,不会传递下一条消息。在等待确认之前,生产者的任何消息都由ConsumerController存放,并在前一个消息确认后传递。

与生产方相似,消费者和ConsumerControlleractor必须位于本地,以便快速且有保证地传递消息。通过运行时检查,强制执行此要求。

在ProducerController和ConsumerController之间可以传送许多未确认的消息,但是它们的数量受流控窗口的限制。流控由消费者驱动,这意味着ProducerController的发送速度不会超过ConsumerController的需求。

点对点示例

斐波那契数列生成器(生产者)的示例:

斐波那契数列消费者

FibonacciProducer将消息发送到ProducerController。 FibonacciConsumer从ConsumerController接收消息。 请注意,“Start ”消息中的ActorRef,构造为消息适配器,以分别将RequestNext和Delivery映射到生产者actor和消费者actor。

ConsumerController和ProducerController通过ConsumerController.RegisterToProducerController消息连接。 ProducerController的ActorRef可以在生产者和消费者方之间通过普通消息或通过使用Receptionist共享。 或者,可以通过将ProducerController.RegisterConsumer发送到ProducerController进行连接。

点对点传递语义

只要生产者和消费者都不崩溃,消息就按照发送到ProducerController的顺序传递给消费者actor,而不会丢失或重复。这意味着一次有效的处理,不需要任何业务层面去重。

如果生产者崩溃,未经确认的消息可能会丢失。为避免这种情况,您需要在生产端启用持久队列。这样,当再次启动相应的生产者时,将重新传送存储的未确认消息。即使使用相同的ConsumerController实例,也可能会传递已被处理的消息,被确认但尚未存储。这意味着我们至少传递一次。

如果消费者崩溃,则可以将新的ConsumerController连接到原始的ProducerConsumer,而无需重新启动它。然后ProducerConsumer将重新传递所有未确认的消息。在这种情况下,未经确认的消息将传递给新的消费方,并且其中一些可能已经由先前的消费者处理过。同样,这意味着我们至少可以传递一次。

work pulling

work pulling是一种模式,其中几个worker actor以自己的步调从共享的worker管理者拉出任务,而不是管理者盲目地将worker推给worker,不考虑workers的个人能力和当前可用性。

一个重要的特性是消息的顺序无关紧要,因为每条消息都是随机路由到有需求的一个worker。换句话说,可以将两个连续消息路由到两个不同的workers,并彼此独立地进行处理。

消息从生产者发送到WorkPullingProducerController并通过ConsumerController actor发送,后者对目标worker(消费者)actor中的处理进行交付、确认。
Reliable delivery_第2张图片

worker actor(消费者)及其消费者控制器通过ServiceKey动态注册到WorkPullingProducerController。它将注册到Receptionist,并且WorkPullingProducerController订阅相同的key以找到活动的workers。这样,可以在集群中的任何节点上动态添加或删除workers。

worker管理器(生产者)actor将通过向WorkPullingProducerController发送WorkPullingProducerController.Start消息来启动流程。

WorkPullingProducerController将RequestNext发送到生产者,然后允许生产者向WorkPullingProducerController发送一条消息。此后,生产者收到新的RequestNext后,再发送一条消息。当任何worker有需求时,WorkPullingProducerController将发送一个新的RequestNext。发送RequestNext之后,在实际消息发送到WorkPullingProducerController之前,所有有需求的workers都有可能被注销。在这种情况下,该消息将被缓冲,并在注册新worker或有新需求时传递。

Producer和WorkPullingProducerController actor应该位于本地,以便这些消息快速且不会丢失。这是通过运行时检查,强制执行。

类似地,在消费方,目标消费者actor将通过向ConsumerController发送初始的ConsumerController.Start消息来开始流程。

从生产者收到的消息被包装在ConsumerController中,发送给消费者,处理完消息后,答复ConsumerController.Confirmed。在前一条消息确认之前,不会传递下一条消息。 ConsumerController会存储来自生产者的,等待确认的消息,并在前一个消息确认后,将其传递。

消费者和ConsumerControlleractor应该是本地的,因此这些消息传递很快而且不会丢失。这是通过运行时检查,强制执行。

在WorkPullingProducerController和每个ConsumerController之间可能会传递许多未确认的消息,但是它受到流控窗口的限制。流控由用户端驱动,这意味着WorkPullingProducerController的发送速度不会超过workers要求的速度。

work pulling 例子

图像转换器worker(消费者)的示例:

图像转换job管理者(生产者)

请注意,“Start”消息中的ActorRef构造为消息适配器,以分别将RequestNext和Delivery映射到生产者actor和消费者actor的协议。

另请参见example that is using ask from the producer。

work pulling 交付语义

对于worker来说,消息有序并不重要,因为每条消息都会随机路由到有需求的workers之一,因此可以按任何顺序进行处理。

只要生产者和workers都不会崩溃(或由于其他原因而罢工),消息就不会丢失或重复发送给worker。意味着不需要业务级别去重,可以进行一次有效的处理。

如果生产者崩溃,未经确认的消息可能会丢失。为避免这种情况,您需要在生产者端启用持久队列。再次启动相应的生产者时,将重新发送已存储的未确认消息。这些消息可能会被路由到和以前不同的workers,并且其中一些消息可能已经被处理,但被确认的事实还没有存储起来。意味着至少一次交付。

如果某个worker崩溃或正常停止,则未经确认的消息将重新发送给其他worker。在这种情况下,其中一些可能已由先前的worker处理过。意味着至少一次交付。

分片

要在集群分片中使用可靠的交付,请在您的项目中添加以下模块:

在发送消息的生产者actor与接收消息的分片的消费者角色之间可靠地传递。

Reliable delivery_第3张图片

并发送给另一个实体

Reliable delivery_第4张图片

并从另一个生产者(不同节点)发送

Reliable delivery_第5张图片

ShardingProducerController应该与ShardingConsumerController一起使用。

生产者可以通过ShardingProducerController将消息发送到由entityId标识的任何ShardingConsumerController。每个ActorSystem(节点)可以共享一个ShardingProducerController,以便发送特定实体类型的所有实体。在ShardingConsumerController和ShardingProducerController之间不需要显式注册。

生产者actor将通过向ShardingProducerController发送ShardingProducerController.Start消息来开始流程。

ShardingProducerController将RequestNext发送到生产者,然后允许生产者向ShardingProducerController发送一条消息。此后,生产者收到新的RequestNext, 再发送一条消息。

在ShardingProducerController.RequestNext消息中,包含有关哪些实体有需求的信息。允许发送新的entityId, 该entityId不在RequestNext.entitiesWithDemand中。如果发送给不需要的实体,则该消息将被缓冲。这种对缓冲的支持意味着收到RequestNext后,允许发送多条消息,但建议仅发送一条消息并等待下一个RequestNext,然后再发送更多消息。

Producer和ShardingProducerController actor应该位于本地,以便这些消息快速且不会丢失。这是通过运行时检查,强制执行的。

类似地,在消费者方,目标消费者actor将通过向ConsumerController发送初始的ConsumerController.Start消息来开始流程。

每个实体将有一个ShardingConsumerController。 ShardingProducerController和每个ShardingConsumerController之间可能有许多未确认的消息在运行,但是它受到流控窗口的限制。流控由用户方驱动,这意味着ShardingProducerController的发送速度不会超过用户方的需求。

分片示例

分片实体是一个待办事项列表,当首先是当消费消息后,完成对可靠传递的答复,它使用异步调用数据库每次更改整个状态。

TodoList实体(消费者)示例:

TodoService (生产者)

请注意,“Start”消息中的ActorRef构造为消息适配器,以分别将RequestNext和Delivery映射到生产者actor和消费者actor的协议。

用这样的分片初始化(来自监护人):

分片交付语义

只要生产者和使用者都不会崩溃,消息按照发送到ShardingProducerController的顺序传递到消费者actor,而不会丢失或重复。意味着一次有效的处理,不需要任何业务级别的去重。

如果生产者崩溃,未经确认的消息可能会丢失。为避免这种情况,您需要在生产者端启用持久队列。再次启动相应的生产者时,将重新发送已存储的未确认消息。在这种情况下,可能会传递已经处理过的消息,但是没有存储被确认的事实。意味着至少一次交付。

如果使用者崩溃或分片重新平衡,则未经确认的消息将重新发送。在那种情况下,其中一些可能已由先前的消费者处理过。

持久化的生产者

在确认已发送的消息之前,生产者方将其保留在内存中以便能够重新发送它们。如果生产者端的JVM崩溃,则那些未确认的消息将会丢失。为了确保在这种情况下也可以传递消息,可以使用DurableProducerQueue。然后,未经确认的消息将以持久方式存储,以便在重新启动生产者时可以重新传递它们。akka-persistence-typed的EventSourcedProducerQueue提供了DurableProducerQueue的实现。

请注意,DurableProducerQueue将增大性能开销。

使用EventSourcedProducerQueue时,需要以下依赖项:

您还必须选择日记插件和快照存储插件,请参阅 Persistence Plugins。

启用了EventSourcedProducerQueue的Work pulling example 中的图像转换器工作管理器示例:

请务必注意,EventSourcedProducerQueue需要一个PersistenceId,该ID必须是唯一的。相同的PersistenceId不能同时用于不同的生产者。托管生产者的Cluster Singleton将满足该要求,或者每个节点一个生产者和一个命名方案,以确保不同的节点使用不同的PersistenceId。

要在崩溃后传递未确认的消息,必须使用与崩溃前相同的PersistenceId重新启动生产者。

生产者的Ask

生产者可以将context.ask与RequestNext中的askNextTo一起使用,而不是对TellNext中的sendNextTo使用tell。区别在于在处理完消息后将发送答复。要包括replyTo ActorRef,必须将消息包装在MessageWithConfirmation中。如果使用了DurableProducerQueue,则当消息已成功存储时,发送答复,但消费者可能尚未对其进行处理。否则,在消费者处理并确认消息后,发送答复。

在Work pulling example中的图像转换器工作管理器中使用Ask的示例:

仅流量控制

可以在不重新发送丢失的消息时使用此功能,但仍使用流控。 例如,当已知消费者和生产者都位于同一本地ActorSystem中时,这可能会很有用。 这样可以提高效率,因为在确认消息之前,不必将消息保留在ProducerController中的内存中,但是缺点是丢失的消息将无法传递。 请参阅ConsumerController的only-flow-control。

配置

有几个配置属性,请参考参考配置中的akka.reliable-delivery config部分:

  • akka-actor-typed reference configuration
  • akka-persistence-typed reference configuration
  • akka-cluster-sharding-typed reference configuration

你可能感兴趣的:(akka,cluster)