Akka手册译(一)——消息传递的可靠性

Akka帮助您构建可靠的应用程序在一台机器上使用多个处理器核心(“扩大”)或分布在计算机网络(“扩张”)。关键的抽象使所有交互者在代码单元——Actor——发生在消息传递过程中,这就是为什么在参与者之间传递消息时以精确的词义得到它们的内容。

以下的讨论涉及到考虑到应用拆分到多个网络主机上。不论发送到在本地JVM的Actor或远程的Actor基本的通信机制是一致的,当然可以观察到交付信息的延迟不同(可能取决于网络链路的带宽和消息大小))和可靠性。对于远程消息发送显然有更多的步骤,这意味着更多的出错可能性。另一个方面是,本地传递只会在同一个JVM中通过引用信息,没有任何限制底层对象发送,而远程运输将受消息大小限制。

编写Actor时采用悲观策略以使每个交互者远程是安全的。这只有依靠属性来保证,并在下面详细讨论这些内容。有一些开销发生在Actor实现时。如果乐意放弃的透明传输比如用一组紧密的Actor做样例,把它们在同一个JVM只使消息传递完全可靠。下面将进一步权衡这方面细节。

作为一种补充部分,我们将涉及到一些顶级内置部分建立更高可靠性。这章结尾将讨论一下“死信机制”。

一般的规则

这些是消息发送的规则(即tell或!方法,也构成询问模式):

  • 至多一次交付,例如非担保的交付;
  • 发送者-接收者每个消息有序化成对;
    第一条规则通常发现在其它Actor实现,当第二条Akka特定于它们。

讨论:至多一次交付是含义

描述交付机制的语义有三类:

  • 至多一次交付指每个消息的机制是1次或0次交付,更直白的说消息可能会丢失。
  • 至少一次交付指每个消息的机制是尝试多次交付,并有一次成功。更直白的说消息会重复但不会丢失。
  • 只一次交付指每个消息只会交付给接收者一次,消息可能即不会被复制也不重复。
    第一个代价最低——高性能,低开销——因为它实现即发即弃的方式不在发送端或传输状态上保持状态。第二个需要累计传输失败量,这意味着发送端保持状态以及接收端实现确认机制。第三个代价最高,之所以性能最差,因为要保持接收端的状态还要过虑掉重复数据。

讨论:为什么用非担保交付

以上这个问题的核心是担保表示:

  1. 网络上的消息发送出去吗?
    2.消息在其他主机接收吗?
  2. 消息放到目标Actor的邮箱了吗?
  3. 目标Actor开始处理消息了吗?
  4. 目标Actor的消息处理成功了吗?
    这些的每一个都有各自的挑战和开销,很明显在消息处理库中有条件的将无法完成。考虑例子的配置邮箱类型以及一个邮箱的边界如何与第三点相互,甚至决定以上五个点的“成功”是什么。

沿这几点的推理得到“Nobody Needs Reliable Messaging.”发送方知道的接收业务级别的确认消息成功的唯一意义在于,Akka不是可以自己组成的。(这不是一个想怎么做就能怎么做的框架)。

Akka依赖分布式并使用不可靠的通信传递消息,因此它不保存可以想像成漏水一样。这个模型已经在Erlang中的取得了巨大的成功,用户围绕它来设计他们的应用程序。可以阅读更多关于这种方法在Erlang文档(10.9和10.10节),Akka密切关注它。

另一个角度看这个问题,它只提供基本保障的用例不需要更强的可靠性不支付的成本实施;它总是可以添加更强的可靠性的基本内容上,但这是不可能的倒行逆施的删除为了获得更多的性能可靠性。

讨论:消息排序

更具体的说,对于给定的两个Actor,消息收到时不会无序。这强调了只能保证应用在发送时明确发送源和目标时,而不是使用介质或其他信息传递特性(除非另有说明)。
如下例这样保障:

