保护多租户 RabbitMQ 实例免受队列积压的影响

在微服务架构中使用事件代理(如 Apache Kafka)和消息代理(如 RabbitMQ)等工具在多个微服务之间共享数据。如果我们遵循事件源、命令源和异步通信等模式,这些消息传递工具将成为我们架构中更重要的组件。

其中一些工具应被视为由多个微服务使用的共享基础设施组件。原因各不相同,但第一个是将要部署的工具/技术的复杂性。

有些工具本质上是重量级的,可能无法安装在单个微服务的拓扑中,这不仅适用于代理。NoSQL(如 Cassandra)或 RDBMS(如 Oracle)等数据源可以属于这一类。这些工具可能需要专门的优化、特殊的硬件、特殊的许可方案,或者可能与微服务的执行环境(如容器)不兼容。技术本身的复杂性还可能需要特定的工程技能,而这些技能可能并非所有 Scrum 团队都具备。

在大多数情况下,共享基础设施组件还可以节省成本。(您通常不会自动扩展这些组件 - 由于负载条件而启动新组件或删除不必要的组件)

嘿,我们不会放弃我们的微服务原则。我们仍然需要隔离微服务的“数据”,我们通过对这些工具应用逻辑分区来实现这一点。我们将为这些微服务创建专用的 Cassandra 数据集、RDBMS 数据库、Kafka 主题、RabbitMQ 队列并消除耦合。

然而,我们仍然共享硬件/软件基础设施。该环境是共享的、多租户的,并且应该为其租户提供公平的使用。如果一个微服务由于合法的流量峰值或有错误的代码而滥用其消息队列/主题,这可能会影响同一集群上的其他微服务的性能。

有些工具将为您提供通过不同机制限制租户行为的选项。例如,在 Kafka 上,我们可以对用户/客户端 ID 应用配额,以强制实现最大发布者/消费者吞吐量。对于其他不提供此功能的系统,我们可以实施一些解决方法。

在本文中,我将介绍一种可以用来保护 RabbitMQ 代理的策略。

RabbitMQ 提供了 2 种应用隔离的机制。虚拟主机和队列。可以为每个微服务创建一个虚拟主机,它将保存所有逻辑基础设施定义,如队列、交换、用户等。这是一个很好的实践,因为这允许团队管理自己的 AMQP 拓扑。但是,这并不能阻止虚拟主机对底层物理 RabbitMQ 实例提出更多要求。

在负载下,RabbitMQ 将首先开始因内存而阻塞。默认行为是将队列消息保留在内存中(即使是持久消息)。这是出于性能目的,并允许 Rabbit 更快地响应消费者。队列中未确认的消息越多,Rabbit 消耗的内存就越多。

Rabbit引入了队列参数“lazy”来取消这种行为并跳过内存。在惰性模式下,消息直接写入磁盘。这对于将消息保存较长时间的队列(例如暂存队列)特别有用。但是,这显然会影响消费速度。

当总内存达到机器总内存的预先配置的百分比时,Rabbit 将开始分页到磁盘以释放内存,同时,它将开始施加反压以保护自己免受要求严格的发布者的影响。不幸的是,它是通过拒绝发布者来做到这一点的,并且它不是一个“放慢速度”的机制。这意味着发布者应该在开始收到代理拒绝后立即进行重试。如果发布商代码是您不想触及的遗留代码、第三方组件,或者您只是想要更精简的发布商,那么这可能不是您的首选方案。

在这种“停止世界”的情况下,所有发布商(无论其成熟度如何)都将进入停止状态,我们不希望这种情况发生。这引导我们关注争用点:队列。

处理 Rabbit 队列的最佳实践是始终保持队列为空。队列积压仅在一种情况下才会发生:发布者发布速度快于消费速度时。

我们可以通过观察队列指标(未确认的消息计数)来自动扩展我们的消费者队列来缓解这种情况。但是,如果自动扩展不可用并且我们正在处理意外的峰值负载,我们可能会开始看到积压的情况。诸如灾难性故障之类的罕见情况也可能发生在消费者端,从而导致消息堆积。

我们需要对不同应用程序/不同团队拥有的队列进行一些治理。可以通过基本队列配置参数来实现这一点的技术实施:最大队列长度。

治理过程很简单:在创建队列之前,必须设置此参数,以确保给定时间队列上最多有最大队列长度消息。(应用程序应该估计高峰时间内队列中可能积压的最大发布数量,并且基线研究是有益的)

然后将使用此参数创建队列。但如果您发布更多内容会怎样?

这在 Rabbit 术语中称为“溢出”,默认的溢出行为是从队列头部删除消息,为新消息腾出空间。对于某些用例,我们可以接受这一点,但对于其他一些用例,我们可能需要保留所有消息。

为了解决这些情况,我们需要创造性地使用一些 AMQP 组件。

拓扑如下图所示:

图片标题

这个想法是将溢出到另一个具有关联的默认 TTL 的队列的消息设置为死信。由于该队列不应消耗内存,因此其类型设置为“lazy”。消息将在此暂存一段 TTL 时间,并以死信形式发送到另一个队列 test_shovel。(Rabbit 不允许我们将它们发送到原始交易所,因为这会导致循环)。最后一个队列将被 Rabbit shovel 消耗,它将消息重新发布到原始队列。

如果到那时,消费者减少了已满的原始队列积压,那么这些 TTL 消息将被排队并最终被消费。

此设置的一个问题是消息的排序。原始排序将会丢失(如果您使用多个消费者,无论如何它都会丢失),因此如果您有单个消费者强制执行的严格排序要求,您可能需要实现消费者端重新排序或监听“暂存队列” ”,并在接收方优先考虑它。

以下是测试设置的队列定义:

原始队列(测试):

图片标题

TTL队列(test_ttl):

图片标题

铲子队列(test_shovel):

图片标题

这些队列分别与路由键“test”绑定到“直接”交换“test”、“test_ttl”和“test_shovel”。

值得注意的是,这种设置应该与可观察性实践齐头并进。流量指标将帮助我们何时增加最大消息计数限制,并在某个时候扩展 Rabbit 实例本身。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千源万码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值