Pulsar Java client

可以使用 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 。

1. Installation

最新版本的 Pulsar Java 客户端库可通过 Maven Central 获得。要使用最新版本,请将pulsar-client库添加到你的构建配置中。

如果使用 Maven,请将以下信息添加到pom.xml文件中


2.8.0

  org.apache.pulsar
  pulsar-client
  ${pulsar.version}

按对应的标签提示添加到指定位置

2. 连接URL

要使用客户端库连接到 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

3. Client

你可以仅使用目标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 以获取可配置参数的完整列表。

除了客户端级别的配置之外,你还可以应用特定于 生产者 和 消费者 的配置,如下文部分所述。

4. 生产者(Producer)

在Pulsar中,生产者写消息到主题中。 一旦你实例化一个 PulsarClient 客户端对象(在 如上 章节),你可以为一个特定的Pulsar 主题 创建一个 生产者 。

Producer producer = client.newProducer()
        .topic("my-topic")
        .create();
​
// You can then send messages to the broker and topic you specified:
producer.send("My message".getBytes());

默认情况下,生产者生成由字节数组组成的消息。您可以通过指定消息 架构 来生成不同的类型。

Producer stringProducer = 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;
});

4.1 配置生产者

如果你像上面的例子一样,只指定主题名来实例化一个Producer对象,那么使用producer的默认配置。

如果你创建了一个生产者,你可以使用 loadConf 配置。在 loadConf 中可以使用以下参数。

类型 配置项 说明 默认值
String topicName 主题名称 null
String producerName 生产者名称 null
long sendTimeoutMs 消息发送超时(ms)。如果消息在' sendTimeout '到期之前没有得到服务器的确认,则会发生错误。 30000
boolean blockIfQueueFull 如果它被设置为 true ,当传出消息队列已满时,生产者的 SendSendAsync 方法会阻塞,而不是失败和抛出错误。如果它被设置为 false ,当传出消息队列已满时,生产者的 SendSendAsync 方法失败,并发生 ProducerQueueIsFullError 异常。 MaxPendingMessages 参数决定传出消息队列的大小。 false
int maxPendingMessages 持有挂起消息的队列的最大大小。例如,等待从 broker 接收确认的消息。默认情况下,当队列满时,所有对 SendSendAsync 方法的调用都会失败,除非你将 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 。下面是一个例子。

Producer producer = client.newProducer()
    .topic("my-topic")
    .batchingMaxPublishDelay(10, TimeUnit.MILLISECONDS)
    .sendTimeout(10, TimeUnit.SECONDS)
    .blockIfQueueFull(true)
    .create();

4.2 消息路由(Message routing)

使用分区主题时,你可以在使用生产者发布消息时指定路由模式。有关使用 Java 客户端指定路由模式的更多信息,请参阅 分区主题 手册。

4.3 异步发送(Async send)

你可以使用 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 。

4.4 配置消息

除了值之外,你还可以在给定的消息上设置其他项:

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返回。

5. 消费者(Consumer)

在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();

5.1 配置消费者

如果你像上面的例子中那样只指定主题和订阅名来实例化一个 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();

5.2 异步接收(Async receive)

receive 方法同步接收消息(消费者进程被阻塞,直到有消息可用)。您还可以使用 异步接收 ,一旦有新消息可用,它会立即返回一个 [CompletableFuture](http://www.baeldung.com/java-completablefuture) 对象。

下面是一个例子。

CompletableFuture asyncMessage = consumer.receiveAsync();

异步接收操作返回包装在 [CompletableFuture](http://www.baeldung.com/java-completablefuture) 中的 Message 。

5.3 批量接收(Batch receive)

使用 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();

5.4 多主题订阅

消费者除了订阅单个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();

笔记

默认情况下,消费者的 subscriptionTopicsModePersistentOnlysubscriptionTopicsMode 的可用选项有 PersistentOnlyNonPersistentOnlyAllTopics

你还可以订阅明确的主题列表(如果愿意,可跨命名空间):

List topics = 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);
            });
}

5.5 订阅模式

Pulsar 有多种 订阅模式 来匹配不同的场景。一个主题可以有多个 不同订阅模式 的订阅。但是,一个subscription一次只能有一种订阅模式。

