RabbitMQ单机性能分析
Broker配置
- CPU: Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz
- 内存: 35GB
- Erlang: Erlang (BEAM) emulator version 10.1.1
- RabbitMQ: 3.7.8
- OS: Tencent tlinux release 1.2 (Final)
分析目标
- 时延: 针对于游戏类强互动型业务, 响应时间是十分影响玩家观感的重要指标, 因此是我们分析的重点.
- 吞吐量: 针对于游戏业务的特点, 集中访问是经常出现的场景, 因此需要依赖MQ进行流量削峰, 承载大量请求, 保证较高的吞吐量.
- 机器负载
测试模型
我们从 Send 端发送消息一条带有时间戳的消息至RabbitMQ, 然后消息转发到 Reply 端, Reply 将标志位修改, 表示完成转发操作, 再将消息发送回 RabbitMQ, 最终回到 Recv 端, 将时间戳于当前时间做差, 得到这一次链路所需时间. 为了保证时间的准确性, 我们可以把 Send 和 Recv 部署到同一个实体机.
测试代码
性能测试
横向比较
我们设置发包大小为 1K Bytes, 分别控制发包速率为 3w/s, 4w/s, 5w/s, 6w/s下, 消息的时延分布和 broker 负载:
链路时延
- 3w/s:
此时 99.5% 的链路时延都在 20ms 以下, 0.5% 超过 20ms, 最大时延在 40ms 左右.
- 4w/s:
此时 99% 的链路时延都在 20ms 以下, 1% 超过 20ms, 最大时延在 120ms 左右.
- 5w/s:
此时98%链路时延都在20ms以下, 2%超过20ms, 最大时延在200ms左右.
- 6w/s:
此时 96% 链路时延都在 20ms 以下, 4% 超过 20ms, 最大时延在 150ms 左右.
Broker 负载
CPU负载和上行带宽负载和发包速率呈线性关系, 内存负载没有变化.
纵向比较
我们再控制 4w/s 发包速率下发包大小分别为 500 Bytes, 1K Bytes, 2K Bytes 的消息时延分布和 broker 负载:
链路时延
- 500 Bytes:
此时 99% 链路时延都在 20ms 以下, 1% 超过 20ms, 最大时延在 40ms 左右.
- 1K Bytes:
此时 99% 链路时延都在 20ms 以下, 1% 超过 20ms, 最大时延在 120ms 左右.
- 2K Bytes:
此时 98% 链路时延都在 20ms 以下, 2% 超过 20ms, 最大时延在 200ms 左右.
Broker 负载
CPU负载和上行带宽负载和发包速率呈线性关系, 内存负载没有变化.
性能分析
- 时延: RabbitMQ的时延在绝大时候都能维持在链路时延 20ms, 单向时延 10ms 下, 但是我们注意到, 消息的时延会存在规律性的波峰, 对照这个时期的CPU负载和内存负载:
我们可以发现此时CPU的负载有明显的下降, 而内存并没有波动. 说明此时并没有发生 paging (消息持久化到磁盘), 因为我们的消息也并没有持久化设置. 因此推测, 这时可能是该节点的Erlang GC按照进程级别进行标记-清扫, 会将 amqqueue 进程暂停, 直至GC结束. 由于在RabbitMQ中, 一个队列只有一个 amqqueue 进程, 该进程又会处理大量的消息, 产生大量的垃圾, 这就导致了该进程GC较慢, 进而流量控制block上游, 导致了消息时延出现较大波动.
- 吞吐量: 发包速率在 4w/s(500 Bytes) 即RabbitMQ吞吐量 8w/s(500 Bytes), CPU负载在 88%, 链路时延 20ms 以下的比率在 99% 左右, 最大时延在 40ms 左右. 我们可以发现, RabbitMQ的吞吐量和 CPU 负载成线性关系.
优化
hipe_compile
在 /etc/rabbitmq/rabbitmq.config 我们可以将 hipe_compile 选项打开:
代码语言:javascript复制Explicitly enable/disable HiPE compilation.
{hipe_compile, true}
其原理类似于 JIT, 可以对RabbitMQ进行预编译, 以增加启动时间为代价降低CPU的负载, 进而提高RabbitMQ的吞吐量, 我们还是以 4w/s(1K Byte) 的发包速率进行测试:
我们可以看到, CPU的负载降低了 23%, 是十分可观的提升.
及时取出消息
在RabbitMQ中, 如果生产者持续高速发送, 而消费者速度较低, 此时会触发流量控制限制消费者的发送, 直到消费者完成消息的取出. 而这一过程会造成消息时延过高, 因此需要及时取出消息.
横向扩展
当单个Broker无法满足业务的qps时, 可以通过设置 RabbitMQ Cluster 的方式横向扩展吞吐量, 并且可以采用镜像队列的方式实现高可用. 值得注意的是, 镜像队列的方式会在各个Broker间同步消息, 占用Broker带宽和CPU.
控制发包大小
从客户端到Broker的的最大帧是 128K, 因此我们可以认为在(0K, 128K]的区间CPU负载和带宽上行负载随着发包大小线性变化. 当消息大小大于128K后会进行拆包和包, 这个过程会影响Broker的性能.
总结
我们可以看到, 在吞吐量为 8w/s(发包速率4w/s, 500 Bytes) 时, CPU的负载为 71%, 单向时延整体在 10ms 以下, 作为服务于游戏业务的中间件时, 其低时延, 高吞吐的特性是可以满足一般的场景业务需求.
注
链路时延: 一条消息从 send -> rabbitmq -> reply -> rabbitmq -> recv 所需要的时间, 单向时延 = 链路时延 / 2.