目录
2、Multi-topic subscriptions 多主题订阅
1、Subscriptions 消息订阅
订阅是一个被命名的配置规则,该规则决定如何将消息传递给使用者。Pulsar 有四种可用的订阅类型:独占类型(exclusive)、共享类型(shared)、容错类型(failover)、共享 key 类型(key_shared)。这些类型的图示说明如下:
发布订阅或者消息排队
在 Pulsar 中,你可以灵活使用不同的订阅类型:
- 如果想要在多个消费者之间实现传统的消息发布和订阅,可以为每个消费者都指定一个唯一的订阅名称。这种订阅类型称为独占(exclusive)订阅类型。
- 如果想要在多个消费者之间实现消息队列,让多个消费者能够共享相同的订阅名称,那么可以使用共享(shared)、容错(failover)或者共享key(key_shared)等订阅类型来实现。
- 如果想要同时实现这两种效果,可以将独占订阅类型与消费者的其他订阅类型结合使用。
(1)Subscription types 订阅类型
如果一个订阅没有消费者,那么无法确定该订阅的类型。订阅的类型需要在消费者连接到该订阅时确定,该类型也可以根据消费者的不同配置在重启所有消费者后进行改变。// 改变订阅类型需要重启所有的消费者
独占订阅类型(Exclusive)
在独占订阅类型中,仅允许一个消费者进行订阅。如果多个消费者连接到该订阅类型,除了允许其中一个消费者进行订阅外,其他消费者创建时将会发生错误。Exclusive 是默认的订阅类型。
在下图中,仅允许 Consumer A-0 消费消息。
容错订阅类型(Failover)
在容错订阅类型中,可以有多个消费者添加到同一个订阅。同时,会从多个消费者中会选择一个消费者作为主节点(master)来接收主题的消息。如果主节点断开连接,所有剩余的消息(未确认和后续生产的)都会被传递到队列中的下一个消费者进行消费。// 其实仍然只有一个消费者进行消费,其他消费者处于等待状态。
对于分区主题,代理(broker)将按优先级和消费者名称的词典顺序对消费者进行排序。然后尝试将主题平均分配给具有最高优先级的消费者。
对于非分区主题,代理(broker)将按照消费者订阅主题的顺序选择消费者。
在下图中,Consumer-B-0 是主 Consumer,如果 Consumer-B-0 断开连接,Consumer-B-1 将是下一个接收消息的 Consumer。
共享订阅类型(Shared)
在共享订阅类型中,可以有多个消费者添加到同一个订阅。消息在消费者之间以循环分发的方式传递,任何给定的消息都只传递给一个消费者。当其中一个消费者断开连接时,所有发送给它但未确认的消息都将重新安排发送给其余的消费者。// 消息只会被消费一次
在下图中,Consumer-C-1 和 Consumer-C-2 添加到了该类型的订阅,同时 Consumer-C-3 和其消费者也可以添加到此订阅。
共享订阅类型有一些的限制,使用该类型时,请注意:
- 不保证消息排序。
- 不能对共享类型使用累积确认。
共享key订阅类型(Key_Shared)
在共享 key 订阅类型中,可以有多个消费者添加到同一个订阅。消息会根据 Key 在消费者之间进行分配,具有相同 Key 或相同排序 Key 的消息仅会传递给一个消费者。无论该消息被重新传递多少次,它都会传递给同一个消费者。当消费者进行连接或断开连接时,都会将导致已经存在的消费者消费消息的 key 的变更。
请注意,当消费者使用 Key_Shared 订阅类型时,需要生产者禁用批处理或者只使用基于Key的批量处理。 Key_Shared 订阅类型必须使用基于 Key 的批处理(key-based batching)有两个原因:
- 代理(broker)根据消息的 Key 分发消息,但默认的批量处理方法可能无法将具有相同 Key 的消息打包到同一批量中。
- 另外,因为消费者而不是代理从批量中分派消息,因此批量中第一条消息的 Key 将被视为该批量中所有消息的 Key,从而导致消息传递错误。
基于 Key 的批量处理需要解决上述问题。该批量处理方法需要确保生产者将具有相同 Key 的消息打包到同一批量中,没有 Key 的消息打包到另一个批量中(该批量没有 Key),当代理(broker)从此批量处理中调度消息时,它使用 NON_KEY 作为 Key。此外,每个消费者仅与一个 Key 关联,并且应该只接收这个 Key 的批量消息。默认情况下,您可以通过配置允许生产者发送的消息数来限制批量消息的大小。
下面是在 Key_Shared 订阅类型下启用基于 Key 的批量处理的示例,客户端是你创建的 Pulsar 客户端:
Producer<byte[]> producer = client.newProducer()
.topic("my-topic")
.batcherBuilder(BatcherBuilder.KEY_BASED) // 启用批处理
.create();
使用 Key_Shared 订阅类型时有一些限制,使用 Key_Shared 订阅类型时,请注意:
- 需要为消息指定 Key 或排序 Key。
- 不能对 Key_Shared 订阅类型使用累积确认
(2)Subscription modes 订阅模式
什么是订阅模式?
订阅模式是指记录消费位置的游标类型。
- 创建订阅时,将创建一个关联的游标来记录上次消费的位置。
- 当该订阅的消费者重新连接时,它可以继续从最后一次消费消息的地方开始消费。
Subscription mode | Description | Note |
---|---|---|
| The cursor is durable, which retains messages and persists the current position. // 游标是持久的,它保留消息并保持当前位置。 // 如果代理从故障中重新启动,它可以从持久性存储(BookKeeper)中恢复游标,以便可以从上次消费的位置继续消费消息。 |
// 默认持久的 |
非持久的 | The cursor is non-durable. // 一旦代理停止,游标将丢失并且永远无法恢复,因此消息无法从上次消费的位置继续消费。 | Reader’s subscription mode is // 读者的订阅模式本质上是不持久的,它不会阻止删除主题中的数据。读者的订阅模式无法更。 |
订阅可以有一个或多个消费者。消费者消费主题的消息时,必须指定订阅的名称。持久订阅和非持久订阅可以具有相同的名称,它们彼此独立。如果消费者指定了之前不存在的订阅,则会自动创建该订阅。
什么时候使用订阅模式?
默认情况下,非持久订阅模式主题的消息会被标记为已删除。如果想要阻止消息被标记为已删除,可以为此主题创建持久订阅模式。在这种情况下,只有已确认的消息才会被标记为已删除。有关更多信息,请参阅消息保留和过期。
如何使用订阅模式?
创建消费者后,消费者的默认订阅模式是持久的。通过更改建消费者的配置,可以将订阅模式更改为非持久模式。
// 持久模式
Consumer<byte[]> consumer = pulsarClient.newConsumer()
.topic("my-topic")
.subscriptionName("my-sub")
.subscriptionMode(SubscriptionMode.Durable) // 持久模式
.subscribe();
// 非持久模式
Consumer<byte[]> consumer = pulsarClient.newConsumer()
.topic("my-topic")
.subscriptionName("my-sub")
.subscriptionMode(SubscriptionMode.NonDurable)
.subscribe();
有关如何创建、检查或删除持久订阅,请参阅订阅管理。
2、Multi-topic subscriptions 多主题订阅
当消费者通过订阅附加到 Pulsar 主题时,默认情况下,它只会订阅一个特定的主题,例如 persistent : // public / default / my-topic,然而,从 Pulsar 1.23.0-Cubating 版本开始,Pulsar 消费者可以同时订阅多个主题。你可以通过下边两种方式定义主题列表:
- 基于正则表达式(regex),例如:persistent : // public / default / finance-.*
- 通过明确的定义主题列表
通过 regex 订阅多个主题时,所有主题必须位于同一命名空间中。
// pulsar 可以跨命名空间订阅主题吗?
订阅多个主题时,Pulsar 客户端会自动的调用 Pulsar API 去发现与正则表达式匹配的主题,然后订阅所有主题。如果其中有主题不存在,一旦这些主题被创建,消费者会自动订阅这些主题。
跨多主题不保证消息顺序
当生产者将消息发送到单个主题时,所有消息都能保证以相同的顺序从该主题读取。但是,跨多个主题的情况下这些顺序并不能保证。因此,当生产者向多个主题发送消息时,消息在这些主题中的读取顺序不能保证与生产者发送的顺序一致。
以下是 Java 的多主题订阅示例:
import java.util.regex.Pattern;
import org.apache.pulsar.client.api.Consumer;
import org.apache.pulsar.client.api.PulsarClient;
PulsarClient pulsarClient = // Instantiate Pulsar client object
// Subscribe to all topics in a namespace
Pattern allTopicsInNamespace = Pattern.compile("persistent://public/default/.*");
Consumer<byte[]> allTopicsConsumer = pulsarClient.newConsumer()
.topicsPattern(allTopicsInNamespace) // 订阅命名空间下所有主题
.subscriptionName("subscription-1")
.subscribe();
// Subscribe to a subsets of topics in a namespace, based on regex
Pattern someTopicsInNamespace = Pattern.compile("persistent://public/default/foo.*");
Consumer<byte[]> someTopicsConsumer = pulsarClient.newConsumer()
.topicsPattern(someTopicsInNamespace) // 订阅命名空间下部分主题
.subscriptionName("subscription-1")
.subscribe();