跳至主内容

RabbitMQ Streams 的交付优化

·6 分钟阅读

RabbitMQ Streams 专为高吞吐量场景而设计,但当您的入口速率较低时会发生什么?较低的消息速率会严重影响交付性能,使消息消耗速率降低一个数量级。RabbitMQ 4.2 引入了一项优化,可显著提高低吞吐量流的交付速率,使所有支持的协议都受益。

使用 Streams 进行发布、存储和消费

当发布应用程序使用 stream 协议向 RabbitMQ stream 发送消息时,客户端库会将它们批量打包到一个发布帧中。RabbitMQ 接收这些消息并进行聚合,通常会将来自多个发布者的消息合并到一个存储单元中,称为“chunk”。然后,这个消息块会被持久化存储在文件系统中。

当消费者使用 stream 协议订阅 stream 时,RabbitMQ 通过在网络上按顺序发送块来分发消息。每个块中包含的消息数量——即块大小——是吞吐量的关键因素;包含数百条消息的大块可以为消费者带来高消息投递速率。

入口速率是块大小的主要决定因素:入口速率越高,块大小越大。Stream 从一开始就被设计为高吞吐量。

小块的问题

当入口速率较低时会发生什么?块中可能只包含几条消息——最坏的情况下甚至只有一条消息。这会消除批量处理的好处:每个帧只向消费者传递少量消息。虽然块分发已得到优化,但它仍然需要多次系统调用(读取文件系统并写入套接字)。

很难给出确切的数字,但假设一个 stream 消费者可以从一个块大小为 300 的 stream 中读取每秒数百万条消息。当块大小为 15 时,速率可能会下降到每秒约 200,000 条消息。这里重要的是比例,而不是绝对数字。

幸运的是,有一些方法可以改善小块的性能。

针对小块的优化:预读

Stream 通常具有一致的结构:如果一个 stream 在某个点包含小块,那么它很可能主要由小块组成。那么,如果我们刚刚读取的块很小,为什么不尝试提前读取几个块呢?分发多个块仍然需要多个帧,但我们只需要从文件系统中读取一次,从而节省了一些昂贵的系统调用。

预读限制为 4,096 字节,因此并非所有小块 stream 都能从这项优化中受益。尽管如此,这个相对简单的想法可以显著提高特定 stream 的投递速率。更好的是,RabbitMQ 将此技术用于所有协议,不仅是 stream 协议,也包括 AMQP。

性能结果

我们使用了一个 3 节点集群和一台虚拟机,并使用 PerfTestStream PerfTest 进行了测试。所有虚拟机均为 m7i.4xlarge AWS 实例。我们创建了 stream 并用 300 万条消息填充它们,为每个 stream 改变消息大小和块大小。然后,我们使用 PerfTest (AMQP 0.9.1) 和 Stream PerfTest (stream 协议) 消费了所有消息。我们对 RabbitMQ 4.1.4 和 4.2.0-beta.3 进行了测试。

我们使用专门为此目的编写的工具来填充 stream,以获得预期的块大小。以下是消费消息的性能工具命令。

# PerfTest
java -jar perf-test.jar --producers 0 --consumers 1 --predeclared --qos 200 \
--stream-consumer-offset first --cmessages 3000000 --queue stream
# Stream PerfTest
java -jar stream-perf-test.jar --producers 0 --consumers 1 --offset first \
--initial-credits 50 --no-latency --cmessages 3000000 --streams stream

12 字节消息,AMQP 0.9.1

即使 12 字节的消息很小,我们也看到预读方法效果很好:对于包含 1 条消息的块的 stream,速度提高了 10 倍。对于每块包含 384 条消息的 stream,速率仍然提高了一倍。

12 字节消息,stream 协议

对于每块包含 1 条消息的 stream,使用 stream 协议,我们实现了近 10 倍的提升(16,000 对比 134,000 条消息/秒)。预读的性能在达到每块 128 条消息之前表现更好。

其他消息大小的结果是一致的,我们将在最后讨论它们。

48 字节消息,AMQP 0.9.1

48 字节消息,stream 协议

256 字节消息,AMQP 0.9.1

256 字节消息,stream 协议

512 字节消息,AMQP 0.9.1

512 字节消息,stream 协议

1024 字节消息,AMQP 0.9.1

1024 字节消息,stream 协议

结果分析

正如预期的那样,使用 stream 协议的消费速率有所提高:对于小块的 stream,速率更高,但在块达到预读限制(4,096 字节)时保持不变。这在 1024 字节的消息中尤为明显,在块大小为 1 和 2 时(小于预读限制),速率有所提高,但在块大小为 4 和 6 时(达到或超过限制,因此预读未生效)大致保持不变。

AMQP 0.9.1 的趋势相似,但我们仍然观察到块超过预读限制后性能有所提高。使用 AMQP 0.9.1(以及其他非 stream 协议,如 AMQP 1.0),RabbitMQ 使用一种类似迭代器的方法来分发消息。这种方法已经使用了一种预读形式,但现在更加积极,并且更好地适应了块的大小。这解释了 AMQP 0.9.1 的良好结果,而其他非 stream 协议也应该获得类似的结果。

最后细节

预读优化默认启用,但可以通过将 stream.read_ahead 配置项设置为 false 来全局禁用。

大块仍然以优化方式分发给消费者:块头在内存中读取,但块数据(消息)以零拷贝的方式通过套接字发送。

结论

RabbitMQ 4.2 中的预读优化证明了有时向前看是值得的——在所有协议上,对于小块的 stream,性能提高了高达 10 倍。谁能想到稍微贪婪地从磁盘读取一点点就能让你的消费者如此快乐呢?

© . This site is unofficial and not affiliated with VMware.