Apache Pulsar是一个企业级的分布式消息系统。项目于2015年由 Yahoo 开源,2017年6月提交给 Apache 孵化器,2018年9月成为Apache的顶级项目。

目录

特性

架构概述

Broker

集群 

元数据

持久存储

Apache BookKeeper

Pulsar相关概念

Topic

数据分区

命名空间

订阅类型

消费模式

生产环境实践

总结


 

特性

Pulsar之所以能够称为下一代消息队列,主要是因为以下特性:

  1. 线性扩展。能够方便的扩容到成百上千个节点(Kafka扩容需要占用很多系统资源在节点间拷贝数据,而Pulsar完全不用)
  2. 高吞吐。已经在Yahoo的生产环境中经受了考验,每秒数百万消息
  3. 低延迟。在大规模的消息量下依然能够保持低延迟(< 5ms)
  4. 持久化机制。Pulsar的持久化机制构建在Apache BookKeeper之上,提供了写与读之前的IO隔离
  5. 基于地理位置的复制。Pulsar将多地域/可用区的复制作为首要特性支持。用户只需配置好可用区,消息就会被源源不断的复制到其他可用区。当某一个可用区挂掉或者发生网络分区,Pulsar会在之后不断的重试。
  6. 部署方式的多样化。既可以运行在裸机,也支持目前例如Docker、K8S的一些容器化方案以及不同的云厂商,同时在本地开发时也只需要一行命令即可启动整个环境。
  7. Topic支持多种消费模式:exclusive、shared、failover

架构概述

在最高层,Pulsar实例由一个或多个Pulsar集群组成。同一个实例内的集群间可以相互复制数据,

Pulsar集群由三部分组成:

  1. Broker:一个或者多个broker负责处理和负载均衡源源不断从Producer发来的消息,并分发给消费者。通过一个全局的ZK集群来处理多种协作式任务,例如说基于地理位置的复制。它与配置存储交互来处理相应的任务,将消息存储在BookKeeper实例中(也称为bookies),同时单个集群内也需要有一套ZK集群,来存储一些元数据。
  2. BookKeeper集群:由一个或多个bookie组成的BookKeeper集群,用于持久化消息。
  3. ZooKeeper集群:用于Pulsar集群操作的该Pulsar集群独有的ZooKeeper

下图是Pulsar集群的说明:

Apache Pulsar 分布式消息系统_应用程序

Broker

在Kafka和RocketMQ中,Broker负责消息数据的存储以及consumer消费位移的存储等,而Pulsar中的broker和他们两个有所不同。

Pulsar中的broker是一个无状态的节点,主要负责运行另外的两个组件:

  1. HTTP服务器,通过调用REST API接口来实现管理任务,以及为Producer和Consumer提供Topic查询。
  2. 调度器,异步的TCP服务,通过自定义Binary Protocol处理所有数据传输(协议是Google开源的Protocol Buffer)。

消息通常被分发到托管ledger缓存,除非backlog已经超出了缓存的大小。如果backlog对于缓存来说增长的太大,broker将开始从BookKeepr读取数据条目。

为了支持全局Topic异地复制,Broker使用复制器查询出发布在本区域尾部的条目,然后通过Java客户端类库重新把他们发步到其他远程可用区。

集群 

Pulsar的实例,由一到多个Pulsar集群组成。而集群组成如下:

  • 一到多个Pulsar broker
  • ZooKeeper仲裁,作为集群层面的配置和协调
  • 一套bookie,用作来消息的持久存储。

集群可以通过geo-replication相互复制。

元数据

Pulsar中的元数据主要存储到ZK中,在Pulsar实例中:

  • 不同可用区相关的配置信息,namespace,以及其他所有需要全局一致的信息,会存在全局的ZK中
  • 集群内部的ZK用于存储集群指定的配置和协调信息,如元数据的所有权、Broker负载报告、BookKeeper ledger元数据等。例如某个Topic的数据写入到了那些Ledger、Broker目前的一些埋点数据等等。

持久存储

Pulsar为应用提供消息发送的保证。一旦消息成功到达Pulsar broker,他将会被发送到他预期的目标上。

