本页译自英文,由模型翻译。若与英文原文存在差异,以英文原文为准。
方法论
ruststream-lapin 如何测量自己:三个循环、在真实节点上的运行、受 Broker 限制的算式,以及代码的计数。这套流程属于整个框架(RustStream 的方法论),本页说明它在 RabbitMQ 上是什么样子。
三个循环
同一个进程把每个场景跑三遍,三个循环只有一处不同:由什么来传递消息。
- 裸客户端。 手写的循环直接驱动
lapin:消费、解码、ack。 - Broker crate。 本 crate,上面什么都没有:Broker 建立连接,队列描述符打开订阅,一个循环从订阅的流里取出投递,解码并 ack。
- RustStream。 用户写的服务:一个
#[subscriber]处理器和包住它的应用。
其余一切在三个循环里都相同:连接、订阅分到的信道、队列参数、预取窗口、ack 的位置、解码成同一个类型、载荷字节、tokio 运行时和二进制文件。
消息体是 512 字节的 JSON,发布时不要求持久化。经典队列把它放在内存里;仲裁队列除了自己的复制存储,没有别的模式。
运行
- 每次运行一个新队列。 每次运行都声明自己的队列,跑完就删掉,所以没有哪个循环会去处理上一次运行留下的东西。
- 测量窗口从第一次投递开始,到最后一条投递上的工作完成时结束,三个循环都是如此。每个循环都在这一刻之后才确认投递,所以在一次运行的数百万次确认中,有一次在每个循环里都落在数字之外。
- 消息条数由一次探测决定,使每一次被测量的运行在任何机器上都至少持续五秒。
- 三个循环交错进行:裸客户端、crate、RustStream,共三轮。每个循环报告自己的最佳、中位和最差一轮。
当裸客户端把这次运行花在等待服务器、而不是干活上时,这一行标为 受 Broker 限制。这是一道算式:在各轮之外做一次探测,测出服务器应答一个 basic.qos 要多久;当一次投递所需的往返加起来不少于一条消息所花时间的一半时,就打上这个标记。往返时间与机器信息一起公布,所以这道算式可以核对。AMQP 投递不需要等待自己的往返,因为服务器主动推送消息体,也不应答确认。它要等的是确认换回来的额度,而预取窗口把这份等待摊到窗口里的每一条消息上。
代码的计数
开销图的每个场景都是用户写的服务,跑在连接到 compose 环境中节点的 LapinBroker 上,构建时不带 testing feature。服务在启动时声明一个经典队列,用对照中相同的预取窗口消费它,并运行在单线程的 tokio 运行时上。在服务处理任何消息之前,另一个线程先把队列填满,填充要等节点确认了每一条消息才结束。
指令数来自 callgrind,内存分配次数来自 DHAT。一个场景先跑一次投递,再跑 1000 次,再跑 2000 次。后两次之差是一条消息的稳态开销,单次投递就是冷启动。
真实的节点按自己的节奏应答,所以服务线程等待套接字的频率每次运行都不一样。同一份代码运行七次,一次运行的指令数最多变动 2.4%,内存分配最多变动一个块。每个场景的分配下限比它测到过的最大值高出千分之一,分配次数超过这个下限时,just bench-code 失败。加上 --baseline=main 时,指令数多出百分之五以上,recipe 也会失败;不带基线的运行不限制指令数。失败的运行照样打印表格,并在表格下面列出它超出的每一项限制,旧值与新值并排。
自己运行
just bench
这条 recipe 从 docker-compose.test.yml 启动节点,运行两个对照场景,停掉节点,然后重写 docs/benchmarks/results.json。它需要几分钟,运行期间机器最好不做别的事。它会清空 RUSTFLAGS,所以二进制文件不会绑定到构建它的那颗 CPU 上。两条 recipe 都按发布版本的方式构建基准测试,跨 crate 启用完整的链接时优化(fat LTO),因此这些数字描述的正是服务实际使用的代码。
just bench-code
代码计数不到一分钟,需要 valgrind。recipe 自己安装基准测试运行器,版本与 Cargo.lock 锁定的发布版本一致。在 recipe 名字后面加一个数字,可以设定每次被测量的运行投递多少条消息,例如 just bench-code 2000;公布的数字是在默认值 1000 下测得的。在 main 上运行 just bench-code --save-baseline=main 记录基线,在分支上运行 just bench-code --baseline=main 与它比较。