Erlang/OTP 消息可靠吗?消息可以复制吗?

Posted

技术标签:

【中文标题】Erlang/OTP 消息可靠吗?消息可以复制吗?【英文标题】:Are Erlang/OTP messages reliable? Can messages be duplicated? 【发布时间】:2011-03-11 11:52:52 【问题描述】:

加长版:

我是 erlang 的新手,正在考虑将它用于可扩展的架构。我发现该平台的许多支持者都在吹捧它的可靠性和容错性。

但是,我很难准确理解在这个消息在瞬态内存中排队的系统中是如何实现容错的。我知道可以安排主管层次结构来重生已故的进程,但是我一直无法找到很多关于重生对进行中的工作的影响的讨论。正在运行的消息和在垂死节点上丢失的部分完成工作的工件会发生什么情况?

当消费者进程死亡时,所有生产者是否会自动重新传输未确认的消息?如果不是,这怎么能被认为是容错的?如果是这样,是什么阻止了已处理但未完全确认的消息被重新传输,从而不适当地重新处理?

(我认识到这些问题并不是 erlang 独有的;在任何分布式处理系统中都会出现类似的问题。但 erlang 爱好者似乎声称该平台让这一切变得“简单”..?)

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

短版:

分布式 erlang 进程是否会受到重复消息的影响?如果是这样,重复保护(即幂等性)是应用程序的责任,还是 erlang/OTP 以某种方式帮助我们解决这个问题?

【问题讨论】:

我的理解是,当一个进程死亡并被重生时,它所做的任何没有被传回的事情都需要重做。 很明显,未完成的工作需要重做......我认为您的意思是重新启动未完成的任务是应用程序的责任(大概是通过跟踪完成和重新发送失败的消息)。你是这个意思吗?有人可以从经验中证实这一点吗? 【参考方案1】:

我认为答案与 Erlang 完全无关。它存在于客户端-服务器交互的语义中,您可以选择在客户端-服务器协议中实现“至少一次”、“最多一次”或“恰好一次”保证。 所有这些调用语义都可以通过在发送或执行之前在客户端和服务器上组合唯一的标签、重试和记录客户端请求来实现,以便在崩溃后可以被服务器拾取。 除了重复之外,您还可能丢失、孤立或延迟消息。

【讨论】:

【参考方案2】:

我将把它分成我希望有意义的点。我可能会重新散列我在The Hitchhiker's Guide to Concurrency 中写的一些内容。您可能想阅读那篇文章以详细了解 Erlang 中消息传递方式背后的基本原理。


1.消息传输

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

这方面的一个随机示例可能是想象一个长时间运行的进程,它处理数据 4 小时。如果它无法处理它,它真的应该承认它收到了一条消息吗?也许应该,也许不应该。这实际上取决于您的应用程序。因此,不作任何假设。您可以让一半的消息异步,而只有一个不是。

Erlang 希望您在需要时发送确认消息(并在超时后等待)。与超时有关的规则和回复的格式留给程序员指定——Erlang 不能假设你想要消息接收的确认,当一个任务完成时,它是否匹配(消息热加载新版本代码时可能会在 4 小时内匹配)等。

简而言之,消息是否未被阅读、无法接收或在传输过程中被某人拔掉插头都无关紧要。如果你想让它变得重要,你需要设计一个跨进程的逻辑。

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


2.消息协议

正如您所说,这些消息存储在临时内存中:如果一个进程死亡,它尚未读取的所有消息都将丢失。如果你想要更多,有各种策略。其中一些是:

尽可能快地读取消息并在需要时将其写入磁盘,发送回确认并稍后处理。将此与具有持久队列的 RabbitMQ 和 ActiveMQ 等队列软件进行比较。 使用进程组在多个节点上的一组进程之间复制消息。此时您可能会输入事务语义。这个用于事务提交的 mnesia 数据库; 在收到一切正常的确认或失败消息之前,不要假设任何事情都有效 进程组和失败消息的组合。如果第一个进程无法处理任务(因为节点关闭),VM 会自动向故障转移进程发送通知,由该进程代替处理它。此方法有时与完整的应用程序一起使用以处理硬件故障。