这种保证,需要消息被发送及确认前,被持久存储。这种消息传输的模式,通常被称为持久化消息传输。在Pulsar中,所有消息的N份拷贝被存储及同步在硬盘上,例如,跨越两台server的4份拷贝,存储于镜像RAID卷。

Pulsar broker 在收到消息并进行确认之后,就必须确保消息在任何情况下都不会丢失。与其他消息系统不同的是,Pulsar 使用Apache BookKeeper来保证持久性。BookKeeper 提供了低延迟的持久化存储。Pulsar 在收到消息之后,将消息发送给多个 BookKeeper 节点(具体由复制系数来定),节点将数据写入预写式日志(write ahead log),同时在内存里也保存一份。节点在对消息进行确认之前,强制将日志写入到持久化的存储上,因此即使出现电力故障,数据也不会丢失。因为 Pulsar broker 将数据发给了多个节点,所以只会在大多数节点(quorum)确认写入成功之后它才会将确认消息发给生产者。Pulsar 就是通过这种方式来保证即使在出现了硬件故障、网络故障或其他故障的情况下仍然能够保证数据不丢失。

Apache BookKeeper

Apache BookKeeper是分布式 write-ahead log(WAL) 系统。

Pulsar使用BookKeeper系统来持久存储消息,BookKeeper为Pulsar提供了很重要的优势:

  • 它使得Pulsar可以使用很多称为legder的独立log。随着时间的移动,可以为topic创建很多的ledger。
  • 它为有序数据提供了非常高效的存储,并处理数据条目复制。
  • 在各种可能的系统崩溃下,它可以保证ledger的读一致性。
  • 它提供跨bookie的I/O分配。
  • 它在容量及吞吐量上都能做到可扩展。可以通过为集群增加bookie立即提升容量。
  • Bookie的设计支持处理成千上万ledger的读写并发。通过使用多个硬盘设备---一块用于journal,其他的用做普通存储,这把读操作从正在进行的写操作延迟影响中隔离出来。

BookKeeper是一个可横向扩展的、错误容忍的、低延迟的分布式存储服务,BookKeeper中最基本的单位是记录,实际上就一个字节数组,而记录的数组称之为ledger,BK会将记录复制到多个bookies,存储ledger的节点叫做bookies,从而获得更高的可用性和错误容忍性。从设计阶段BK就考虑到了各种故障,Bookies可以宕机、丢数据、脏数据,但是主要整个集群中有足够的Bookies服务的行为就是正确的。


在Pulsar中,每个分区Topic是由若干个ledger组成的,而ledger是一个append-only的数据结构,只允许单个writer,ledger中的每条记录会被复制到多个bookies中,一个ledger被关闭后(例如broker宕机了或者达到了一定的大小)就只支持读取,而当ledger中的数据不再需要的时候(例如所有的消费者都已经消费了这个ledger中的消息)就会被删除。

Apache Pulsar 分布式消息系统_Pulsar_02

 

Bookkeeper的主要优势在于它可以保证在出现故障时在ledger的读取一致性。因为ledger只能被同时被一个writer写入,因为没有竞争,BK可以更高效的实现写入。在Broker宕机后重启时,Pulsar会启动一个恢复的操作,从ZK中读取最后一个写入的Ledger并读取最后一个已提交的记录,然后所有的消费者也都被保证能看到同样的内容。Apache Pulsar 分布式消息系统_数据_03

 

我们知道Kafka在0.8版本之前是将消费进度存储到ZK中的,但是ZK本质上基于单个日志的中心服务,简单来讲,ZK的性能不会随着你增加更多的节点而线性增加,会只会相反减少,因为更多的节点意味着需要将日志同步到更多的节点,性能也会随之下降,因此QPS也会受单机性能影响,因此0.8版本之后就将消费进度存储到了Kafka的Topic中,而RocketMQ最初的版本也类似,有几种不同的实现例如ZK、数据库等,目前版本采用的是存储到本机文件系统中,而Pulsar采用了和Kafka类似的思想,Pulsar将消费进度也存储到了BK的ledger中。

Apache Pulsar 分布式消息系统_apache_04

 

Ledger

ledger是单写入仅可追加的数据结构,被分配给多个BookKeeper的存储节点或者bookie。Ledger条目被复制到多个bookie。Ledge自己的语义十分简单:

  • Pulsar broker可以创建ledger,往ledger添加条目,关闭ledger。
  • ledger关闭后--无论是主动还是因为写入程序错误---他都可以被以只读的方式打开。
  • 最后,当ledger中的条目不再需要时,整个ledger都将从系统中被删除(跨所有bookie)

