可以使用 Pulsar 的 Java 客户端来创建 生产者,消费者 和 消息读取器 ,以及进行 任务管理 。 当前Java客户端版本为2.8.0。
在 Java 客户端所创建的 生产者 ,消费者 和 读取器 中所提供的方法都是线程安全的。
Pulsar 客户端的 Javadoc 按包分为两个域,如下所示。
包 | 描述 | Maven Artifact |
---|---|---|
[org.apache.pulsar.client.api ](https://pulsar.apache.org/api/client/2.8.0-SNAPSHOT) |
生产者和消费者 API | org.apache.pulsar:pulsar-client:2.8.0 |
[org.apache.pulsar.client.admin ](https://pulsar.apache.org/api/admin/2.8.0-SNAPSHOT) |
The Java admin API | org.apache.pulsar:pulsar-client-admin:2.8.0 |
本文档仅关注用于生产和消费 Pulsar 主题消息的客户端 API。有关如何使用 Java admin client,请参阅 Pulsar admin interface 。
最新版本的 Pulsar Java 客户端库可通过 Maven Central 获得。要使用最新版本,请将pulsar-client
库添加到你的构建配置中。
如果使用 Maven,请将以下信息添加到pom.xml
文件中
2.8.0 org.apache.pulsar pulsar-client ${pulsar.version}
按对应的标签提示添加到指定位置
要使用客户端库连接到 Pulsar,您需要指定 Pulsar 协议 URL。
您可以将 Pulsar 协议 URL 分配给特定集群并使用该 pulsar
方案。默认端口是 6650
。下面是一个 localhost
例子。
pulsar://localhost:6650
如果你有多个brokers,则 URL 如下。
pulsar://localhost:6550,localhost:6651,localhost:6652
生产 Pulsar 集群的 URL 如下。
pulsar://pulsar.us-west.example.com:6650
如果使用 TLS 认证,则 URL 如下。
pulsar+ssl://pulsar.us-west.example.com:6651
你可以仅使用目标Pulsar 集群 的URL实例化 PulsarClient 对象,如下所示:
PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://localhost:6650") .build();
如果你有多个 broker,你可以像这样创建一个 PulsarClient:
PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://localhost:6650,localhost:6651,localhost:6652") .build();
单机集群的默认broker URL
如果你运行的是standalone mode 模式的集群,broker可以默认通过
pulsar://localhost:6650
URL来访问
如果创建客户端,则可以使用 loadConf
配置。在 loadConf
中有如下参数。
类型 | 配置项 | 说明 | 默认值 |
---|---|---|---|
String | serviceUrl |
Pulsar 服务的服务 URL 提供者 | None |
String | authPluginClassName |
认证插件的名字 | None |
String | authParams |
认证插件的字符串表示参数 示例 key1:val1, key2:val2 | None |
long | operationTimeoutMs |
操作超时时长(ms) | 30000 |
long | statsIntervalSeconds |
每个统计信息之间的间隔 通过 positive statsInterval 将statsIntervalSeconds设置为至少1秒,Stats被激活 |
60 |
int | numIoThreads |
用于处理到brokers的连接的线程数 | 1 |
int | numListenerThreads |
用于处理消息监听器的线程数。监听器线程池在所有消费者和阅读器之间共享,使用“监听器”模型来获取消息。对于给定的消费者,侦听器始终从同一线程调用以确保排序。如果您希望多个线程处理单个主题,则需要创建一个[shared ](https://pulsar.apache.org/docs/en/next/concepts-messaging/#shared)订阅,并为此订阅创建多个消费者。这并不能确保排序。 |
1 |
boolean | useTcpNoDelay |
是否在连接上使用TCP无延迟标志来禁用Nagle算法 | true |
boolean | useTls |
是否在连接上使用 TLS 加密 | false |
String | tlsTrustCertsFilePath |
受信任的 TLS 证书文件的路径 | None |
boolean | tlsAllowInsecureConnection |
Pulsar 客户端是否接受来自 broker 的不受信任的 TLS 证书 | false |
boolean | tlsHostnameVerificationEnable |
是否开启 TLS 主机名验证 | false |
int | concurrentLookupRequest |
允许在每个broker连接上发送的并发查找请求数,以防止broker过载 | 5000 |
int | maxLookupRequest |
每个broker连接上允许的最大查找请求数,以防止broker过载 | 50000 |
int | maxNumberOfRejectedRequestPerConnection |
在当前连接关闭且客户端创建新连接以连接到另一个broker之后的某一时间段(30秒)内broker的请求被拒绝的最大次数 | 50 |
int | keepAliveIntervalSeconds |
每个客户端broker连接的保持活跃间隔的秒数 | 30 |
int | connectionTimeoutMs |
等待建立连接到broker的持续时间,如果持续时间过了,没有从broker得到响应,连接尝试将被放弃 | 10000 |
int | requestTimeoutMs |
完成请求的最长持续时间 | 60000 |
int | defaultBackoffIntervalNanos |
backoff 间隔的默认持续时间 | TimeUnit.MILLISECONDS.toNanos(100) |
long | maxBackoffIntervalNanos |
backoff 间隔的最大持续时间 | TimeUnit.SECONDS.toNanos(30) |
查看 PulsarClient 类的 Javadoc 以获取可配置参数的完整列表。
除了客户端级别的配置之外,你还可以应用特定于 生产者 和 消费者 的配置,如下文部分所述。
在Pulsar中,生产者写消息到主题中。 一旦你实例化一个 PulsarClient 客户端对象(在 如上 章节),你可以为一个特定的Pulsar 主题 创建一个 生产者 。
Producerproducer = client.newProducer() .topic("my-topic") .create(); // You can then send messages to the broker and topic you specified: producer.send("My message".getBytes());
默认情况下,生产者生成由字节数组组成的消息。您可以通过指定消息 架构 来生成不同的类型。
ProducerstringProducer = client.newProducer(Schema.STRING) .topic("my-topic") .create(); stringProducer.send("My message");
确保在不需要时关闭生产者、消费者和客户端。
producer.close(); consumer.close(); client.close();关闭操作也可以是异步的:
producer.closeAsync() .thenRun(() -> System.out.println("Producer closed")) .exceptionally((ex) -> { System.err.println("Failed to close producer: " + ex); return null; });
如果你像上面的例子一样,只指定主题名来实例化一个Producer
对象,那么使用producer的默认配置。
如果你创建了一个生产者,你可以使用 loadConf
配置。在 loadConf
中可以使用以下参数。
类型 | 配置项 | 说明 | 默认值 |
---|---|---|---|
String | topicName |
主题名称 | null |
String | producerName |
生产者名称 | null |
long | sendTimeoutMs |
消息发送超时(ms)。如果消息在' sendTimeout '到期之前没有得到服务器的确认,则会发生错误。 | 30000 |
boolean | blockIfQueueFull |
如果它被设置为 true ,当传出消息队列已满时,生产者的 Send 和 SendAsync 方法会阻塞,而不是失败和抛出错误。如果它被设置为 false ,当传出消息队列已满时,生产者的 Send 和 SendAsync 方法失败,并发生 ProducerQueueIsFullError 异常。 MaxPendingMessages 参数决定传出消息队列的大小。 |
false |
int | maxPendingMessages |
持有挂起消息的队列的最大大小。例如,等待从 broker 接收确认的消息。默认情况下,当队列满时,所有对 Send 和 SendAsync 方法的调用都会失败,除非你将 BlockIfQueueFull 设置为 true 。 |
1000 |
int | maxPendingMessagesAcrossPartitions |
跨分区挂起消息的最大数目。使用这个设置 当总数超过配置的值时 来降低每个分区的最大挂起消息({@link #setMaxPendingMessages(int)})。 | 50000 |
MessageRoutingMode | messageRoutingMode |
针对 分区主题 的生产者的消息路由逻辑。只有在消息上没有设置key时才应用此逻辑。可供选择的选项如下: pulsar.RoundRobinDistribution :轮循 pulsar.UseSinglePartition :将所有消息发布到单个分区 pulsar.CustomPartition :自定义分区方案 |
pulsar.RoundRobinDistribution |
HashingScheme | hashingScheme |
确定在哪个分区发布特定消息(仅限分区主题)的哈希函数。可供选择的选项如下: pulsar.JavaStringHash :相当于Java中的 String.hashCode() 。 pulsar.Murmur3_32Hash :应用 Murmur3 哈希函数。 pulsar.BoostHash :应用C++的 Boost 库中的哈希函数 |
HashingScheme.JavaStringHash |
ProducerCryptoFailureAction | cryptoFailureAction |
当加密失败时,生产者应该采取行动。FAIL:加密失败,未加密消息发送失败。SEND:如果加密失败,则发送未加密消息。 | ProducerCryptoFailureAction.FAIL |
long | batchingMaxPublishDelayMicros |
发送消息的批处理时间周期 | TimeUnit.MILLISECONDS.toMicros(1) |
int | batchingMaxMessages |
批处理中允许的最大消息数 | 1000 |
boolean | batchingEnabled |
启用消息批处理 | true |
CompressionType | compressionType |
生产者使用的消息数据压缩类型。可用选项:[LZ4 ](https://github.com/lz4/lz4) [ZLIB ](https://zlib.net/) [ZSTD ](https://facebook.github.io/zstd/) [SNAPPY ](https://google.github.io/snappy/) |
No compression |
如果不想使用默认配置,可以配置参数。
有关完整列表,请参阅 ProducerBuilder 类的 Javadoc 。下面是一个例子。
Producerproducer = client.newProducer() .topic("my-topic") .batchingMaxPublishDelay(10, TimeUnit.MILLISECONDS) .sendTimeout(10, TimeUnit.SECONDS) .blockIfQueueFull(true) .create();
使用分区主题时,你可以在使用生产者发布消息时指定路由模式。有关使用 Java 客户端指定路由模式的更多信息,请参阅 分区主题 手册。
你可以使用 Java 客户端 异步 发布消息。使用异步发送,生产者将消息放入阻塞队列并立即返回。然后客户端库将消息发送到后台的broker。如果队列已满(最大大小可配置),则在调用 API 时,生产者会被阻塞或立即失败,具体取决于传递给生产者的参数。
下面是一个例子。
producer.sendAsync("my-async-message".getBytes()).thenAccept(msgId -> { System.out.println("Message with ID " + msgId + " successfully sent"); });
正如你可以从上面的例子中看到的,异步发送操作返回一个包装在[CompletableFuture
](http://www.baeldung.com/java-completablefuture)中的 MessageId 。
除了值之外,你还可以在给定的消息上设置其他项:
producer.newMessage() .key("my-message-key") .value("my-async-message".getBytes()) .property("my-key", "my-value") .property("my-other-key", "my-other-value") .send();
你可以使用sendAsync()来终止builder链并获得一个future返回。
在Pulsar中,消费者订阅主题,并处理生产者发布到这些主题的消息。你可以实例化一个新的 消费者 ,首先实例化一个 PulsarClient 对象,给它传递一个Pulsar broker的URL(如上所述)。
一旦实例化了 PulsarClient 对象,就可以通过指定 主题 和 订阅 来创建一个 消费者 。
Consumer consumer = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscribe();
subscribe
方法将自动将消费者订阅到指定的主题和订阅。让消费者监听主题的一种方法是设置一个while
循环。在这个示例循环中,使用者监听消息,打印任何接收到的消息的内容,然后 确认 消息已被处理。如果处理逻辑失败,您可以使用 negative acknowledgement 来稍后重新传递消息。
while (true) { // Wait for a message Message msg = consumer.receive(); try { // Do something with the message System.out.println("Message received: " + new String(msg.getData())); // Acknowledge the message so that it can be deleted by the message broker consumer.acknowledge(msg); } catch (Exception e) { // Message failed to process, redeliver later consumer.negativeAcknowledge(msg); } }
如果您不想阻塞主线程而是不断地监听新消息,请考虑使用 MessageListener
。
MessageListener myMessageListener = (consumer, msg) -> { try { System.out.println("Message received: " + new String(msg.getData())); consumer.acknowledge(msg); } catch (Exception e) { consumer.negativeAcknowledge(msg); } } Consumer consumer = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .messageListener(myMessageListener) .subscribe();
如果你像上面的例子中那样只指定主题和订阅名来实例化一个 Consumer
对象,那么消费者将使用默认配置。
当你创建一个消费者时,你可以使用 loadConf
配置。在 loadConf
中可以使用以下参数。
类型 | 配置项 | 说明 | 默认值 |
---|---|---|---|
Set |
topicNames |
主题名称 | Sets.newTreeSet() |
Pattern | topicsPattern |
主题模式 | None |
String | subscriptionName |
订阅名称 | None |
SubscriptionType | subscriptionType |
订阅模式。四种可用的订阅模式:Exclusive、Failover、Shared、Key_Shared | SubscriptionType.Exclusive |
int | receiverQueueSize |
消费者接收队列的大小。例如,在应用程序调用Receive之前,消费者积累的消息数。高于默认值的值会增加消费者的吞吐量,但会以更多的内存使用为代价。 | 1000 |
long | acknowledgementsGroupTimeMicros |
将消费者确认按指定时间分组。默认情况下,消费者使用100ms分组时间向broker发送确认。将组时间设置为0将立即发送确认。更长的ack组时间是更有效率的,代价是在失败后稍微增加消息的重新交付。 | TimeUnit.MILLISECONDS.toMicros(100) |
long | negativeAckRedeliveryDelayMicros |
在重新发送处理失败的消息之前的等待延迟。当应用程序使用{@link Consumer#negativeAcknowledge(Message)}时,失败的消息会在固定的超时后重新传递。 | TimeUnit.MINUTES.toMicros(1) |
int | maxTotalReceiverQueueSizeAcrossPartitions |
跨分区的最大接收队列总大小。如果接收队列的总大小超过此值,则此设置将减少单个分区的接收队列大小。 | 50000 |
String | consumerName |
消费者名称 | null |
long | ackTimeoutMillis |
未确认消息的超时时间 | 0 |
long | tickDurationMillis |
ack-timeout重发粒度。当将ack-timeout设置为更大的值(例如,1小时)时,使用更高的tickDurationMillis 可以减少用于跟踪消息的内存开销。 |
1000 |
int | priorityLevel |
在共享订阅模式中分发消息时,broker给予consumer更多优先权的优先级。 broker的优先级由高到低。例如,0=max-priority,1,2,… 在共享订阅模式中,如果最大优先级消费者有许可,broker首先将消息分发给他们。否则,broker将考虑下一个优先级消费者。 Example 1 如果一个订阅拥有 priorityLevel 0的consumerA和 priorityLevel 1的consumerB,那么broker 只会将消息分派给consumerA,直到许可证用完 ,然后开始将消息分派给consumerB。 Example 2 Consumer, Priority Level, Permits C1, 0, 2 C2, 0, 1 C3, 0, 1 C4, 1, 2 C5, 1, 1 broker将消息分派给消费者的顺序:C1,C2,C3,C1,C4,C5,C4。 |
0 |
ConsumerCryptoFailureAction | cryptoFailureAction |
当消费者收到无法解密的消息时,应该采取行动。 FAIL:这是默认选项,消息失败,直到加密成功。 DISCARD:静默地确认消息而不向应用程序传递消息。 CONSUME:向应用程序传递加密的消息。应用程序负责解密消息。 消息解压缩失败。 如果消息包含批处理消息,则客户端无法在batch中检索单个消息。 发送的加密消息包含{@link EncryptionContext},其中包含加密和压缩信息,应用程序可以使用这些信息解密所消费的消息有效负载。 | ConsumerCryptoFailureAction.FAIL |
SortedMap |
properties |
此消费者的名称或值属性。 properties 是附加到consumer的应用程序定义的元数据。 在获取主题统计信息时,请将此元数据与消费者统计信息关联起来,以便于识别。 |
new TreeMap<>() |
boolean | readCompacted |
如果启用 readCompacted ,消费者将从压缩的主题读取消息,而不是读取主题的完整消息backlog。 消费者只看到压缩主题中每个key的最新值,直到压缩backlog时达到主题消息中的点为止。超过这个点,像往常一样发送信息。 仅在订阅持久性主题时启用 readCompacted ,这些主题只有一个活跃的消费者(如failure或exclusive订阅)。 试图在订阅非持久主题或共享订阅时启用它会导致订阅调用抛出 PulsarClientException 。 |
false |
SubscriptionInitialPosition | subscriptionInitialPosition |
第一次订阅主题时设置指针(cursor)的初始位置。 | SubscriptionInitialPosition.Latest |
int | patternAutoDiscoveryPeriod |
当使用主题消费者的模式时,主题自动发现周期。默认值(最小值)为1分钟。 | 1 |
RegexSubscriptionMode | regexSubscriptionMode |
使用正则表达式订阅主题时,可以选择特定类型的主题。 PersistentOnly :只订阅持久主题。 NonPersistentOnly :只订阅非持久主题。 AllTopics :订阅持久和非持久主题。 |
RegexSubscriptionMode.PersistentOnly |
DeadLetterPolicy | deadLetterPolicy |
消费者的Dead letter策略。 在默认情况下,一些消息可能会被多次重传,甚至达到从未停止的程度。 通过使用dead letter机制,消息具有最大重传次数。当超过重新投递的最大次数时,消息将被发送到Dead Letter主题并自动确认。 你可以通过设置 deadLetterPolicy 来启用dead letter机制。 Example client.newConsumer().deadLetterPolicy(DeadLetterPolicy.builder().maxRedeliverCount(10).build()).subscribe(); 默认dead letter主题名称是 {TopicName}-{Subscription}-DLQ 。 设置自定义的dead letter主题名称: client.newConsumer().deadLetterPolicy(DeadLetterPolicy.builder().maxRedeliverCount(10).deadLetterTopic("your-topic-name").build()).subscribe(); 当指定dead letter策略而不指定 ackTimeoutMillis 时,可以将ack超时设置为30000毫秒。 |
None |
boolean | autoUpdatePartitions |
如果启用了 autoUpdatePartitions ,消费者将自动订阅分区增加。注意:这只针对分区消费者。 |
true |
boolean | replicateSubscriptionState |
如果 replicateSubscriptionState 被启用,订阅状态将被复制到异地复制集群。 |
false |
如果您不想使用默认配置,可以配置参数。有关完整列表,请参阅 ConsumerBuilder 类的Javadoc。
下面是一个例子。
Consumer consumer = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .ackTimeout(10, TimeUnit.SECONDS) .subscriptionType(SubscriptionType.Exclusive) .subscribe();
该 receive
方法同步接收消息(消费者进程被阻塞,直到有消息可用)。您还可以使用 异步接收 ,一旦有新消息可用,它会立即返回一个 [CompletableFuture
](http://www.baeldung.com/java-completablefuture) 对象。
下面是一个例子。
CompletableFutureasyncMessage = consumer.receiveAsync();
异步接收操作返回包装在 [CompletableFuture
](http://www.baeldung.com/java-completablefuture) 中的 Message 。
使用 batchReceive
为每次调用接收多个消息。
下面是一个例子。
Messages messages = consumer.batchReceive(); for (Object message : messages) { // do something } consumer.acknowledge(messages)
笔记
批接收策略限制单个批处理中的消息数量和字节数。您可以指定一个超时时间以等待足够的消息。
如果满足下列条件中的任何一个,则batch接收完成:足够的消息数量、消息字节数、等待超时。
Consumer consumer = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .batchReceivePolicy(BatchReceivePolicy.builder() .maxNumMessages(100) .maxNumBytes(1024 * 1024) .timeout(200, TimeUnit.MILLISECONDS) .build()) .subscribe();默认批量接收策略为:
BatchReceivePolicy.builder() .maxNumMessage(-1) .maxNumBytes(10 * 1024 * 1024) .timeout(100, TimeUnit.MILLISECONDS) .build();
消费者除了订阅单个Pulsar主题外,你还可以使用 多主题订阅 同时订阅多个主题。若要使用多主题订阅,可以提供一个 正则表达式(regex) 或 主题 List
。 如果通过 正则表达式 选择主题, 则所有主题都必须位于同一Pulsar命名空间中。
以下是一些示例。
import org.apache.pulsar.client.api.Consumer; import org.apache.pulsar.client.api.PulsarClient; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; ConsumerBuilder consumerBuilder = pulsarClient.newConsumer() .subscriptionName(subscription); // Subscribe to all topics in a namespace Pattern allTopicsInNamespace = Pattern.compile("public/default/.*"); Consumer allTopicsConsumer = consumerBuilder .topicsPattern(allTopicsInNamespace) .subscribe(); // Subscribe to a subsets of topics in a namespace, based on regex Pattern someTopicsInNamespace = Pattern.compile("public/default/foo.*"); Consumer allTopicsConsumer = consumerBuilder .topicsPattern(someTopicsInNamespace) .subscribe();
在上面的示例中,消费者订阅了能够匹配主题名称格式的 持久
主题。如果希望消费者订阅所有可以匹配主题名称格式的 持久
和 非持久
主题,请将 subscriptionTopicsMode
设置为 RegexSubscriptionMode.AllTopics
。
Pattern pattern = Pattern.compile("public/default/.*"); pulsarClient.newConsumer() .subscriptionName("my-sub") .topicsPattern(pattern) .subscriptionTopicsMode(RegexSubscriptionMode.AllTopics) .subscribe();
笔记
默认情况下,消费者的
subscriptionTopicsMode
是PersistentOnly
。subscriptionTopicsMode
的可用选项有PersistentOnly
,NonPersistentOnly
和AllTopics
。
你还可以订阅明确的主题列表(如果愿意,可跨命名空间):
Listtopics = Arrays.asList( "topic-1", "topic-2", "topic-3" ); Consumer multiTopicConsumer = consumerBuilder .topics(topics) .subscribe(); // Alternatively: Consumer multiTopicConsumer = consumerBuilder .topic( "topic-1", "topic-2", "topic-3" ) .subscribe();
你还可以使用 subscribeAsync
方法而不是 同步订阅方法 异步订阅多个主题。下面是一个例子。
Pattern allTopicsInNamespace = Pattern.compile("persistent://public/default.*"); consumerBuilder .topics(topics) .subscribeAsync() .thenAccept(this::receiveMessageFromConsumer); private void receiveMessageFromConsumer(Object consumer) { ((Consumer)consumer).receiveAsync().thenAccept(message -> { // Do something with the received message receiveMessageFromConsumer(consumer); }); }
Pulsar 有多种 订阅模式 来匹配不同的场景。一个主题可以有多个 不同订阅模式 的订阅。但是,一个subscription一次只能有一种订阅模式。
订阅与订阅名称相同,一次只能指定一种订阅方式。除非此订阅的所有现有消费者都处于离线状态,否则你无法更改订阅模式。
不同的订阅方式有不同的消息分发模式。本节介绍订阅模式的区别和使用方法。
为了更好地描述它们的区别,假设您有一个名为 my-topic
的主题,并且生产者已经发布了 10 条消息。
Producerproducer = client.newProducer(Schema.STRING) .topic("my-topic") .enableBatching(false) .create(); // 3 messages with "key-1", 3 messages with "key-2", 2 messages with "key-3" and 2 messages with "key-4" producer.newMessage().key("key-1").value("message-1-1").send(); producer.newMessage().key("key-1").value("message-1-2").send(); producer.newMessage().key("key-1").value("message-1-3").send(); producer.newMessage().key("key-2").value("message-2-1").send(); producer.newMessage().key("key-2").value("message-2-2").send(); producer.newMessage().key("key-2").value("message-2-3").send(); producer.newMessage().key("key-3").value("message-3-1").send(); producer.newMessage().key("key-3").value("message-3-2").send(); producer.newMessage().key("key-4").value("message-4-1").send(); producer.newMessage().key("key-4").value("message-4-2").send();
5.5.1 独占(Exclusive)
创建一个新的消费者并使用 Exclusive
订阅模式订阅。
Consumer consumer = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscriptionType(SubscriptionType.Exclusive) .subscribe()
只有第一个消费者被允许订阅,其他消费者将收到一个错误。第一个消费者接收所有10条消息,并且消费顺序与生产顺序相同。
笔记
如果topic是一个分区主题,则第一个消费者订阅所有分区主题,其他消费者不会被分配分区并收到一个错误。
5.5.2 灾备(Failover)
创建新的消费者并使用 Failover
订阅模式订阅。
Consumer consumer1 = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscriptionType(SubscriptionType.Failover) .subscribe() Consumer consumer2 = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscriptionType(SubscriptionType.Failover) .subscribe() //conumser1 is the active consumer, consumer2 is the standby consumer. //consumer1 receives 5 messages and then crashes, consumer2 takes over as an active consumer.
多个消费者可以附加到同一个订阅,但只有第一个消费者是活跃的,其他消费者是备用的。当活跃消费者断开连接时,消息将被发送给备用消费者之一,然后备用消费者将成为活跃消费者。
如果第一个活跃消费者在收到5条消息后断开连接,则备用消费者变为活跃消费者。Consumer1将会收到:
("key-1", "message-1-1") ("key-1", "message-1-2") ("key-1", "message-1-3") ("key-2", "message-2-1") ("key-2", "message-2-2")
Consumer2将会收到:
("key-2", "message-2-3") ("key-3", "message-3-1") ("key-3", "message-3-2") ("key-4", "message-4-1") ("key-4", "message-4-2")
笔记
如果一个主题是分区主题,则每个分区只有一个活跃消费者,一个分区的消息只分发给一个消费者,多个分区的消息分发给多个消费者。
5.5.3 共享(Shared)
创建新的消费者并使用 Shared
订阅模式订阅:
Consumer consumer1 = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscriptionType(SubscriptionType.Shared) .subscribe() Consumer consumer2 = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscriptionType(SubscriptionType.Shared) .subscribe() //Both consumer1 and consumer 2 is active consumers.
在共享订阅模式中,多个消费者可以附加到相同的订阅,并且消息在消费者之间以轮询分发的方式传递。
如果broker一次只发送一条消息,则consumer1接收到以下信息。
("key-1", "message-1-1") ("key-1", "message-1-3") ("key-2", "message-2-2") ("key-3", "message-3-1") ("key-4", "message-4-1")
consumer2接收到以下信息。
("key-1", "message-1-2") ("key-2", "message-2-1") ("key-2", "message-2-3") ("key-3", "message-3-2") ("key-4", "message-4-2")
Shared
订阅不同于 Exclusive
和 Failover
订阅模式。 Shared
订阅有更好的灵活性,但不能提供顺序保证。
5.5.4 Key_shared
这是自 2.4.0 发布以来的新订阅模式,创建新的消费者并使用 Key_Shared
订阅模式订阅。
Consumer consumer1 = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscriptionType(SubscriptionType.Key_Shared) .subscribe() Consumer consumer2 = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscriptionType(SubscriptionType.Key_Shared) .subscribe() //Both consumer1 and consumer2 are active consumers.
Key_Shared
订阅与 Shared
订阅类似,所有消费者都可以附加到相同的订阅。但它与 Key_Shared
订阅不同,具有相同key的消息按顺序仅传递给一个消费者。在不同消费者之间可能的消息分配(默认情况下,我们事先不知道哪些key会被分配给一个消费者,但一个key只会在同一时间被分配给一个消费者)。
consumer1收到以下信息。
("key-1", "message-1-1") ("key-1", "message-1-2") ("key-1", "message-1-3") ("key-3", "message-3-1") ("key-3", "message-3-2")
consumer2收到以下信息。
("key-2", "message-2-1") ("key-2", "message-2-2") ("key-2", "message-2-3") ("key-4", "message-4-1") ("key-4", "message-4-2")
如果在生产者端启用批处理,默认情况下,具有不同key的消息将被添加到批处理中。broker将把batch分发给消费者,因此默认的批处理机制可能会破坏Key_Shared订阅保证的消息分发语义。生产者需要使用 KeyBasedBatcher
。
Producer producer = client.newProducer() .topic("my-topic") .batcherBuilder(BatcherBuilder.KEY_BASED) .create();
或者生产者可以禁用批处理。
Producer producer = client.newProducer() .topic("my-topic") .enableBatching(false) .create();
笔记
如果未指定消息的key,则默认情况下将不带key的消息按顺序分发给一个消费者。
使用 reader interface ,Pulsar 客户端可以在一个主题中 手动定位
自己,并从指定的消息开始读取所有消息。Pulsar API for Java 使你能够通过指定主题和 MessageId 来创建 Reader 对象 。
下面是一个例子。
byte[] msgIdBytes = // Some message ID byte array MessageId id = MessageId.fromByteArray(msgIdBytes); Reader reader = pulsarClient.newReader() .topic(topic) .startMessageId(id) .create(); while (true) { Message message = reader.readNext(); // Process message }
在上面的示例中,为特定主题和消息(通过 ID)实例化了一个 Reader
对象;reader在消息被 msgIdBytes
(如何获得该值取决于应用程序)识别后遍历主题中的每条消息。
上面的示例代码展示了 Reader
对象指向特定的消息(通过ID),但你也可以使用 MessageId.earliest
来指向topic上最早可用的消息,使用 MessageId.latest
指向最新的消息。
在创建reader时,可以使用 loadConf
配置。在 loadConf
中有如下参数。
类型 | 配置项 | 说明 | 默认值 |
---|---|---|---|
String | topicName |
Topic名 | None |
int | receiverQueueSize |
消费者接收队列的大小。例如,在应用程序调用 Receive 之前,消费者可以积累的消息数量。高于默认值的值会增加consumer的吞吐量,但会以更多的内存使用为代价。 |
1000 |
ReaderListener |
readerListener |
为接收到消息而调用的监听器 | None |
String | readerName |
Reader名 | null |
String | subscriptionName |
Subscription名 | 当只有一个主题时,默认订阅名是 "reader-" + 10-digit UUID 。 当有多个主题时,默认订阅名是 "multiTopicsReader-" + 10-digit UUID 。 |
String | subscriptionRolePrefix |
订阅角色前缀 | null |
CryptoKeyReader | cryptoKeyReader |
抽象对key存储的访问的接口。 | null |
ConsumerCryptoFailureAction | cryptoFailureAction |
当消费者收到无法解密的消息时,应该采取行动。 FAIL:这是默认选项,消息失败,直到加密成功。 DISCARD:静默地确认消息而不向应用程序传递消息。 CONSUME:向应用程序传递加密的消息。应用程序负责解密消息。 消息解压缩失败。 如果消息包含批处理消息,则客户端无法在batch中检索单个消息。 发送的加密消息包含{@link EncryptionContext},其中包含加密和压缩信息,应用程序可以使用这些信息解密所消费的消息有效负载。 | ConsumerCryptoFailureAction.FAIL |
boolean | readCompacted |
如果启用 readCompacted ,消费者将从压缩的主题读取消息,而不是读取主题的完整消息backlog。 消费者只看到压缩主题中每个key的最新值,直到压缩backlog时达到主题消息中的点为止。超过这个点,像往常一样发送信息。 仅在订阅持久性主题时启用 readCompacted ,这些主题只有一个活跃的消费者(如failure或exclusive订阅)。 试图在订阅非持久主题或共享订阅时启用它会导致订阅调用抛出 PulsarClientException 。 |
false |
boolean | resetIncludeHead |
如果设置为true,则返回的第一个消息是由 messageId 指定的消息。 如果设置为false,则返回的第一个消息是位于 messageId 指定的消息旁边的消息。 |
false |
在粘性键范围读取器中,broker只会发送消息键的哈希值包含在指定键哈希范围内的消息。可以在Reader上指定多个键哈希范围。
以下是创建粘性键范围读取器的示例。
pulsarClient.newReader() .topic(topic) .startMessageId(MessageId.earliest) .keyHashRange(Range.of(0, 10000), Range.of(20001, 30000)) .create();
总的哈希范围大小是65536,所以该范围的最大端点应该小于或等于65535。
在 Pulsar 中,所有消息数据都由“幕后”字节数组组成。 Message schemas 使你能够在构造和处理消息时使用其他类型的数据(从简单的类型,如字符串到更复杂的、特定于应用程序的类型)。如果你在不指定schema的情况下构建 生产者 ,那么生产者只能生成类型为 byte[]
的消息。下面是一个例子。
Producerproducer = client.newProducer() .topic(topic) .create();
上面的生产者相当于一个 Producer
(实际上,您应该始终明确指定类型)。如果你想对不同类型的数据使用生产者,你需要指定一个schema来通知 Pulsar 将通过 主题 传输哪种数据类型。
假设您有一个 SensorReading
类要通过 Pulsar 主题传输:
public class SensorReading { public float temperature; public SensorReading(float temperature) { this.temperature = temperature; } // A no-arg constructor is required public SensorReading() { } public float getTemperature() { return temperature; } public void setTemperature(float temperature) { this.temperature = temperature; } }
然后你可以像这样创建一个 Producer
(或 Consumer
):
Producerproducer = client.newProducer(JSONSchema.of(SensorReading.class)) .topic("sensor-readings") .create();
以下schema格式目前适用于Java:
没有schema或字节数组schema(可以使用 Schema.BYTES
应用):
ProducerbytesProducer = client.newProducer(Schema.BYTES) .topic("some-raw-bytes-topic") .create();
或者,相当于:
ProducerbytesProducer = client.newProducer() .topic("some-raw-bytes-topic") .create();
普通utf-8编码字符串数据 的 String
。使用 Schema.STRING
应用于schema:
ProducerstringProducer = client.newProducer(Schema.STRING) .topic("some-string-topic") .create();
使用 Schema.JSON
为pojo创建JSON schemas。下面是一个例子。
ProducerpojoProducer = client.newProducer(Schema.JSON(MyPojo.class)) .topic("some-pojo-topic") .create();
使用 Schema.PROTOBUF
生成Protobuf schemas。下面的例子展示了如何创建Protobuf schema,并使用它实例化一个新的生产者:
ProducerprotobufProducer = client.newProducer(Schema.PROTOBUF(MyProtobuf.class)) .topic("some-protobuf-topic") .create();
使用 Schema.AVRO
定义Avro schemas。下面的代码片段演示了如何创建和使用Avro schema。
ProduceravroProducer = client.newProducer(Schema.AVRO(MyAvro.class)) .topic("some-avro-topic") .create();
Pulsar 目前支持三种认证方案: TLS 、 Athenz 和 Oauth2 。您可以将 Pulsar Java 客户端与所有这些一起使用。
要使用 TLS ,你需要使用 setUseTls
方法设置TLS为 true
,将你的Pulsar客户端指向TLS证书路径,并提供证书和密钥文件的路径。
下面是一个例子。
MapauthParams = new HashMap<>(); authParams.put("tlsCertFile", "/path/to/client-cert.pem"); authParams.put("tlsKeyFile", "/path/to/client-key.pem"); Authentication tlsAuth = AuthenticationFactory .create(AuthenticationTls.class.getName(), authParams); PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar+ssl://my-broker.com:6651") .enableTls(true) .tlsTrustCertsFilePath("/path/to/cacert.pem") .authentication(tlsAuth) .build();
要使用 Athenz 做为身份认证提供者,你需要使用 TLS 并且在hash中提供如下四个参数的值:
tenantDomain
tenantService
providerDomain
privateKey
你还可以设置一个可选的 keyId
。下面是一个例子。
MapauthParams = new HashMap<>(); authParams.put("tenantDomain", "shopping"); // Tenant domain name authParams.put("tenantService", "some_app"); // Tenant service name authParams.put("providerDomain", "pulsar"); // Provider domain name authParams.put("privateKey", "file:///path/to/private.pem"); // Tenant private key path authParams.put("keyId", "v1"); // Key id for the tenant private key (optional, default: "0") Authentication athenzAuth = AuthenticationFactory .create(AuthenticationAthenz.class.getName(), authParams); PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar+ssl://my-broker.com:6651") .enableTls(true) .tlsTrustCertsFilePath("/path/to/cacert.pem") .authentication(athenzAuth) .build();
支持的格式
privateKey
参数支持如下三种格式:
file:///path/to/file
file:/path/to/file
data:application/x-pem-file;base64,
以下示例展示了如何使用 Oauth2 作为 Pulsar Java 客户端的身份验证提供程序。
可以使用工厂方法为Pulsar Java客户端配置身份验证。
PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://broker.example.com:6650/") .authentication( AuthenticationFactoryOAuth2.clientCredentials(this.issuerUrl, this.credentialsUrl, this.audience)) .build();
此外,你也可以在 Pulsar 客户端中使用编码参数来配置身份认证。
Authentication auth = AuthenticationFactory .create(AuthenticationOAuth2.class.getName(), "{"type":"client_credentials","privateKey":"...","issuerUrl":"...","audience":"..."}"); PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://broker.example.com:6650/") .authentication(auth) .build();