消息队列Pulsar使用须知

Broker
producer生产的消息会归属到指定的topic里。topic是一个消息的集合,当中的消息会按照一定的规则被切分成不同的partition,每个topic-partition会落靠在某个broker上。broker用来接收和发送消息,负责客户端(consumer、producer)的读写请求。Pulsar中的broker是无状态的,不存储实际的数据。

Tenant/Namespace
Pulsar的三层结构有利于实现多租户系统,namespace级别的策略设置可以更巧妙地运维和管理,如下:为了支持统一化的消息平台,Pulsar引入了Topic Domain的概念,默认消息是可持久化的。这种层级化结构,可以使Pulsar能适配不同的应用场景。

Topic/Partition
topic里的每个分区本质上就是一个stream(无界的数据流)。Pulsar提供了partition的逻辑抽象,底层物理存储将逻辑的partition划分为多个分片(Segment),均匀存储在所有节点上。其中Segment(Ledger)由多条entry组成,entry是由更多的消息(Message)通过匹配进行批量组成的。

Message
最底层的message通常包含Message ID,字段一般包括:ledger-id(在哪个segment)、entry-id(entry在这个segment的位置)、batch-id(消息被匹配后的位置)、partition-index(消息在topic的哪个partition)。

Subscription Mode
Exclusive:独占订阅多个consumer同时存在,只会有一个consumer是活跃的,也即只有这一个consumer可以接收到这个topic的所有消息。Failover:故障转移订阅等同于Kafka中的consumer group订阅模式。 同一订阅中可以包含多个consumer,同一topic-partition的数据只能被订阅中的某个consumer消费,不能同时被多个consumer共享消费。对于给定的topic-partition,将选择一个consumer作为该topic-partition的主consumer,其他consumer将被指定为故障转移consumer,当主consumer断开连接时,其持有的topic-partition将被重新分配给其中一个故障转移consumer,而新分配的consumer将成为新的主consumer。发生这种情况时,所有未ack的消息都将传递给新的主consumer,类似于Kafka中的consumer group分区重新平衡,属于流(Stream)模式,可以保证同一分区中数据有序。例如,Failover订阅sub-F中有2个消费者consumer-1和consumer-2,订阅有3个分区test-0、test-1和test-2的主题test,那么结果类似于consumer-1消费test-0,test-2,consumer-2消费test-1分区中的数据。Shared:共享订阅同一订阅中可以包含多个consumer,所有分区中的消息以轮询的方式分发给每个consumer,并且任何给定的消息仅传递给一个consumer。当consumer断开连接时,所有传递给它并且未被ack的消息将被重新安排,以便发送给该订阅上剩余的consumer。同一topic-partition的数据可以被多个consumer共享消费。属于队列(Queue)模式,不能保证数据有序。例如,Shared订阅sub-S中有2个消费者consumer-1和consumer-2,订阅test-0分区其中有5条消息,msg-0~msg-4,那么结果类似于consumer-1消费到msg-0,msg-2,msg-4,consumer-2消费到msg-1,msg-3。Key_Shared:Key保序共享订阅类似于共享订阅,但不是按轮询方式分发,而是按照key进行分发,比如按同一特征(奇数、偶数等)。此模式融合了Failover的有序性和Shared的消费扩展性,是一种更均衡的订阅模式。

Cursor

Cursor在consumer端,代表了每个订阅组的消费状态(等同于Kafka中的offset,这里可以将表示消息的四元组Message ID简化成二元组ledger-id,entry-id)。所有订阅状态的管理是由broker负责,追踪每个订阅消费到了哪里,并存储到cursor中,然后提供到客户端接口Acknowledge Cumulatively(等同于Kafka中的commit offset),后续再进行相应的操作,比如阿里或重置。
Reader

Cursor相当于让Pulsar来管理consumer的消费状态,但有些应用场景不需要Pulsar来管理(比如要做exactly-once-processing,将消费状态跟数据库的修改放到同一个事务操作里面),因此引入了Reader这个概念(Non-durable Cursor),即Reader(本质是一个Consumer)的消费状态不会被持久化,只存在内存里,当broker crash恢复后其消费状态会丢失。例如,Flink的checkpoint机制可以将当前的消费状态跟计算状态作为一个整体保存下来,当flink job crash重启后从保存的状态中找到Message ID重新打开一个Reader进行消费。