了解Apache Pulsar中的游标
了解Apache Pulsar如何使用游标来确保没有消息被遗漏或重复消耗。
在我之前介绍Apache BookKeeper的博客中提到,Apache Pulsar为Apache BookKeeper中的每个订阅维护一个游标分类帐。在消费者处理了一个发给经纪商的确认信息,并且经纪商也收到了该信息之后,经纪商就会相应地更新游标账本。在这篇博客中,让我们仔细看看游标,它是如何工作的,以及与它相关的一些概念。
什么是游标
确保消息能够被成功消费是任何分布式消息系统的一个关键部分。在例外情况下,如客户端或服务器端的机器故障,一个具有强大容错能力的消息系统可以确保没有消息丢失或被重新接收。为了提供这样的能力,消息系统需要一个精确的消息消费和确认的跟踪机制。Apache Pulsar使用游标来实现这一目的。
从名字上我们可以看出,"光标 "这个词是指当你打字时在电脑屏幕上移动的闪烁的垂直线。它向你显示屏幕上将被添加到新文本的位置。同样,Pulsar中的光标告诉经纪人信息消费的进度。
为了全面了解光标,我们首先需要了解Pulsar中不同订阅类型的含义。订阅定义了一组消费者(一个或多个)如何消费一个主题上的消息。一个主题支持多个不同类型的订阅同时工作。这种设计理念背后的原因是,我们可以根据各种应用的需要,轻松配置不同的订阅模式。目前,Pulsar支持以下四种订阅类型:
- 独家。Pulsar中的默认订阅类型。独占式订阅只允许单个消费者读取该订阅的消息。如果其他消费者想要连接到该订阅,该请求将被经纪人拒绝。独占订阅提供了消息排序保证。
- 故障转移。支持与同一订阅相关的多个消费者,而只有一个实例可以处理消息。你可以把故障转移订阅视为独占订阅的 "高级 "版本。如果一个消费者由于某种原因无法接收消息,Pulsar会自动从实例池中选择另一个消费者来接管,保持消息消费的连续性。
- 共享的。允许多个消费者以轮流的方式从一个主题中消费消息。因此,共享订阅不能保证消息消费的顺序。尽管多个消费者收到共享订阅的消息,但在正常情况下没有消息被重复消费。如果一个消费者失败或突然断开连接,所有发送到该消费者的消息如果没有被确认,将被传递给其他消费者以获得确认。
- Key_Shared。与共享订阅类似,key_shared订阅也允许多个消费者附加到它上面。这种订阅类型确保消息根据密钥值被传递给不同的消费者;也就是说,具有相同密钥的消息被发送到同一个消费者。
在Pulsar中,一个主题中的每个订阅都有一个游标与之绑定。根据所采用的订阅类型,游标跟踪一个或多个消费者的消费和确认信息。例如,消费者共享共享或键_共享订阅的游标。Pulsar订阅类似于Kafka消费者组,而游标就像Kafka中的偏移量(Kafka使用数字偏移量来跟踪消费者的位置)。也就是说,游标的工作方式比偏移量要复杂得多。
注意,由于每个主题有可能有一个以上的订阅,你可以为该主题创建多个不同类型的订阅。这也意味着多个消费者组可以阅读同一主题的消息。在这种情况下,每个订阅的游标记录了各自组中消费者的消息消费位置,不会相互影响。
游标如何工作
在我们学习了这些订阅类型之后,让我们来看看游标是如何工作的。请看下图中的一个简单例子:
- 代理商向消费者发送一条消息。
- 在收到该消息后,消费者确认该消息,并将确认函发回给经纪人。
- 经纪人收到确认后移动游标,更新存储在BookKeeper中的游标分类帐。
由于经纪人是无状态的,而游标是有状态的,Pulsar在BookKeeper中存储消费位置信息。当经纪人收到消费者的确认时,它会更新消费者所绑定的订阅的游标分类帐。在消费者失败的情况下,消息消费和确认信息仍然是安全的,当消费重新开始时,没有消息会被重新接受。这是因为游标分类帐数据是根据配置的复制策略(即Ensemble Size、Write Quorum和Ack Quorum的值)安全地存储在bookies上。
注意 :一小部分游标元数据被存储在ZooKeeper而不是BookKeeper中,比如cursorLedger的索引信息。原因是随着主题和消费者数量的增加,游标元数据的大小会变得非常大,让ZooKeeper不堪重负,影响整个Pulsar集群的性能。
那么,在消费者确认了一个主题中的一条或多条消息后,光标是否肯定会移动?经纪人又会在多少个点(消息)上移动光标?为了回答这两个问题,我们必须首先了解Pulsar中的消息是如何被确认的。
消息是如何被确认的
默认情况下,Pulsar中的每个消息都需要被确认(简称ACK)。目前,Pulsar支持两种确认方式--单独确认和累积确认。
单独确认,也被称为选择性确认,允许消费者有选择地确认流中的单个消息。该消息之前或之后的消息可以保持不被确认。例如,对于一个共享订阅,多个消费者可能以不同的速度消费和确认消息,在那些已经确认的消息之间留下未确认的消息(这导致了 "确认漏洞",这将在后面讨论)。
累积确认允许消费者只确认它所处理的最后一条消息。通过这种方式,经纪人将该消息和在它之前传递的所有消息标记为已确认。因此,使用累积确认有利于提高确认的效率。
并非所有的订阅类型都支持两种方式的确认。单个确认可以为所有四种订阅类型配置,而共享和key_shared订阅则不支持累积确认。
现在我们知道了Pulsar中消息的确认方式,我们来分析一下这两个问题。消费者确认消息后,光标不一定会移动。这是因为有可能在最新确认的消息之前,有些消息仍然没有被确认。经纪人将光标向前移动多少个点取决于订阅所采用的确认方法。例如,如果有五条消息通过累积确认被确认,光标就会向前移动五个点,前提是前面的所有消息都已经被确认。
要知道光标在一个主题中的确切位置,我们可以检查光标的markDeletePosition 属性,该属性标志着已确认的消息的位置在最古老的未确认的消息之前。由于该消息和在它之前的所有消息都已被确认,它们可以被删除。
注意 :你可以通过使用pulsar-admin topics stats 命令来检查一个主题的细节,该命令的输出包含与游标相关的信息,如markDeletePosition,cursorLedger, 和individuallyDeletedMessages 。欲了解更多信息,请参见Pulsar文档。
总而言之,经纪人是否移动光标与markDeletePosition 属性有关。由markDeletePosition 标记的消息和在它之前的所有消息都已经被消费并成功确认。请注意,由于光标所标记的消费位置只适用于光标所关联的订阅,因此有可能多个订阅光标在一个主题上处于不同的位置。请看下图作为一个例子。
确认孔(Acknowledgment Holes
与Kafka和RocketMQ等消息系统不同,Pulsar同时支持单独确认和累积确认。当使用单个确认时,在已确认的消息之间可能存在未确认的消息,因为多个消费者实例会收到消息并自行确认它们(订阅类型是共享的)。这些未被确认的消息也被称为 "确认孔"。
Pulsar中的游标使用一个叫做individuallyDeletedMessages 的抽象概念来记录确认孔的信息。具体来说,这个抽象概念包含多个具有开放和关闭间隔的范围(确认信息以范围的形式存储)。一个开放的区间意味着消息没有被确认,而一个封闭的区间代表一个被确认的消息。在下面的例子中,消息M4和M7没有被确认,所以区间是开放的。
如图所示,一个确认孔指的是两个连续区间之间未确认的消息。一个订阅的消费者的数量和他们的消费率都可能影响到确认洞的数量。如上所述,当确认孔存在时,光标(更准确地说,markDeletePosition )会停留在最古老的未确认消息之前。在一定范围内的所有消息都被确认后,较小的范围被合并。因此,光标会相应地移动。
注意 :如何在生产中减少确认孔的数量是一个非常复杂的话题。毕竟,可能有多种因素会导致确认孔,如经纪人或客户的异常。一般来说,根据individuallyDeletedMessages ,我们可以尝试减少消费者的数量,并调整消息确认的频率,以实现性能优化。
TTL是如何影响游标的
默认情况下,Pulsar会永远保存所有未确认的消息。你可能会想,如果消息由于某种原因长时间不被确认,游标是否会保持静态。实际上,我们应该避免存在大量未确认消息的情况,因为这可能意味着对磁盘空间的巨大压力。在这种情况下,光标是否移动取决于Pulsar的生存时间(TTL)。
通过设置TTL策略,我们可以定义未确认消息的保留期。在配置的时间范围之后,Pulsar会自动确认这些消息,迫使光标移动。这也意味着这些信息可以被删除。
注意 :本博客没有详细解释TTL,因为它与Pulsar中的消息保留和删除策略有关,这是另一个复杂的话题,需要另一篇博客来详细讨论。在这里,你只需要知道我们仍然有另一种确认消息的方式,在消息不能被消费者确认的情况下,可以移动光标。关于TTL的更多信息,请参阅消息保留和过期。
总结
这篇博客介绍了游标的概念以及它是如何工作的。为了清楚地了解游标,我们还谈到了Pulsar中不同的订阅类型以及消息确认的两种方式。游标机制不仅保证了分布式消息系统的消息消耗和确认的准确性,而且还提供了容错的能力。即使客户端出错,游标也能保证消息不被重复消费,并且在消费重启后不会遗漏消息。