Ledger的读一致性

BookKeeper最主要的长处是他在存在失败的情况下,能够保证ledger的读一致性。因为ledger仅可被单线程写入,这让程序很轻易就做到高效增加条目,而不需要考虑一致性。失败后,ledger将会执行恢复程序,最终确定ledger状态并发布最后提交的条目日志。经过上述操作,ledger的所有读取者会被保证读取到完全一样的内容。

托管ledger

考虑到BookKeeper Ledger提供单一log抽象,构建于ledger之上的类库,被称为托管ledger,它代表了单一log的存储层。托管ledger代表消息流的抽象。它有一个单独的写入者,往流的末尾持续添加数据,多个游标在消费流数据,每个游标都持有自己的关联位置。

在内部,一个单独的托管ledger使用多个BookKeeper ledger来存储数据。使用多个ledger出于以下两个原因考虑:

  1. 失败后,ledger不再可写,一个新的ledger需要被创建
  2. 当ledger含有的全部消息被所有cursor都消费完成时,ledger就可以被删除了。这允许ledger周期回滚。

Pulsar相关概念

Topic

向 Pulsar 发送数据的应用程序叫作生产者(producer),而从 Pulsar 读取数据的应用程序叫作消费者(consumer)。有时候消费者也被叫作订阅者。发布订阅系统中最核心的概念是Topic,简单来说,Topic可以理解为一个管道,Producer可以往这个管道丢消息,Consumer可以从这个管道的另一端读取消息,但是这里可以有多个Consumer同时从这个管道读取消息。
Apache Pulsar 分布式消息系统_应用程序_05

 

数据分区

写入主题的数据可能只有几个 MB,也有可能是几个 TB。所以,在某些情况下主题的吞吐量很低,有时候又很高,完全取决于消费者的数量。那么碰到有些主题吞吐量很高而有些又很低的情况该怎么处理?为了解决这个问题,Pulsar 将一个主题的数据分布到多台机器上,也就是所谓的分区。

在处理海量数据时,为了保证高吞吐量,分区是一种很常见的手段。默认情况下,Pulsar 的主题是不进行分区的,但通过命令行工具或 API 可以很容易地创建分区主题,并指定分区的数量。

在创建好分区主题之后,Pulsar 可以自动对数据进行分区,不会影响到生产者和消费者。也就是说,一个应用程序向一个主题写入数据,对主题分区之后,不需要修改应用程序的代码。分区只是一个运维操作,应用程序不需要关心分区是如何进行的。

主题的分区操作由一个叫作 broker 的进程来处理,Pulsar 集群里的每个节点都会运行自己的 broker。

每个Topic可以划分为多个分区,同一个Topic下的不同分区所包含的消息都是不同的。每个消息在被添加到一个分区后都会分配一个唯一的offset,在同一个分区内消息是有序的,因此客户端可以根据比如说用户ID进行一个哈希取模从而使得整个用户的消息都发往整个分区,从而一定程度上避免race condition的问题。
通过分区,将大量的消息分散到不同的节点处理从而获得高吞吐。默认情况下,Pulsar的topic都是非分区的,但是支持通过cli或者接口创建一定分区数目的Topic。

将一个主题分到多个 broker 上:
Apache Pulsar 分布式消息系统_数据_06

默认情况下Pulsar会自动均衡Producer和Consumer,但有时候客户端想要根据自己的业务规则也进行路由,Pulsar默认支持以下几种规则:

  • 单个分区——生产者随机挑选一个分区,并将数据写入该分区。该策略与非分区主题提供的保证是一样的,不过如果有多个生产者向同一个主题写入数据,该策略就会很有用。
  • 轮询(round robin)分区——生产者通过轮询的方式将数据平均地分布到各个分区上。比如,第一个消息写入第一个分区,第二个消息写入第二个分区,并以此类推。
  • 哈希(hash)分区——每个消息会带上一个键,要写入哪个分区取决于它所带的键。这种分区方式可以保证次序。
  • 自定义分区——生产者使用自定义函数生成分区对应的数值,然后根据这个数值将消息写入对应的分区。

 

