Apache Kafka和RabbitMQ:消息传递的语义和保证



我们准备了该系列文章的下一部分的翻译,该文章比较了Apache Kafka和RabbitMQ的功能。 该出版物涉及语义和消息传递保证。 请注意,作者考虑了Kafka直到0.10(包括0.10)版本,并且在0.11版本中出现了一次。 但是,该文章仍然具有实用性,并且从实用的角度讲到很多有用的观点。
先前的部分: 第一第二

RabbitMQ和Kafka均提供可靠的消息传递保证。 这两个平台均提供“最多一次交付”和“至少一次交付”的担保,但基于“严格一次交付”的原则,Kafka的担保在非常有限的情况下适用。

首先,让我们弄清楚这些保证的含义:

  • 最多一次交付。 这意味着邮件不能多次传递。 但是,该消息可能会丢失。
  • 至少一次交付。 这意味着该消息将永远不会丢失。 在这种情况下,邮件可以多次传递。
  • 一次交货。 消息传递系统的圣杯。 所有消息都只发送一次。

这里的“交货”一词可能不是一个确切的术语。 说“处理”会更准确。 无论如何,我们现在感兴趣的是消费者是否可以处理消息,以及发生这种情况的原则是:“不超过一个”,“至少一个”或“严格地一次”。 但是,“处理”一词使这种感觉复杂化,在这种情况下,“严格按照一次”的原则进行传递并不是一个准确的定义,因为可能需要两次传递消息才能对消息进行一次正确处理。 如果收件人在处理期间断开连接,则该消息必须再次发送给新的收件人。

第二个。 在讨论消息处理的问题时,我们来到了部分失败的话题,这对开发人员来说是一个头疼的问题。 在处理消息的过程中,有几个阶段。 它由应用程序和消息系统之间的开头和结尾之间的通信会话以及中间包含数据的应用程序本身组成。 应用程序部分失败的情况必须由应用程序本身处理。 如果执行的操作完全是事务性的,并且结果是根据“全有或全无”的原则制定的,则可以避免应用程序逻辑中的部分故障。 但是通常,很多阶段包括与其他系统的交互,在这些系统中,事务性是不可能的。 如果我们在交互中包括消息传递系统,应用程序,缓存和数据库之间的关系,是否可以保证“仅一次”处理? 答案是否定的。

“严格一次”策略仅限于以下情形:已处理消息的唯一接收者是消息传递平台本身,并且该平台本身提供完整的事务。 在这种有限的情况下,您可以处理消息,编写消息,发送信号,这些消息已作为按照“全有或全无”原则进行的事务处理的一部分进行处理。 它由Kafka Streams库提供。

但是,如果消息处理始终是幂等的,则可以避免通过事务“严格一次”实施策略的需求。 如果邮件的最终处理是幂等的,则可以担心接受重复项。 但是,并非所有动作都可以完全实现。

端到端警报

在我使用的所有消息传递系统的任何设备中未显示的是端到端确认。 鉴于消息可以在RabbitMQ中排队,所以端到端通知没有任何意义。 类似地,在Kafka上,几组不同的收件人可以同时从一个主题读取信息。 以我的经验,端到端警报是刚接触消息概念的人们经常会要求的。 在这种情况下,最好立即说明这是不可能的。

责任链

总体而言,邮件源无法知道其邮件已传递给收件人。 他们只能知道消息传递系统已收到消息并承担了确保其安全存储和传递的责任。 责任链从源头开始,经过消息传递系统,直到收件人。 每个人都应正确履行职责,并将信息清楚地传达给下一个。 这意味着,作为开发人员,您必须正确设计应用程序,以防止消息在您控制之下时丢失或误用。

讯息传送程序

本文主要致力于每个平台如何提供“至少一个”和“不超过一个”的发送策略。 但是仍然有一个消息传递顺序。 在本系列的前几部分中,我写了关于消息传输的顺序和消息处理的顺序,我建议您参考这些部分。

简而言之,RabbitMQ和Kafka都提供先进先出(FIFO)保证。 RabbitMQ在队列级别维护此顺序,在分段级别维护Kafka。 以前的文章中讨论了这种设计决策的含义。

RabbitMQ中的交货保证

提供交货保证:

  • 消息可靠性-存储在RabbitMQ上它们不会消失;
  • 消息通知-RabbitMQ与发送者和接收者交换信号。

可靠性要素


队列镜像

队列可以在许多节点(服务器)上进行镜像(复制)。 每个队列在一个节点上都有一个前导队列。 例如,有三个节点,10个队列,每个队列两个副本。 10个控制队列和20个副本将分布在三个节点上。 可以配置按节点分配的控制队列。 如果节点冻结:

  • 代替挂起节点上的每个前导队列,在另一个节点上提供此队列的副本;
  • 在其他节点上创建新副本以替换传出节点上丢失的副本,从而支持复制因子。

容错的问题将在本文的下一部分中讨论。

可信队列

RabbitMQ上有两种队列:可靠队列和不可靠队列。 可靠的队列将写入磁盘并在节点重新启动的情况下保存。 当节点启动时,它们将被覆盖。