订阅与订阅名称相同,一次只能指定一种订阅方式。除非此订阅的所有现有消费者都处于离线状态,否则你无法更改订阅模式。

不同的订阅方式有不同的消息分发模式。本节介绍订阅模式的区别和使用方法。

为了更好地描述它们的区别,假设您有一个名为 my-topic 的主题,并且生产者已经发布了 10 条消息。

Producer producer = 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 订阅不同于 ExclusiveFailover 订阅模式。 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的消息按顺序分发给一个消费者。

6. Reader

使用 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 指向最新的消息。

6.1 配置reader

在创建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

6.2 Sticky key range reader

在粘性键范围读取器中,broker只会发送消息键的哈希值包含在指定键哈希范围内的消息。可以在Reader上指定多个键哈希范围。

以下是创建粘性键范围读取器的示例。

pulsarClient.newReader()
        .topic(topic)
        .startMessageId(MessageId.earliest)
        .keyHashRange(Range.of(0, 10000), Range.of(20001, 30000))
        .create();

总的哈希范围大小是65536,所以该范围的最大端点应该小于或等于65535。

7. Schema

在 Pulsar 中,所有消息数据都由“幕后”字节数组组成。 Message schemas 使你能够在构造和处理消息时使用其他类型的数据(从简单的类型,如字符串到更复杂的、特定于应用程序的类型)。如果你在不指定schema的情况下构建 生产者 ,那么生产者只能生成类型为 byte[] 的消息。下面是一个例子。

Producer producer = client.newProducer()
        .topic(topic)
        .create();

上面的生产者相当于一个 Producer(实际上,您应该始终明确指定类型)。如果你想对不同类型的数据使用生产者,你需要指定一个schema来通知 Pulsar 将通过 主题 传输哪种数据类型。

7.1 Schema示例

假设您有一个 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 ):

Producer producer = client.newProducer(JSONSchema.of(SensorReading.class))
        .topic("sensor-readings")
        .create();

以下schema格式目前适用于Java:

  • 没有schema或字节数组schema(可以使用 Schema.BYTES 应用):

Producer bytesProducer = client.newProducer(Schema.BYTES)
      .topic("some-raw-bytes-topic")
      .create();

或者,相当于:

Producer bytesProducer = client.newProducer()
      .topic("some-raw-bytes-topic")
      .create();
  • 普通utf-8编码字符串数据 的 String 。使用 Schema.STRING 应用于schema:

Producer stringProducer = client.newProducer(Schema.STRING)
      .topic("some-string-topic")
      .create();
  • 使用 Schema.JSON 为pojo创建JSON schemas。下面是一个例子。

Producer pojoProducer = client.newProducer(Schema.JSON(MyPojo.class))
      .topic("some-pojo-topic")
      .create();
  • 使用 Schema.PROTOBUF 生成Protobuf schemas。下面的例子展示了如何创建Protobuf schema,并使用它实例化一个新的生产者:

Producer protobufProducer = client.newProducer(Schema.PROTOBUF(MyProtobuf.class))
      .topic("some-protobuf-topic")
      .create();
  • 使用 Schema.AVRO 定义Avro schemas。下面的代码片段演示了如何创建和使用Avro schema。

Producer avroProducer = client.newProducer(Schema.AVRO(MyAvro.class))
      .topic("some-avro-topic")
      .create();

8. 认证(Authentication)

Pulsar 目前支持三种认证方案: TLS 、 Athenz 和 Oauth2 。您可以将 Pulsar Java 客户端与所有这些一起使用。

8.1 TLS认证

要使用 TLS ,你需要使用 setUseTls 方法设置TLS为 true ,将你的Pulsar客户端指向TLS证书路径,并提供证书和密钥文件的路径。

下面是一个例子。

Map authParams = 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();

8.2 Athenz

要使用 Athenz 做为身份认证提供者,你需要使用 TLS 并且在hash中提供如下四个参数的值:

  • tenantDomain

  • tenantService

  • providerDomain

  • privateKey

你还可以设置一个可选的 keyId 。下面是一个例子。

Map authParams = 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,

8.3 Oauth2

以下示例展示了如何使用 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();

你可能感兴趣的:(消息中间件,#,Pulsar,消息队列,java)