构建 Pulsar 的目的是为了支持多租户(multi-tenant)应用场景。Pulsar 的多租户机制包含了两种资源:资产(property)和命名空间(namespace)。资产代表系统里的租户。假设有一个 Pulsar 集群用于支持多个应用程序(就像 Yahoo 那样),集群里的每个资产可以代表一个组织的团队、一个核心的功能或一个产品线。一个资产可以包含多个命名空间,一个命名空间可以包含任意个主题。

Pulsar 各个组件间的关系:

Apache Pulsar 分布式消息系统_数据_07

命名空间

命名空间是 Pulsar 最基本的管理单元。在命名空间层面,我们可以设置权限、调整复制选项、管理跨级群的数据复制、控制消息的过期时间或执行其他关键任务。命名空间里的主题会继承命名空间的配置,所以我们可以一次性对同一个命名空间内的所有主题进行配置。命名空间可以分为两种:

  • 本地(local)——本地命名空间只在集群内可见。
  • 全局(global)——命名空间对多个集群可见,可以是同一个数据中心内的集群,也可以是跨地域数据中心的集群。该功能取决于是否启用了集群复制功能。

虽然本地命名空间和全局命名空间的作用域不同,但它们都可以在不同的团队或不同的组织内共享。如果应用程序获得了命名空间的写入权限,就可以往该命名空间内的所有主题写入数据。如果写入的主题不存在,就会创建该主题。

订阅类型

每个命名空间可以包含一到多个主题,每个主题可以有多个订阅者,每个订阅者可以接收所有发布到该主题的消息。为了给应用程序提供更大的灵活性,Pulsar 提供了三种订阅类型,它们可以共存在同一个主题上:

  • 独享(exclusive)订阅——同时只能有一个消费者。
  • 共享(shared)订阅——可以由多个消费者订阅,每个消费者接收其中的一部分消息。
  • 失效备援(failover)订阅——允许多个消费者连接到同一个主题上,但只有一个消费者能够接收消息。只有在当前消费者发生失效时,其他消费者才开始接收消息。

下图展示了这三种类型的订阅。Pulsar 的订阅机制解耦了消息的生产者和消费者,在不增加复杂性和开发工作量的情况下为应用程序提供了更大的弹性。

Apache Pulsar 分布式消息系统_命名空间_08

消费模式

消费决定了消息具体是如何被分发到消费者的,Pulsar支持几种不同的消费模式: exclusive、shared、failover。

图示如下:
Apache Pulsar 分布式消息系统_apache_09

  1. Exclusive: 一个topic只能被一个消费者消费。Pulsar默认就是这个模式
  2. Shared: 共享模式或者叫轮询模式,多个消费者可以连接到同一个topic,消息被依次分发给消费者,当一个消费者宕机或者主动断开连接,那么发到那个消费者的还没有ack的消息会得到重新调度分发给其他消费者。
  3. Failover: 多个消费者可以连接同一个topic并按照字典序排序,第一个消费者会开始消费消息,称之为master,当master断开连接,所有未ack和队列中剩下的消息会分发给另一个消费者。
    Pulsar目前也支持另一种Reader接口,支持传入一个消息ID,例如说Message.Earliest来从最早的消息开始消费。

生产环境实践

Pulsar 目前在助力 Yahoo 的主要应用,如Yahoo Mail、Yahoo Finance、Yahoo Sports、Gemini广告平台和 Yahoo 分布式键值存储系统Sherpa。很多场景都要求很强的持久性保证,比如零数据丢失,同时又要求很高的性能。Pulsar 从 2015 年开始部署到生产环境,现在在 Yahoo 的生产环境里大规模地运行。

  • Pulsar 被部署在 10 多个数据中心里,具备了全网格复制能力
  • 每天处理超过 1000 亿个消息
  • 支持着 140 万个主题
  • 整体的消息发布延迟小于 5 毫秒

总结

Pulsar作为下一代分布式消息队列,拥有非常多吸引人的特性,也弥补了一些其他竞品的短板,例如地域复制、多租户、扩展性、读写隔离等等。

 

官网Pulsar架构概述

官网Pulsar消息系统概述

Pulsar VS. Kafka: 统一的消息消费模型(Queue + Stream)