持续职位

如果队列可靠,那么这并不意味着在重新启动节点时会保存其消息。 只有发件人标记为持久的邮件才会被恢复。

使用RabbitMQ时,消息越可靠,可能的性能就越低。 如果存在实时事件流,并且丢失其中几个事件或流中的时间间隔很小不是很关键,那么最好不要使用队列复制并将所有消息传输为不稳定事件。 但是,如果不希望由于节点故障而丢失消息,则最好将可靠的队列与复制和稳定的消息一起使用。

讯息通知


讯息传递

消息在传输过程中可能会丢失或重复。 这取决于发送者的行为。

“被遗忘了”

源可以决定不向接收者请求确认(将消息的接收通知发送给发送者),并简单地自动发送消息。 消息将不会重复,但可能会丢失(满足“最多一次传递”的策略)。

发件人确认

当发送方打开队列代理的通道时,他可以使用同一通道发送确认。 现在,为了响应收到的消息,队列代理必须提供以下两项之一:

  • 基本确认 肯定的确认。 收到消息后,它的责任现在由RabbitMQ承担;
  • basic.nack。 负面确认。 发生了某些事情,该消息未得到处理。 它的责任仍在源头。 如果需要,他可以再次发送消息。

除了肯定和否定的交付通知外,还提供了basic.return消息。 有时,发件人不仅需要知道消息已到达RabbitMQ,还需要知道消息确实落入一个或多个队列中。 源可能会向主题交换系统发送一条消息,在该系统中,该消息未路由到任何传递队列。 在这种情况下,代理仅丢弃该消息。 在某些情况下,这是正常的,在其他情况下,源必须知道消息是否已被丢弃,并根据此进一步进行处理。 您可以为单个邮件设置“必选”标志,如果未在任何传递队列中定义该邮件,则basic.return将返回给发件人。

源可能会在发送每条消息后等待确认,但这会大大降低其性能。 相反,源可以发送稳定的消息流,从而限制未确认消息的数量。 达到临时消息限制后,发送将暂停,直到收到所有确认。

现在,有许多消息正在从发件人传输到RabbitMQ,现在使用multiple标志将确认分组,以提高性能。 通过该通道发送的所有消息均分配有一个单调递增的整数值,即“序列号”。 消息通知包括相应消息的序列号。 并且,如果同时值为倍数= true,则发件人必须跟踪其消息的序列号,以便知道哪些消息已成功传递,哪些没有成功。 我写了一篇有关该主题的详细文章。

感谢您的确认,我们通过以下方式避免了消息丢失:

  • 在否定通知的情况下重新发送消息;
  • 在出现否定通知或basic.return的情况下,继续将消息存储在某个位置。

交易次数

出于以下原因,在RabbitMQ中很少使用事务:

  • 担保不力。 如果消息被路由到多个队列或带有强制性图标,则将不支持事务连续性。
  • 生产率低下。

老实说,我从未使用过它们,它们没有给发件人除确认外的任何其他保证,而只是增加了如何解释交易完成所产生的消息接收确认的不确定性。

通讯/频道错误

除了收到消息的通知外,发送者还必须牢记通信工具和代理的故障。 这两个因素都会导致通信通道的丢失。 随着频道的丢失,接收任何尚未收到的接收消息通知的机会消失了。 在此,发件人必须在消息丢失的风险和重复的风险之间进行选择。

当消息在操作系统缓冲区中或经过预处理后,代理失败可能发生,然后消息将丢失。 或者,也许消息已排队,但消息代理在发送确认之前死亡。 在这种情况下,邮件将被成功发送。

同样,通信手段的失败也会影响局势。 消息传输期间是否发生故障? 还是在消息排队之后,但是在收到肯定通知之前?

发件人无法确定,因此他必须选择以下选项之一:

  • 不要转发该消息,否则有丢失消息的风险;
  • 重新发送该消息并造成重复的风险。

如果有许多发件人消息正在传输,则问题变得更加复杂。 发件人唯一能做的就是在邮件中添加特殊的标头,从而给收件人提示,表明邮件正在第二次发送。 收件人可以决定检查消息是否存在此类标头,如果找到了这些标头,则可以额外检查接收到的消息是否存在重复项(如果以前没有执行过这种检查)。

收件者


收件人可以使用两个选项来接收通知:

  • 没有通知模式;
  • 手动通知模式。

无通知模式

他是一种自动通知方式。 而且他很危险。 首先,因为当消息进入您的应用程序时,它已从队列中删除。 如果发生以下情况,则可能导致消息丢失:

  • 在收到消息之前,连接已中断;
  • 该消息仍在内部缓冲区中,并且该应用程序已被禁用;
  • 消息处理失败。

此外,我们正在失去背压机制,无法监视消息传递质量。 通过设置手动发送通知的模式,可以设置预取(或设置提供的服务水平,QoS)以限制系统尚未确认接收的一次性消息数。 没有此功能,RabbitMQ会以连接允许的速度发送消息,这可能比接收者处理消息的速度更快。 结果,缓冲区已满,并且发生内存错误。

手动通知方式

