Apache Pulsar 性能极限测试

360影视 欧美动漫 2025-08-29 19:44 1

摘要:Apache Pulsar 是一款非常优秀的消息队列,其存算分离架构设计相比其他开源MQ具有很大的技术先进性。在部分数据库和中间件产品 Serverless 架构设计中,使用 Pulsar 用于服务解耦,比如 Milvus 使用 Pulsar 作为内部通信管道

Apache Pulsar 是一款非常优秀的消息队列,其存算分离架构设计相比其他开源MQ具有很大的技术先进性。在部分数据库和中间件产品 Serverless 架构设计中,使用 Pulsar 用于服务解耦,比如 Milvus 使用 Pulsar 作为内部通信管道(尽管 Milvus 将在新版本中移除 Pulsar 依赖,猜测主要原因是在项目逐渐成熟后想降低运维复杂度的一个正常架构演进,但是带 Pulsar 的 Milvus 目前已迭代到 2.x 的版本,也足以说明 Pulsar 性能及可靠性)。除此之外,Pulsar 原生还支持延迟消息、跨集群容灾(GEO),解决业务痛点的同时,可以保证高可用性。

Pulsar 客户端写入耗时主要有 2 段:

1. 客户端写到计算节点 Broker

2. broker 并行调用写多个 bookie 副本,做数据持久化

根据源码分析,Broker 中,一条消息在服务端接收到,写入 Bookie 成功,到执行完客户端回调函数,整个耗时可以通过(pulsar_broker_publish_latency)指标观测。

使用 arthas 打印线程级别火焰图,基本耗时都在几次网络 IO 上。

线程耗时占比内部主要耗时占比BookKeeperClientWorker-110%取队列:50%执行bookie回调函数,写队列:50%BookKeeperClientWorker-225%取bookie队列:40%向bookie发送写请求:40%执行broker数据写成功的回调函数:20%pulsar-io-125%执行写bookie网络队列的任务:30%读取bookie的网络回包,写入bookie队列:30%超时等待:30%pulsar-io-230%读取客户端写请求,写bookie请求队列:40%给客户端返回响应:40%超时等待:20%

Pulsar 的数据模型决定了 Pulsar 具有承载百万 Topic 的能力

Pulsar 的 Ledger 文件相当于 RocksDB 的 Commit Log,存储主数据,但是性能更好,因为做了攒批排序,这样在某个topic消费时,数据是局部有序的,会减少查RocksDB和读盘次数。Pulsar 具有多级缓存,生产消息时,数据被写入WAL和WriteCache,消费者会先从 WriteCache 读取数据如果读取不到,会从 RocksDB 查询对应的 Ledger 文件和数据位置,并将结果写入 ReadCache。一个磁盘上同一时刻只有 1 个打开状态的 ledger 文件,一个 RocksDB

写请求在 bookie 中的执行过程如下,实线部分是同步执行,会阻塞写耗时的。

Bookie 的客户端(Broker)选择一组副本集(Ensemble)后,并发向多个 Bookie 发起写请求Bookie 收到写请求时,先写入 writeCache(默认为堆外内存的1/4),攒批异步刷盘默认开启,将数据写入 Journal(也就是 WAL)并触发刷盘Journal 线程从队列取出待刷盘的数据,写入内存Buffer中,Buffer满则写入 PageCacheForceWrite 线程将写入PageCache的 WAL 数据强制刷盘完成后回调客户端的回调函数,表示写成功。

Pulsar WAL的刷盘策略有3种,满足任意一条都会刷盘
1. 最大等待时间,默认1ms,即使队列里只有1个写请求也需要等待。
2. 最大字节大小,默认 512KB
3. 允许队列为空时刷盘,默认关闭,这里我们修改为打开。打开后,只要队列里有1条数据,也会刷盘,但会提升IOPS,针对 SSD 磁盘可以打开,在小流量时可以减少多余的攒批耗时,在大流量下,由于攒批策略会优先判断,会继续保持攒批刷盘的效果。

# 开启强制刷盘,数据从PageCache刷到磁盘后再返回journalSyncData=true# 开启写journaljournalWriteData=true# 当journal队列为空时,来一条数据也及时刷盘,不等攒批journalFlushWhenQueueEmpty=true

另外,Journal 数据块大小与读缓存参数做调整,与SSD实际的物理磁盘扇区大小(4 KB)对齐

# 所有日志的写入和提交都应与指定的大小保持一致。# 如果未达到该大小,则会用零进行填充以使其与指定大小保持一致。# 仅在将 journalFormatVersionToWrite 设置为 5 时才会生效。journalAlignmentSize=4096readBufferSizebytes=4096

