先来谈谈 pulsar
pulsar 可以简单的看做是 broker 集群 + bookkeeper集群 构成。broker 集群属于无状态集群,只处理业务逻辑;而 bookkeeper 集群属于有状态集群,负责处理数据存储。
这样的组合和我们平时的开发多么相似,无状态(业务)服务负责执行业务逻辑,有状态数据存储负责持久化数据。
那么,从这个角度理解,最起码我们对 pulsar 中为什么需要 broker 有了认识。最复杂的部分还是在于 bookkeeper。bookkeeper是什么呢?
A scalable, fault-tolerant, and low-latency storage service optimized for real-time workloads
pulsar 为什么选择 bookkeeper?对应我们日常开发中的存储方案选型,说明 bookkeeper 的某些特性非常适合pulsar定位的功能场景。至于特性是什么,在最后会推荐相关文章。
其实同理,当我们自研一款消息队列时,我们甚至可以用mysql来实现消息存储。为什么可以选择mysql呢?
前面已经学过,innodb也是顺序写入,支持WAL。innodb中也存在 Buffer Pool,可以批量缓存消息,并且消息本身不更新。总之,这些特性不会对性能造成太大影响。
而且可以将mysql单表看做是一个分区(队列),每个topic可以对应多张表。则这样也可以支持业务服务实现队列模型和发布-订阅模型。
经过这样的对比,现在我们就可以把关注点放到 bookkeeper。
bookkeeper是有状态服务,并且可以集群化。那么必然涉及到两个关键点:
数据写入磁盘的形式,以及数据存储形式
集群数据同步时,与CAP理论的博弈
我们先来看单机bookkeeper,也就是 bookie。如上图(引自Pulsar-Cloud Native Messaging & Streaming),先看蓝色部分,总共四个步骤:
append entries(追加):顺序写入,类似mysql的日志写入
组提交:类似mysql中的组提交机制。bookie将数据写入磁盘同样采用同步刷盘机制,所以为了避免高并发写入时刷盘机制的影响,故可以利用组提交机制优化效果
写入缓存:类似Buffer Pool
响应给客户端:所以客户端收到成功ack,表示bookie的数据已经写入了磁盘
针对读取的执行路径:
缓存中数据会定期刷盘存储,存储前会进行排序等预处理。
并且同时增加一个索引存储,思想也类似于rocketmq中commitlog。
通过这样的设计实现了读写分离。接着看一下集群数据同步的设计。
在我们之前看过的raft等协议中,通常是领导者模型。只有领导者负责写入,这样的好处是避免并发写,将偏序变成了全序。但是缺点是领导者容易成为瓶颈,不过我们可以通过构建多个raft小集群的方式来缓解领导者压力。
而在 bookkeeper 中,在数据之上,又抽象出了 ledger 这个逻辑存储单元。那么显然的,一个 ledger对应的是多个数据条目,而这些数据条目可以来自于不同的 bookie。
ledger带来两大影响:
首先对于客户端来说(实际就是 pulsar 的broker),不关心数据实际如何存储,只关心和 ledger 的交互。
其次对于数据来说,被逻辑地划分为多个 ledger,所以同样可以看做是一种逻辑分区。有了逻辑分区,那么不同分区之间就不存在并发写入导致的偏序问题。并且,bookkeeper 规定一个ledger只能被一个客户端写入。这样也就解决了同一个逻辑分区中,多个客户端并发写入的问题,也就解决了偏序问题。
所以,bookkeeper(简称BK)中,每一个 bookie 都是平等的,并不需要有一个领导者bookie来维持全序关系。
到这里,我们可以看到,通过引入 ledger 这层抽象,系统设计也变得完全不同。所以抽象是多么的重要,编码、考虑问题、生活中也需要这样的抽象思维。
知道了 bookie 是平等的,那么肯定是客户端具有了领导者这样的意味。所以客户端是如何写入数据的呢?
这里 BK 采用了类似 NWR 机制,客户端可以灵活配置写入数量。比如有10个bookie(bookie平等,所以可以扩容),客户端可以决定一条消息需要同时发往n个bookie,并且当n个bookie中的m个bookie 返回成功后(m <= n),就认为写入成功。
客户端就是pulsar中的broker。当然,BK还有其他优化和特殊处理,这里不再做进一步对比。
以上是对 pulsar 的简要介绍,那么接着我们来对比一下 pulsar、rabbitmq、rocketmq、kafka 的选型问题。
先来看rabbitmq,基于erlang语言开发已经注定了rabbitmq在语言生态上的劣势,这就决定rabbitmq不会被用到高流量场景中,因为难以深度定制。当然,rabbitmq已支持丰富的客户端语言而出名,并且简单易用。多说一句,这就和最近的基金一样,机构抱团,强者越强。rocketmq、kafka、pulsar都是JVM生态,所以再次强调,社区非常重要。
对于rocketmq来说,最大的特点有两个:一是tag机制,可以定义子主题,这个机制就在很多场景下起到了关键作用;二是无缝支持canal等产品。基于这两个特点,就决定了rocketmq的应用场景。
然后是kafka,高性能就不说了,最关键的是kafka的定位不同,官网定义:
Apache Kafka is an open-source distributed event streaming platform used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications.
对比rocketmq:
Apache RocketMQ™ is a unified messaging engine, lightweight data processing platform.
再看一下rabbitmq的定义:
RabbitMQ is the most widely deployed open source message broker.
这样就没必要继续比较了,kafka不光是消息队列,更是流计算平台。所以要往流计算、大数据考虑,kafka肯定适合。
最后看看pulsar的定义:
Apache Pulsar is a cloud-native, distributed messaging and streaming platform
全面对标kafka,采用全新的架构,解决kafka中让人头疼的重平衡。但是pulsar真的是未来的主流吗?这句话不一定对,不过可以预见,未来新涌现的消息队列中,会有相当一部分采用pulsar类似的架构思想:存储和计算分离。
关于消息队列的内容本篇就全部完结。本系列针对消息队列的文章,更多的是为了联系之前学过的知识,加深印象,并从已有知识中分析新的知识,这也符合费曼学习法。Over!