Actor A1发送消息 M1,M2,M3 到 A2
Actor A3发送消息 M4,M5,M6 到A2
这意味着:

  1. M1必须在M2和M3之前交付;
  2. M2必须在M3之前交付;
  3. M4必须在M5和M6之前交付;
  4. M5必须在M6之前交付;
  5. A2能从A1和A3交叉看到消息;
  6. 由于不是保证交付,任何消息都可能抛下,即没有到达A2

注意
Akka保证适用于消息队列的顺序进入收件人的邮箱。如果邮箱遵循FIFO实现顺序(例如PriorityMailbox),然后处理顺序的Actor将偏离入队秩序。

注意这些规则是不可达的

Actor A发送消息M1 到Actor C
Actor A 然后发送消息M2 到Actor B
Actor B转递消息M2到Actor C
Actor C可能以任何顺序收到M1和M2

由于可达顺序是M2在M1在Acotr C收到后再收到(尽管其中任何一个可能丢失)。这个顺充可能不确定因不同消息延迟,在A,B,C在不同的网络主机上。请参阅下文。

注意
Actor创建被视为一个消息从父级发送到子级,如同上面所讨论的。在发送消息到Actor时能重新在初始化时重新排序就意味着消息没有到达因为Actor还没生成。举个例子发送一个消息从R2引用发送过来时,消息可能太早到而不能创建远程布署的Actor R1时。更好的定义顺序是创建Actor后立即发送一个消息给它。

通讯失败

请注意以上两个Actor间保证顺序的讨论仅限于用户消息。Actor的子级通信是特别的系统消息,与用户消息的顺序无关。特别是:

子Actor C发送消息M到它的父级P
子Actor F处理失败
父Actor P可能收到两个事件顺序M,F或F,M

原因是内部系统消息有自己的邮箱调用用户排队的顺序和系统的消息不能保证出列的订购时间。

JVM内(本地)消息发送规则

留意正下这节所要做的

在这一节中依赖较强的可适应性是不可取的,从应用程序绑定到本地布署上。应用程序被设计的不同(不仅仅是一些消息交换模式和一些Actor)以适应一个集群上运行的机器。我们的信条是一次设计,任意布署。而要实现这一点,人应该只依赖于一般规则。

本地发送的可靠性

Akka测试套件的依赖在本地的上下文中没有丢失的消息(及远程开发没有错误测试),这意味着我们努力保持测试稳定。本地tell操作可能因为一些原因的错误就如同通常在JVM中调用的方法。

  • StackOverflowError
  • OutOfMemoryError
  • other VirtualMachineError
    此外,本地发送有特定的Akka方法使发送时出错:
  • 比如邮箱没有接收到消息(如,BoundedMaibox满了)
  • 接收的actor处理失败或已经终止
    当排除每一个错误的配置引起第二个时,第二个消息得不到反馈在处理异常。通知会被它的主客代替。这是一般不区分外部观察者的失去了消息

本地消息发送顺序

上述警告的不及物的消息假定在严格的FIFO邮箱中在特定条件下被消除。会注意到,这些很细微甚至涉及到未来优化整个段落。这可能是下列不完整的几点:

  • 在收到顶级的Actor回复前,有一个内部锁保护内部监时队列,这把锁不是直接的。这意味着排队请求期间从不同的发送者到acotr的结构(比如,细节更复杂)可能被重新排序根据底层线程调度。由于不存在完全公平锁在JVM上,这是认识上的误区。
  • 使用相同的机制在建设一个路由器,更精确地ActorRef路由,因此Actor与路由器部署存在同样的问题。
  • 如前所述,发生任何锁的问题是涉及在入队,这可能也适用于自定义邮箱。
    这个列表已经仔细编制,但其他问题场景可能逃脱了我们的分析。

本地排除和网络排序如何实施