线程级别耗时分析如下,在经过上述参数调整后,写入过程中,无异常耗时阻塞点。

线程耗时占比内部主要耗时占比Journal17%读journal队列:30%数据写入 pagecache:30%写forceWrite队列:25%ForceWrite48%读forceWrite队列:10%强制刷盘:80%回调客户端处理结果,将请求写入网络队列:10%bookie-io28%解析写请求并写入journal队列:30%执行网络队列任务,给客户端回包:30%超时等待:30%

单分区 topic,单生产者,在保证顺序的前提下,测试同步、异步以及不同消息大小下写 Pulsar 的 QPS 和耗时。

broker 2 节点,4C 16GB,25Gb 网络带宽

bookie 3 节点,磁盘配置:4*4TB nvme,25Gb 网络带宽

压测命令:

# 先创建一个单分区的topicbin/pulsar-admin --admin-url http://192.0.0.1:8080 topics create-partitioned-topic persistent://public/default/test_qps -p 1# 副本数=2,ack=2bin/pulsar-admin --admin-url http://192.0.0.1:8080 namespaces set-persistence public/default \--bookkeeper-ensemble 2 \--bookkeeper-write-quorum 2 \--bookkeeper-ack-quorum 2# 同步,单线程,观察耗时bin/pulsar-perf produce persistent://public/default/test_qps \-u pulsar://192.0.0.1:6650 \--disable-batching \--batch-max-messages 1 \--max-outstanding 1 \--rate 500000 \--test-duration 120 \--busy-wait \--size 1024 > 1024.log &# 异步,开启压缩,观测吞吐export OPTS="-Xms10g -Xmx10g -XX:MaxDirectMemorySize=10g"bin/pulsar-perf produce persistent://public/default/test_qps_async \-u pulsar://192.0.0.1:6650 \--batch-max-messages 10000 \--memory-limit 2G \--rate 2000000 \--busy-wait \--compression LZ4 \--size 1024 > 1024.log &客户端(异步/同步)消息大小QPS上限吞吐平均耗时瓶颈同步128 byte41464.0 Mbit/s0.3 ms网络等待同步1 KB320025 Mbit/s0.3 ms网络等待同步,多IO线程1 KB320025 Mbit/s0.3 ms网络等待同步16KB3411416.5 Mbit/s0.3 ms网络等待同步512 KB7042750.9 Mbit/s1.4 ms网络等待异步128 byte14998261464.7 Mbit/s5ms网卡、磁盘、GC惩罚异步1 KB10601848282 Mbit/s5 ms网卡、磁盘、GC惩罚异步16 KB787089542.9 Mbit/s6ms网卡、磁盘、GC惩罚

经测试,Pulsar 生产者生产一条消息,同步等待服务端持久化执行成功后返回,耗时仅需 0.3 ms。

在异步攒批的情况下,Pulsar 单客户端单分区单线程可实现100万写入TPS。在开启压缩后,TPS可提升到150万,并且可以保证消息顺序。

备注:

同步情况下,客户端发送一条消息,等待服务端处理完毕,回复客户端结果后,客户端再发送下一条消息,主要耗时在等待网络回包。异步的情况下,Pulsar支持异步回调,对耗时较高的刷盘可以实现攒批,极大提升性能。并且broker支持按顺序回调客户端的回调函数,可以保证消息顺序。单生产者往单分区topic生产消息时,只会使用1个IO线程,使用同一个 channel 来保证顺序,因此无论同步异步,使用多线程的耗时和单线程一样。

使用 fio 模拟单线程写pagecache并同步刷盘的性能:

fio --name=fsync_test \--filename=/data2/testfile \--bs=1k \--size=1k \--rw=write \--ioengine=sync \--fsync=1 \--numjobs=1 \--iodepth=1 \--direct=0 \--group_reporting \--runtime=60 \--time_based

nvme 单线程写 pagecache耗时18 us,fsync 平均耗时26 us,总耗时应该在44us左右,结合上面测试,在bookie 中,单条数据写入并完成刷盘大概需要 100 us,额外的耗时消耗在线程切换及从阻塞队列中读写数据(线程切换耗时比例参考上面火焰图的耗时拆解)。

除了服务内部的耗时,还有服务之间网络传输的耗时,经统计,同 AZ 内,单次网络耗时 0.05ms。

因此最终结论是,在NVMe磁盘下,Pulsar 客户端发起一次写请求,2 副本都写成功并且客户端收到回包,平均需要 0.3ms。

Pulsar 单客户端单分区单线程可实现100万写入TPS。在开启压缩后,TPS可提升到150万,并且可以保证消息顺序。

来源:走进科技生活

相关推荐