前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Exponential Backoff with RabbitMQ

Exponential Backoff with RabbitMQ

作者头像
Bruce Li
发布2023-03-02 12:53:40
2740
发布2023-03-02 12:53:40
举报

本文翻译自https://www.alphasights.com/news/exponential-backoff-with-rabbitmq?locale=en。

在AlphaSights公司,RabbitMQ是我们event-drivien架构的核心元素。它使得我们的服务之间相互解耦,并且一个新的应用开始消费需要的events也非常容易。

不过,有时候也会出错,导致消费者不能处理消息。通常,有两种原因:或者是我们引入了一个bug,使得工作线程本身不工作;或者是工作线程依赖的第三方服务在当时暂时不available。

一般来说有三种方法来处理RabbitMQ中消息处理失败:丢弃消息;把消息重新放回队列(requeuing);或者是把消息发送到一个死信交换机(dead-letter exchange)。

假定我们收到的所有消息都很重要,我们不能直接丢弃掉,因此我们还剩下两种选项。

The Problem in Requeueing

这是我们最初始的方法,每次当一条消息处理失败,我直接把消息重新放回队列(requeue),接着我们可以尝试再次处理它。

尽管对于简单场景,这是一个有效的方案,但是对于我们的案例,相比于解决的问题,它引入了更多的问题。

在工作线程出问题的情况下,仅仅是再次处理同一条消息不会有任何帮助,它只会反复的再次失败(并且会在你的监控工具上生成很多杂音)。尽管如此,最坏的问题是,我们会使另外一个服务超负荷运行。如果这个服务因此不available,发送上千次请求并不是一个好的注意。

The Problem in Dead-Lettering

第二种方法就是把处理失败的消息发送到一个死信交换机,它进而把消息路由到一个空置的队列,然后我们需要手动处理。当问题确认解决之后,我们可以把消息放回工作队列去重新处理,或者如果没有必要再消费这条消息的话,直接把这条消息reject掉,这种方法永远不会使另外一个服务超负荷运行。

现在的问题是,我们有一个手动的步骤。大多数时候失败是由于一些间歇性的问题引起的,比如说超时,而且如果延后几秒钟或者几分钟重新处理这条消息,常常就可以解决问题。

Enters the Exponential Backoff Strategy

针对我们遇到的这些问题,解决方案是采取一个指数回退算法策略。这样不会使其它服务超负荷,并且对于间歇性的错误,这些消息会自动重试,避免了没有必要的人工介入。但是,实现这个策略并不是像我们想象的那么简单。

我们开始研究其他人是怎么做的,好像通用的方案是使用一个重试交换机,加上一个基于消息级别的TTL。它如下所示工作:

一旦你理解了RabbitMQ怎么处理TTL和死信交换机,实现就很简单:

  • 我们有两个交换机:工作交换机和重试交换机;
  • 工作交换机定义成重试队列的死信交换机;
  • 基于某条消息处理失败的次数,我们计算这条消息的TTL。举个例子,第一次消息处理失败,我们发送消息时带上一个1000ms的TTL,如果它再次失败,我们发送一个2000ms的TTL,以此类推;
  • 假定工作交换机是重试队列的死信交换机,当一条消息TTL到了,它就会被转到工作交换机,然后被再次消费。

The Problem

你可能已经猜到,这种方法也有一个问题,它跟RabbitMQ如何处理过期消息有关。具体可参考官方文档:

While consumers never see expired messages, only when expired messages reach the head of a queue will they actually be discarded (or dead-lettered).

When setting a per-queue TTL this is not a problem, since expired messages are always at the head of the queue. When setting per-message TTL however, expired messages can queue up behind non-expired ones until the latter are consumed or expired.

意思是说只有当一条消息到达队列头的时候,它才可能会被死信,所以,如果我们有一条消息的TTL是5分钟,另外一条消息的TTL是1秒钟,第一条消息就会阻塞队列中其余的消息,第二条消息只有等第一条消息过期之后,才会变成死信(进而再次被消费)。

原因是RabbitMQ队列的原则总是“先进先出”,因此TTL会告诉Rabbit MQ是否需要把消息发送给消费者,或者是否需要reject这条消息。因为重试队列没有任何消费者,消息会一直保持在那直到它可以被安全的reject。

这使得这个方案行不通,因为更高TTL的消息会阻塞失败之后需要更快执行的消息。因此继续探索。

And, Finally, Our Solution

为了解决这个问题,我们想到了一个类似的解决方案,即是针对不同的TTL值,动态创建新的队列。

这里关键的不同是针对不同的TLL创建新的队列。这解决了阻塞消息的问题,因为现在每条到达queue.5000的消息,都是TTL为5000ms的消息,因此队列中的第一条消息总是接下来即将过期的消息。其它的队列也是类似。

为了避免所有的消息被消费之后,出现一堆空的队列,我们用一个x-expires参数来定义动态创建的队列,意味着队列的最后一条消息被消费之后,队列本身也会被删掉。

Show Me the Code

如果你对到目前为止所看到的都是一些图很失望的话,这里即是我们使用的代码(参考文末链接)。对于每次消息处理失败,主要是Sneakers handler完成了神奇的逻辑。

尽管实现非常细节,但是只要你了解整体架构,应用到其它语言应该就会很简单。

Useful Links

  • https://www.rabbitmq.com/dlx.html
  • http://dev.venntro.com/2014/07/back-off-and-retry-with-rabbitmq/
  • https://felipeelias.github.io/rabbitmq/2016/02/22/rabbitmq-exponential-backoff.html
  • https://www.rabbitmq.com/ttl.html
  • https://github.com/alphasights/sneakers_handlers/blob/1c61e9e855da571a670a24140211093cc01a9120/lib/sneakers_handlers/exponential_backoff_handler.rb
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 天马行空布鲁斯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档