跳转至

从消息队列理解Pull与Push模型底层逻辑

约 1460 个字 2 张图片 预计阅读时间 5 分钟

在构建分布式消息系统时,我们面临的一个基本决策是确定数据流动的方向:消费者是从代理 (Broker) 拉取数据,还是由代理主动将数据推送给消费者。这一决策不仅影响系统的性能和效率,还决定了系统的架构和可扩展性。

推送 (Push) 模型的优势与局限

image.png

推送模型 中,数据流是由生产者主动向代理发送数据,随后代理根据预先设定的规则或策略,将数据推送给注册的消费者,这种模型的优点包括较低的延迟实时性更强的数据传递。

然而,推送模型也有明显的局限性。

  • 首先,它对网络带宽的要求较高,特别是在高并发的场景下,大量的小数据包频繁传输会导致网络拥塞。例如,消费者的消费能力为 10 条/s,而生产者生产能力为 100 条/s,此时会导致大量请求向下积压,当然,可以在 mq (Broker) 处进行消息堆积,或利用死信队列等机制进行退回,但是相应的,对于 Broker 的服务器压力也会显著增大。
  • 其次,由于数据传输速率由代理控制,不同消费者之间的处理能力差异可能导致部分消费者无法及时处理接收到的数据,从而造成系统资源浪费或系统过载

特别是当消费者的处理速度低于生产者的生成速度时,消费者可能会遭受类似拒绝服务攻击的情况,即过多的数据淹没而无法正常工作。

在消息队列的推送模式中,代理(Broker)需要记录和维护每个消费者的消费进度,以便准确地将未消费的消息推送给相应的消费者。这是因为推送模式下,代理负责控制消息的发送时间和数量,确保每个消费者都能接收到它们尚未处理的消息。这种方式对代理的管理和协调能力要求较高,尤其是在消费者数量中多且处理能力不一地情况下,容易导致系统复杂度增加和性能瓶颈。

拉取 (Pull) 模型的优势与挑战

相比之下,拉取模型则采取了一种更为保守和灵活的方式。

在这种模型中,数据由生产者发送到代理,但消费者需要主动发起请求以获取数据。这样做的好处是,消费者可以根据自身的处理能力和当前的工作负载情况,自主决定何时以及多少数据进行拉取。即使在消费速度下降的情况下,消费者也只会暂时落后,而不会像在推送模型中被压垮。此外,拉取模型还支持更有效的数据批处理。消费者可以一次性拉取大量数据,从而减少网络交互次数,提高传输效率,同时保持较低的延迟。

image.png

拉取模式下,消费者自己决定何时以及多少消息进行消费。消费者会主动向代理请求消息,代理只需提供消息存储和查询的功能即可。

这种方式减少了代理的负担,使其不必追踪每个消费者的消费进度,同时也赋予了消费者更大的灵活性,可以根据自身处理能力和当前负载情况自主调整消费速率。这种方式简化了系统设计,提高了系统的可扩展性和鲁棒性。

然而,拉取模型并非没有挑战。最明显的问题是,如果代理中没有新的数据可供拉取,消费者可能会频繁地发起空查询,导致 CPU 利用率升高,形成所谓的 “忙等 (Busy-Waiting)” 状态。为了解决这一问题,Kafka 引入了 "长轮询" 机制,允许消费者发出拉取请求后,如果没有新的数据可供拉取,该请求会在代理端等待一段时间,直到有新数据产生或达到预设的超时时间。这样既避免了频繁地空查询,有保证了数据的及时传递。

Kafka 的选择和实践

基于上述分析,Kafka 选择了拉取模型 作为其核心数据传输机制。这一选择不仅符合大多数消息系统的传统设计,还特别适合于需要处理大量异构消费者的应用场景。通过实现长轮询功能,Kafka 有效地平衡了数据传输的即时性和系统的稳定性,使得即使在网络条件不稳定或消费者处理能力有限的情况下,也能保持高效的数据交换。此外,Kafka 还通过数据批处理和调整拉取请求的参数,进一步提高了系统的整体性能。

推送模型和拉取模型本质上是用于应对不同的技术场景的。

  • Kafka 的拉取模型更适合数据处理场景,因为它允许消费者根据自身处理能力自主控制数据拉取的频率和数量,有效避免了消费者过载和资源浪费。
  • 而在业务场景中,实时性和低延迟要求更高,RocketMq 和 RabbitMQ 的推送模型能更好地满足这些需求

结论

  • 推送(Push)模型,要求的是实时性
  • 拉取(Pull)模型,要求的是吞吐量 (批处理,压缩)