根据手头的任务,您可能会使用其中的一种或多种。它们都可以在 Erlang 中实现,并且在许多情况下,已经编写了模块来为您完成繁重的工作。

所以这可能会回答您的问题。 因为您自己实施协议,所以您可以选择是否多次发送消息。


3.什么是容错

选择上述策略之一确实取决于容错对您意味着什么。在某些情况下,人们的意思是说“没有数据丢失,没有任务失败”。其他人使用容错说“用户永远不会看到崩溃”。在 Erlang 系统的情况下,通常的含义是保持系统运行:可以让单个用户挂断电话而不是让所有人挂断电话。

这里的想法是让失败的东西失败,但让其余的继续运行。为此,VM 为您提供了一些功能:

您可以知道进程何时终止以及为什么会终止 如果其中一个出现问题,您可以强制相互依赖的进程一起终止 您可以运行一个记录器,自动为您记录每个未捕获的异常,甚至定义您自己的 可以监控节点,以便您知道它们何时出现故障(或断开连接) 您可以重新启动失败的进程(或失败的进程组) 如果一个节点出现故障,让整个应用程序在不同节点上重新启动 还有更多关于 OTP 框架的内容

使用这些工具和一些标准库的模块为您处理不同的场景,您可以在 Erlang 的异步语义之上实现几乎您想要的东西,尽管能够使用 Erlang 的容错定义通常是值得的。


4.几点说明

我个人的观点是,除非你想要纯事务语义,否则很难有比 Erlang 中存在的假设更多的假设。您总是会遇到的一个问题是节点出现故障。您永远无法知道它们是否因服务器实际崩溃或网络故障而关闭。

在服务器崩溃的情况下,只需重新执行任务就足够简单了。但是,对于净拆分,您必须确保一些重要操作不会重复执行,但也不会丢失。

它通常归结为CAP theorem,它基本上为您提供了 3 个选项,您必须从中选择两个:

    一致性 分区容错 可用性

根据您的定位,需要不同的方法。 CAP 定理通常用于描述数据库,但我相信每当您在处理数据时需要一定程度的容错时,都会提出类似的问题。

【讨论】:

我们需要一种方法来捐赠我们的一些积分,以便将答案提高 +1 以上。这里的工作非常出色。【参考方案3】:

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

    当进程终止时,该进程将重新启动。 在大多数情况下,进程崩溃不会导致整个应用崩溃 发送消息后,只要接收者存在,就会收到该消息。

据我所知,erlang 中的消息不会重复。如果您发送消息并且进程接收到消息,则该消息将从队列中消失。但是,如果您发送一条消息并且该进程收到该消息但在处理它时崩溃,那么该消息将消失并且未处理。在您的系统设计中应该考虑这一事实。 OTP 通过使用进程将基础设施关键代码(例如主管、gen_servers 等)与可能会崩溃的应用程序代码隔离开来,帮助您处理所有这些问题。

例如,您可能有一个将工作分派到进程池的 gen_server。池中的进程可能会崩溃并重新启动。但是 gen_server 仍然处于运行状态,因为它的全部目的只是接收消息并将它们分派到池中进行处理。这允许整个系统在池中出现错误和崩溃的情况下保持正常运行,并且总有一些东西在等待您的消息。

仅仅因为系统是容错的并不意味着你的算法是。

【讨论】:

以上是关于Erlang/OTP 消息可靠吗?消息可以复制吗?的主要内容,如果未能解决你的问题,请参考以下文章

我可以在 C 节点中获得 Erlang OTP 行为吗?

Erlang / OTP App 的 handle_info 没有收到 nodedown, _, _ 或 nodedown, _ 消息

如何在Erlang / OTP中构建MQ使用者循环?

Erlang 通过 TLS 的分布式

EMQ --集成搭建

[emqttd] (EMQ)