对于一个给定的规则对Actor、消息发送直接从第一个到第二个不会收到无序适用于通过网络发送的消息与基于TCP的Akka远程传输协议。
在前一节中解释当地消息发送服从传递因果顺序在特定条件下。这个命令可以违反了由于不同的消息传递延迟。例如:

node-1上Actor A发送消息M1到node-3上的actor C
然后node-1的Actor A上发送消息M2到node-2的Actor B
node-2上ActorB转发消息M2到node-3的 Actor C
Actor C 可能以任何顺序收到M1 和 M2

M1可能花很长时间旅行到node-3 比M2旅行经过node-3经过node-2

高级抽象

Akka提供强大的、更高层次的抽象基于一个微小而持续的Akka核心工具集。

消息传递模式

正如上面所讨论的可靠传递的要求是一个显式的ACK-RETRY协议。在其最简单的形式要求

  • 一种识别个人信息和确认相关信息
  • 重试机制,如果不确认将重新发送消息
  • 为接收器检测和丢弃重复
    第三种成为必要借助确认不必要到达。ACK-RETRY协议与业务级别的确认支持“至少一次”的Akka持久模块交付。副本可以检测到跟踪消息的标识符通过“至少一次”交付。另一种实现第三部分将使处理消息幂等层面的业务逻辑。

实现这三个需求的另一个例子是在可靠的代理模式(这是现在取代“至少一次”交付)。

事件源

事件源(和共享)用于制造大型网站规模数十亿的用户,这个想法非常简单:当一个组件(Actor)过程命令将生成一个事件列表代表命令的效果。这些事件存储除了应用于组件的状态。这个方案的优点是,事件只添加到存储,没有什么是永远的突变;这使得完美的复制和扩展消费者的事件流(即其他组件可以使用事件流来复制组件的状态在一个不同的容器或反应的变化)。如果组件的状态因为机器故障或被排挤出缓存它可以很容易地重建重演了事件流(通常采用快照来加快这一进程)。事件源支持Akka持久性。

邮箱的明确确认

通过实现一个自定义邮箱类型可以重试的消息处理接收Actor的一端为处理临时失败。此模式主要是有用的本地通信上下文交付担保否则足以满足应用程序的需求。

请注意,规则的警告在jvm(本地)消息发送申请。

实现这种模式的一个例子是显示在邮箱与明确的确认。

死信

消息不能交付(这可以确定)将交付给一个叫做 /deadLetters合成的Actor。这交付发生在力所能及;它可能会失败甚至在本地JVM(例如在Actor终止)。通过发送的消息不可靠的网络传输将丢失没有出现死亡的信件。

死信用于哪些方面

这个设备的主要用途是为调试,特别是如果一个Actor发送与到达不一致(通常检查死者字母会告诉你,发送方或接收方设置错了沿途某处)。为了有效使用它尽可能避免发送deadLetters,即运行您的应用程序与一个合适的死信记录器(参见下面的更多)不时和清理日志输出。这种需要明智的应用常识:很可能是避免发送Actor终止,使发送方的代码更清晰。

死信服务遵循相同的规则对交付担保和其他消息发送,因此它不能用于实现保证交付。

如何接收死信

Actor可以订阅akka.actor.DeadLetter在事件流,Event Stream显示如何使用它。订阅的Actor将会收到所有死信件发表在这一点的本地系统。死信不是通过网络传播,如果想收集在一个地方要订阅一个Actor/手动网络节点和转发。死信在这个节点生成可以确定发送操作失败,可以本地系统为远程发送(如果没有可以建立网络连接)或远程(如果你发送的Actor不存在在这个时间点)。

死信不需要担心

每一次Actor不会由自已决定停止,因为有一可能它自已发送丢失。一些复杂的自动关闭场景是良性的:看到akka.dispatch.Terminate。终止消息意味着两个终止请求,当然只有一个能成功。同样,你可能会看到akka.actor.Terminated。但父级看孩子时终止消息Actor的层次结构在死信从孩子开始。

上一篇
下一篇

推荐阅读更多精彩内容