收件人必须手动发送每封邮件的接收通知。 如果消息数量大于一个,他可以设置一个预取,并同时处理许多消息。 他可以决定为每条消息发送一个通知,或者可以应用多重标志并一次发送多个通知。 对通知进行分组可以提高性能。

收件人打开通道时,经过该通道的消息包含“传递标签”参数,该参数的值是一个整数,单调递增的数字。 它包含在每个收据通知中,并用作消息标识符。

通知可能如下:

  • 基本确认 之后,RabbitMQ从队列中删除消息。 多重标志可以在这里应用。
  • basic.nack。 接收者必须设置一个标志来告诉RabbitMQ是否再次将消息排队。 重新设置时,消息将转到队列的开头。 从那里再次将其发送给收件人(甚至是同一收件人)。 basic.nack通知支持多标志。
  • 基本拒绝 与basic.nack相同,但不支持多标记。

因此,语义上的requeue = false的basic.ack和basic.nack相同。 两位操作员都意味着从队列中删除一条消息。

下一个问题是何时发送收货通知。 如果消息被快速处理,则您可能希望在完成此操作(成功或失败)之后立即发送通知。 但是,如果消息在RabbitMQ队列中并且处理需要几分钟? 在此之后发送通知将是有问题的,因为如果通道关闭,则所有没有通知的消息都将返回到队列,并且将进行第二次发送。

连接/消息代理错误

如果连接断开或代理中发生错误,此后该通道将停止工作,则所有未确认其接收的消息将再次排队并转发。 这样做是有益的,因为它可以防止数据丢失,但是不好,因为它会导致过多的重复。

收件人长时间接收消息的时间越长(接收者未确认),转发的风险就越高。 重新发送消息时,RabbitMQ的转发标志设置为true。 因此,接收者至少具有该消息可能已经被处理的指示。

幂等

如果需要幂等性并保证不丢失任何消息,则应内置一些重复检查或其他幂等性方案。 如果检查重复消息过于昂贵,则可以应用一种策略,其中发件人始终向重新发送的消息添加特殊的标头,而收件人则检查接收到的消息中是否存在此类标头和重新发送标志。

结论


RabbitMQ提供可靠的长期消息传递保证,但是在许多情况下它们无济于事。

以下是要记住的要点:

  • 如果“至少一次交付”策略中需要可靠的保证,则应使用队列镜像,可靠的队列,持久性消息,发件人的确认,确认标志和来自收件人的强制通知。
  • 如果发送是“至少一次传递”策略的一部分,则在复制要发送的数据时,可能需要添加重复数据删除或幂等的机制。
  • 如果消息丢失问题不像传递速度和高可伸缩性问题那么重要,那么请考虑没有冗余,没有持久消息且在源端没有确认的系统。 尽管如此,我还是希望保留来自收件人的强制通知,以便通过更改预取限制来控制接收到的消息流。 在这种情况下,您将需要批量发送通知并使用“ multiple”标志。

卡夫卡的送货保证

提供交货保证:

  • 消息持久性-存储在段中的消息不会丢失;
  • 消息通知-一方面是Kafka(可能还有Apache Zookeeper存储库),另一方面是源/接收器之间的信号交换。

关于消息打包的两个词

RabbitMQ和Kafka之间的区别之一是使用软件包进行消息传递。

RabbitMQ通过以下方式提供与包装类似的功能:

  • 暂停发送每X条消息,直到收到所有通知。 RabbitMQ通常使用“多个”标志对通知进行分组。
  • «prefetch» «multiple».

. “multiple”. TCP.

Kafka . , . RabbitMQ, , , . , .

Kafka , , . , . RabbitMQ API , . RabbitMQ .

,



Kafka - , , . . , , , , , .

Kafka (In Sync Replicas, ISR). . , , ( 10 ). , . - , .. . .



, Kafka , , , Kafka .



, Kafka, , :

  • , . Acks=0.
  • . Acks=1
  • . Acks=All

, RabbitMQ. , , ( , ). , .

Kafka . :

  • enable.idempotence “true”,
  • max.in.flight.requests.per.connection 5 ,
  • retries 1 ,
  • acks “all”.

, acks=0/1 , .



, , . ZooKeeper Kafka.

(), , :

  • . . . . , .
  • , . “ ”. , ; , . , 10 , 4 , , , ;
  • , . “ ”. , , , . , 10 , , 4 ;
  • . , .

“ ” Kafka Streams, Java. Java . “ ”, , , . , , “ ” . , , () .

, Kafka Streams, , , “ ”. Kafka: . , . , , , ( ), .



Kafka “--”. . , , .

“ ”, , (, , ). “ ”, , . .

: “ ” ? . , , . . (Last Stable Offset, LSO) — ; “ ” .

结论

. , , . , Kafka , .

总结一下


  • “ ” “ ”.
  • .
  • , . Kafka , .
  • , , .
  • .
  • Kafka , “--”. .
  • Kafka, - , ( ). RabbitMQ .
  • Kafka , RabbitMQ , .

Source: https://habr.com/ru/post/zh-CN437446/


All Articles