Erlang / OTP消息是否可靠? 消息是否可以重复?

长版本:

我是erlang的新手,并考虑将其用于可扩展的体系结构。 我发现这个平台的许多支持者都鼓吹其可靠性和容错性。

然而,我很难理解在这个系统中如何实现容错,在这个系统中消息在瞬态存储器中排队。 我明白,可以安排一个主pipe层级来重生死亡的进程,但是我一直没有find关于重新进行的进程的影响的讨论。 飞行中的消息和部分完成的工作在垂死的节点上丢失了什么?

当消费者stream程死亡时,所有的生产者是否都会自动转发消息? 如果不是的话,这怎么可以被认为是容错? 如果是这样,什么阻止了一个被处理的消息 – 但不是很确认 – 被转发,因此被不适当地重新处理?

(我认识到这些问题并不是erlang所特有的,在任何分布式处理系统中都会出现类似的问题,但是erlang的爱好者似乎声称这个平台使得这一切都变得“容易”

假设消息被重新传输,我可以很容易的想象一个情况,即一个复杂的消息传递链的下游影响可能在故障后变得非常混乱。 如果没有某种沉重的分布式事务系统,我不明白如何在不重复每一个过程的情况下保持一致性和正确性。 我的应用程序代码是否必须始终强制执行约束以防止事务被多次执行?

简洁版本:

分布式erlang进程是否受到重复消息的影响? 如果是这样,重复保护(即幂等)应用责任,或者erlang / OTP以某种方式帮助我们呢?

我将把这个分成几个观点,我希望这个观点是有道理的 我可能会重新散列一下我在“并发旅程指南”中所写的内容 。 您可能需要阅读那篇文章,以获取有关在Erlang中完成消息传递的基本原理的详细信息。


1.消息传输

通过Erlang传递的消息是通过发送到邮箱(一种用于存储数据的队列)的asynchronous消息完成的。 关于消息是否被接收,甚至是否被发送到有效的进程是完全没有假设的。 这是因为在语言层面上假设有人可能只想在4天内处理一条信息,甚至在达到某个特定状态之前就不会承认它的存在。

一个随机的例子可以想象一个长时间运行的过程,将数据压缩4个小时。 它是否真的承认它收到一条消息,如果它无法对待它? 也许它应该,也许不是。 这真的取决于你的应用程序。 因此,没有任何假设。 你可以有一半的消息是asynchronous的,只有一个不是。

如果您需要,Erlang希望您发送确认消息(并等待超时)。 与超时有关的规则和应答的格式留给程序员来指定 – Erlang不能假设你想要在消息接收,任务完成时,是否匹配(消息当新版本的代码热载入时,可以在4小时内匹配)等等。

为了简短起见, 不pipe消息是否被读取,接收失败,或者在运输过程中由于拔下插头而中断,如果你不想要的话,并不重要。 如果你想要它,你需要devise一个跨进程的逻辑。

在Erlang进程之间实现高级消息协议的负担是给程序员的。


2.消息协议

正如你所说的那样,这些消息被存储在瞬态存储器中:如果一个进程死了,所有没有被读取的消息都将丢失。 如果你想要更多,有各种策略。 其中一些是:

  • 尽可能快地读取消息并在需要时将其写入磁盘,然后发送确认并稍后处理。 比较这个队列与持久队列队列软件,如RabbitMQ和ActiveMQ。
  • 使用进程组跨多个节点上的一组进程复制消息。 此时您可以input事务语义。 这个用于mnesia数据库的事务提交;
  • 不要以为任何事情都能奏效,除非你收到确认一切正常或失败的消息
  • 进程组和失败消息的组合。 如果第一个进程无法处理任务(因为该节点closures),则VM会自动将通知发送到故障切换进程,而该进程将处理该进程。 此方法有时用于完整的应用程序来处理硬件故障。

根据手头的任务,您可以使用其中的一个或多个。 他们都可以在Erlang中实现,而且在很多情况下,模块已经被编写来为你做繁重的工作。

所以这可能会回答你的问题。 由于您自己实现协议,因此您的select是否发送消息不止一次。


3.什么是容错

select上述策略之一取决于您的容错方式 。 在某些情况下,人们的意思是说“没有数据丢失,没有任何任务失败”。 其他人使用容错来表示“用户从不会看到崩溃”。 在Erlang系统的情况下,通常的含义是保持系统正常运行:也许有一个用户打电话而不是让所有人都放弃它。

这里的想法是让失败的东西失败,但保持其余的运行。 为了达到这个目标,虚拟机为你提供了一些东西:

  • 你可以知道一个进程何时死亡,为什么死亡
  • 如果其中一个出错,你可以强制相互依赖的进程一起死亡
  • 你可以运行一个logging器,自动为你logging每个未捕获的exception,甚至定义你自己的exception
  • 可以对节点进行监控,以便知道何时断开(或断开连接)
  • 您可以重新启动失败进程(或失败进程组)
  • 如果发生故障,整个应用程序将在不同节点上重新启动
  • OTP框架还有更多的东西

使用这些工具和一些标准库的模块为您处理不同的场景,您可以在Erlang的asynchronous语义之上实现您想要的东西,尽pipe通常可以使用Erlang的容错定义。


4.几个笔记

我个人认为,除非你想要纯粹的事务语义,否则在Erlang中存在更多的假设是相当困难的。 一个问题,你总是会遇到麻烦是节点下降。 你永远不知道是否因为服务器崩溃或networking故障而停机。

在服务器崩溃的情况下,只需重新完成任务就够了。 然而,净分裂,你必须确保一些重要的操作不会做两次,但也不会丢失。

它通常归结为CAP定理 ,基本上给你3个选项,其中你必须select两个:

  1. 一致性
  2. 分区容忍
  3. 可用性

根据你自己的位置,将需要不同的方法。 CAP定理通常用于描述数据库,但是我相信在处理数据时,只要需要一定程度的容错,就会问到类似的问题。

erlang OTP系统是容错的。 这并不能解除您需要在其中构build同样容错的应用程序。 如果你使用erlang和OTP,那么你可以依靠一些东西。

  1. 当一个进程死亡时,进程将重新启动。
  2. 在大多数情况下,进程崩溃不会导致您的整个应用程序
  3. 发送消息时,只要接收方存在,就会收到消息。

据我所知在erlang消息不会重复。 如果您发送消息并且进程收到消息,则消息将从队列中消失。 但是,如果您发送消息,并且该进程接收到该消息,但在处理消息时发生崩溃,则该消息将消失,并且未处理。 这个事实应该在你的系统的devise中考虑。 OTP可以帮助您处理所有这些问题,方法是使用进程将应用程序代码中的基础架构关键代码(例如,supervisors,gen_servers等)隔离,以防崩溃。

例如,你可能有一个gen_server将工作分派给进程池。 池中的进程可能会崩溃并重新启动。 但是gen_server仍然保持运行,因为它的全部目的只是接收消息并将其发送到池中进行工作。 这样可以让整个系统保持运行,尽pipe在游泳池出现错误和崩溃,总是有一些东西在等着你的消息。

只是因为系统容错并不意味着你的algorithm是。

我觉得答案跟Erlang根本没有关系。 它在于客户端 – 服务器交互的语义,您可以select在您的客户端 – 服务器协议中实现“至less一次”,“至多一次”或“精确一次”保证。 所有这些调用语义都可以通过在发送或执行它们之前,在客户端和服务器上结合唯一标记,重试和logging客户端请求来实现,以便在服务器崩溃后被服务器拾取。 除了重复,你可能会丢失,孤立或延迟的消息。