背景
ApachePulsar是下一代分布式消息流平台,采用计算存储分层架构,具备多租户、高一致、高性能、百万topic、数据平滑迁移等诸多优势。越来越多的企业正在使用Pulsar或者尝试将Pulsar应用到生产环境中。
腾讯把Pulsar作为计费系统的消息总线来支撑千亿级在线交易。腾讯计费体量庞大,要解决的核心问题就是必须确保钱货一致。首先,保证每一笔支付交易不出现错账,做到高一致、高可靠。其次,保证计费承载的所有业务7*24可用,做到高可用、高性能。计费消息总线必须具备这些能力。
Pulsar架构解析
在一致性方面,Pulsar采用Quorum算法,通过writequorum和ackquorum来保证分布式消息队列的副本数和强一致写入的应答数(AW/2)。在性能方面,Pulsar采用Pipeline方式生产消息,通过顺序写和条带化写入降低磁盘IO压力,多种缓存减少网络请求加快消费效率。
Pulsar性能高主要体现在网络模型、通信协议、队列模型、磁盘IO和条带化写入。下面我会一一详细讲解。
网络模型
PulsarBroker是一个典型的Reactor模型,主要包含一个网络线程池,负责处理网络请求,进行网络的收发以及编解码,接着把请求通过请求队列推送给核心线程池进行处理。首先,Pulsar采用多线程方式,充分利用现代系统的多核优势,把同一任务请求分配给同一个线程处理,尽量避免线程之间切换带来的开销。其次,Pulsar采用队列方式实现了网络处理模块及核心处理模块的异步解耦,实现了网络处理和文件I/O并行处理,极大地提高了整个系统的效率。
通信协议
信息(message)采用二进制编码,格式简单;客户端生成二进制数据直接发送给Pulsar后端broker,broker端不解码直接发送给bookie存储,存储格式也是二进制,所以消息生产消费过程没有任何编解码操作。消息的压缩以及批量发送都是在客户端完成,这能进一步提升broker处理消息的能力。
队列模型
Pulsar对主题(topic)进行分区(partition),并尽量将不同的分区分配到不同的Broker,实现水平扩展。Pulsar支持在线调整分区数量,理论上支持无限吞吐量。虽然ZooKeeper的容量和性能会影响broker个数和分区数量,但该限制上限非常大,可以认为没有上限。
磁盘IO
消息队列属于磁盘IO密集型系统,所以优化磁盘IO至关重要。Pulsar中的磁盘相关操作主要分为操作日志和数据日志两类。操作日志用于数据恢复,采用完全顺序写的模式,写入成功即可认为生产成功,因此Pulsar可以支持百万主题,不会因为随机写而导致性能急剧下降。
操作日志也可以是乱序的,这样可以让操作日志写入保持最佳写入速率,数据日志会进行排序和去重,虽然出现写放大的情况,但是这种收益是值得的:通过将操作日志和数据日志挂在到不同的磁盘上,将读写IO分离,进一步提升整个系统IO相关的处理能力。
条带化写入
条带化写入能够利用更多的bookie节点来进行IO分担;Bookie设置了写缓存和读缓存。最新的消息放在写缓存,其他消息会批量从文件读取加入到读缓存中,提升读取效率。
从架构来看,Pulsar在处理消息的各个流程中没有明显的卡点。操作日志持久化只有一个线程来负责刷盘,可能会造成卡顿。根据磁盘特性,可以设置多块盘,多个目录,提升磁盘读写性能,这完全能够满足我们的需求。
测试
在腾讯计费场景中,我们设置相同的场景,分别对Pulsar和Kafka进行了压测对比,具体的测试场景如下。
压测数据如下:
以上数据可以看到,网络IO方面,3个副本多分区的情况下,Pulsar几乎要把broker网卡出流量跑满,因为一份数据需要在broker端分发3次,这是计算存储分离的代价。
Kafka的性能数据有点让人失望,整体性能没有上去,这应该和Kafka本身的副本同步机制有关:Kafka采用的是follow同步拉取的策略,导致整体效率并不高。
延迟方面,Pulsar在生产端表现更优越些,当资源没有到达瓶颈时,整个时耗99%在10毫秒以内,在垃圾回收(GarbageCollection,GC)和创建操作日志文件时会出现波动。
从压测的结果来看,在高一致的场景下,Pulsar性能优于Kafka。如果设置log.flush.interval.messages=1的情况,Kafka性能表现更差,kafka在设计之初就是为高吞吐,并没有类似直接同步刷盘这些参数。
此外,我们还测试了其他场景,比如百万Topic和跨地域复制等。在百万Topic场景的生产和消费场景测试中,Pulsar没有因为Topic数量增长而出现性能急剧下降的情况,而Kafka因为大量的随机写导致系统快速变慢。
Pulsar原生支持跨地域复制,并支持同步和异步两种方式。Kafka在同城跨地域复制中,吞吐量不高,复制速度很慢,所以在跨地域复制场景中,我们测试了Pulsar同步复制方式,存储集群采用跨城部署,等待ACK时必须包含多地应答,测试使用的相关参数和同城一致。测试结果证明,在跨城情况下,Pulsar吞吐量可以达到28万QPS。当然,跨城跨地域复制的性能很大程度依赖于当前的网络质量。
可用性分析
作为新型分布式消息流平台,Pulsar有很多优势。得益于bookie的分片处理以及ledger选择存储节点的策略,运维Pulsar非常简单,可以摆脱类似Kafka手动平衡数据烦扰。但Pulsar也不是十全十美,本身也存在一些问题,社区仍在改进中。
Pulsar对ZooKeeper的强依赖
Pulsar对ZooKeeper有很强的依赖。在极限情况下,ZooKeeper集群出现宕机或者阻塞,会导致整个服务宕机。ZooKeeper集群奔溃的概率相对小,毕竟ZooKeeper经过了大量线上系统的考验,使用还是相对广泛的。但ZooKeeper堵塞的概率相对较高,比如在百万Topic场景下,会产生百万级的ledger元数据信息,这些数据都需要与ZooKeeper进行交互。
例如,创建一次主题(topic),需要创建主题分区元数据、Topic名、Topic存储ledger节点;而创建一次ledger又需要创建和删除唯一的ledgerid和ledger元数据信息节点,一共需要5次ZooKeeper写入操作,一次订阅也需要类似的4次ZooKeeper写入操作,所以总共需要9次写入操作。如果同时集中创建百万级的主题,势必会对ZooKeeper造成很大的压力。
Pulsar具有分散ZooKeeper部署的能力,能够在一定程度上缓解ZooKeeper的压力,依赖最大的是zookeeperServer这个ZooKeeper集群。从之前的分析来看,写操作相对可控,可以通过控制台创建Topic。bookie依赖的ZooKeeper操作频率最高,如果该ZooKeeper出现阻塞,当前写入并不会造成影响。
可以按照同样的思路优化对zookeeperServerzk的依赖。至少对于当前的服务可以持续一段时间,给ZooKeeper足够的时间进行恢复;其次减少ZooKeeper的写入次数,只用于必要的操作,比如broker选举等。像broker的负载信息,可以寻求其他存储介质,尤其是当一个broker服务大量主题时,这个信息会达到兆(M)级别。我们正在和Pulsar社区携手优化broker负载功能。
Pulsar内存管理稍复杂
Pulsar的内存由JVM的堆内存和堆外存构成,消息的发送和缓存通过堆外内存来存储,减少IO造成的垃圾回收(GC);堆内存主要缓存ZooKeeper相关数据,比如ledger的元数据信息和订阅者重推的消息ID缓存信息,通过dump内存分析发现,一个ledger元数据信息需要占用约10K,一个订阅者者重推消息ID缓存初始为16K,且会持续增长。当broker的内存持续增长时,最终频繁进行整体垃圾回收(fullGC),直到最终退出。
要解决这个问题,首先要找到可以减少内存占用的字段,比如ledger元数据信息里面的bookie地址信息。每个ledger都会创建对象,而bookie节点非常有限,可以通过全局变量来减少创建不必要的对象;订阅者重推消息ID缓存可以把初始化控制在1K内,定期进行缩容等。这些操作可以大大提升Broker的可用性。
和Kafka相比,Pulsarbroker的优点比较多,Pulsar能够自动进行负载均衡,不会因为某个broker负载过高导致服务不稳定,可以快速扩容,降低整个集群的负载。
总结
总体来说,Pulsar在高一致场景中,性能表现优异,目前已在腾讯内部广泛使用,比如腾讯金融和大数据场景。大数据场景主要基于KOP模式,目前其性能已经非常接近Kafka,某些场景甚至已经超越Kafka。我们深信,在社区和广大开发爱好者的共同努力下,Pulsar会越来越好,开启下一代云原生消息流的新篇章。