Pulsar 2.5.0 之Java client

Pulsar 2.5.0 之Java client

官網(wǎng)原文標題《Pulsar Java client》

翻譯時間:2020-02-14

官網(wǎng)原文地址:http://pulsar.apache.org/docs/en/client-libraries-java/

譯者:本文介紹如何使用javaClient創(chuàng)建生產(chǎn)者、消費者以及通過管理后臺接口讀取消息。


Pulsar Java client

通過Java client 可以創(chuàng)建生產(chǎn)者、消費者以及讀取消息,當前API版本為2.5.0,包括兩大塊內(nèi)容

描述 Maven Artifact
org.apache.pulsar.client.api 創(chuàng)建生產(chǎn)和創(chuàng)建消息者 API org.apache.pulsar:pulsar-client:2.5.0
org.apache.pulsar.client.admin admin API org.apache.pulsar:pulsar-client-admin:2.5.0

本章重點是創(chuàng)建生產(chǎn)和創(chuàng)建消息者 API如何使用,關于 admin client API 仔細閱讀文檔 Pulsar admin interface

Java Client 導入包方式

Maven

在pom.xml 文件添加如下

<!-- in your <properties> block -->
<pulsar.version>2.5.0</pulsar.version>

<!-- in your <dependencies> block -->
<dependency>
  <groupId>org.apache.pulsar</groupId>
  <artifactId>pulsar-client</artifactId>
  <version>${pulsar.version}</version>
</dependency>

Gradle

build.gradle 文件添加如下信息

def pulsarVersion = '2.5.0'

dependencies {

compile group: 'org.apache.pulsar', name: 'pulsar-client', version: pulsarVersion

}

Java Client URLS

客戶端client 通過pulsar協(xié)議來進行通訊,類型列表如下

如何創(chuàng)建 PulsarClient 對象

默認示例:

PulsarClient client = PulsarClient.builder()

    .serviceUrl("pulsar://localhost:6650")

    .build();

多brokers示例

PulsarClient client = PulsarClient.builder()

    .serviceUrl("pulsar://localhost:6650,localhost:6651,localhost:6652")

   .build();

本地集群模式 默認 broker URL pulsar://localhost:6650

通過loadConf 可以進行自定義 PulsarClient 參數(shù)

參數(shù)配置

參數(shù)類型 參數(shù)名稱 描述 默認值
String serviceUrl
String authPluginClassName
String authParams
long operationTimeoutMs 30000
long statsIntervalSeconds 60
int numIoThreads 1
int numListenerThreads 1
boolean useTcpNoDelay true
boolean useTls false
string tlsTrustCertsFilePath
boolean tlsAllowInsecureConnection false
boolean tlsHostnameVerificationEnable false
int concurrentLookupRequest 5000
int maxLookupRequest 50000
int maxNumberOfRejectedRequestPerConnection 50
int keepAliveIntervalSeconds 30
int connectionTimeoutMs 10000
int requestTimeoutMs 60000
int defaultBackoffIntervalNanos TimeUnit.MILLISECONDS.toNanos(100);
long maxBackoffIntervalNanos TimeUnit.SECONDS.toNanos(30)

Producer 對象創(chuàng)建與使用

發(fā)送消息,需要創(chuàng)建Producer對象對指定的topic發(fā)送消息

PulsarClient client = PulsarClient.builder()
    .serviceUrl("pulsar://localhost:6650")
    .build();
Producer<byte[]> producer = client.newProducer()
    .topic("my-topic")
   .create();
*// You can then send messages to the broker and topic you specified:*
producer.send("My message".getBytes());

默認情況下 messages schema. 是字節(jié)數(shù)組,schema是可以根據(jù)自己的業(yè)務場景進行選擇的。例如我們可以使用string 類型schema。

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

Producer 對象可以使用默認配置,也可以自定義配置參數(shù),自定義配置是通過loadConf 來進行配置。

參數(shù)類型 參數(shù)名稱 描述 默認值
String topicName null
String producerName null
long sendTimeoutMs 30000
boolean blockIfQueueFull false
int maxPendingMessages 1000
int maxPendingMessagesAcrossPartitions 50000
MessageRoutingMode messageRoutingMode pulsar.RoundRobinDistribution
HashingScheme hashingScheme HashingScheme.JavaStringHash
ProducerCryptoFailureAction cryptoFailureAction ProducerCryptoFailureAction.FAIL
long batchingMaxPublishDelayMicros TimeUnit.MILLISECONDS.toMicros(1)
int batchingMaxMessages 1000
boolean batchingEnabled true
CompressionType compressionType No compression

Producer自定義配置示例:

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

如果Producer 創(chuàng)建了分片topic,發(fā)送消息時需要消息路由模式發(fā)送,消息路由了解更多,請閱讀 Partitioned Topics cookbook.

異步發(fā)送消息,返回MessageId包裝對象

producer.sendAsync("my-async-message".getBytes()).thenAccept(msgId -> {
  System.out.printf("Message with ID %s successfully sent", msgId);
});

Message 對象自定義配置示例:

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()方法,這個方法有返回值


Consumer

通過PulsarClient 對象來創(chuàng)建Consumer對象,Consumer對象在創(chuàng)建的時候需要指定主題與訂閱名稱。對象創(chuàng)建完成,就可以來對訂閱的主題進行消費

示例:

Consumer consumer = client.newConsumer()
    .topic("my-topic")
   .subscriptionName("my-subscription")
    .subscribe();

通過while 循環(huán)來監(jiān)聽與接收內(nèi)容,如果接收失敗,可以通過negativeAcknowledge方法,重新投遞消息

while (true) {
 // Wait for a message
 Message msg = consumer.receive();
 try {
   // Do something with the message
   System.out.printf("Message received: %s", 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);
 }
}

可以使用默認配置,也可以通過loadConf 進行自定義配置

參數(shù)類型 參數(shù)名稱 描述 默認值
Set<String> topicNames Sets.newTreeSet()
Pattern topicsPattern None
String subscriptionName None
SubscriptionType subscriptionType SubscriptionType.Exclusive
int receiverQueueSize 1000
long acknowledgementsGroupTimeMicros TimeUnit.MILLISECONDS.toMicros(100)
long negativeAckRedeliveryDelayMicros TimeUnit.MINUTES.toMicros(1)
int maxTotalReceiverQueueSizeAcrossPartitions 50000
String consumerName null
long ackTimeoutMillis 0
long tickDurationMillis 1000
int priorityLevel 0
ConsumerCryptoFailureAction cryptoFailureAction ConsumerCryptoFailureAction.FAIL
SortedMap<String, String> properties new TreeMap<>()
boolean readCompacted false
SubscriptionInitialPosition subscriptionInitialPosition SubscriptionInitialPosition.Latest
int patternAutoDiscoveryPeriod 1
RegexSubscriptionMode regexSubscriptionMode RegexSubscriptionMode.PersistentOnly
DeadLetterPolicy deadLetterPolicy None
boolean autoUpdatePartitions true
boolean replicateSubscriptionState false

示例

Consumer consumer = client.newConsumer()
    .topic("my-topic")
    .subscriptionName("my-subscription")
    .ackTimeout(10, TimeUnit.SECONDS)
    .subscriptionType(SubscriptionType.Exclusive)
    .subscribe();

Consumer接收方式

  • 異步接收

    CompletableFuture<Message> asyncMessage = consumer.receiveAsync();

  • 批量接收

    Messages messages = consumer.batchReceive();
    for (message in messages) {
     // do something
    }
    consumer.acknowledge(messages)
    

自定義批量接收策略

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

多主題訂閱

當consumer訂閱pulsar的主題,默認情況下,它訂閱了一個指定的主題,例如:persistent://public/default/my-topic。從Pulsar的1.23.0-incubating的版本,Pulsar消費者可以同時訂閱多個topic。你可以用以下兩種方式定義topic的列表:

通過正則訂閱多主題時,所有的主題必須在同一個命名空間(namespace)

當訂閱多主題時,pulsar客戶端會自動調用Pulsar的API來發(fā)現(xiàn)匹配表達式的所有topic,然后全部訂閱。如果此時有暫不存在的topic,那么一旦這些topic被創(chuàng)建,conusmer會自動訂閱。

不能保證順序性

當消費者訂閱多主題時,pulsar所提供對單一主題訂閱的順序保證,就hold不住了。如果你在使用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("persistent://public/default/.*");
Consumer allTopicsConsumer = consumerBuilder
   .topicsPattern(allTopicsInNamespace)
  .subscribe();
// Subscribe to a subsets of topics in a namespace, based on regex
Pattern someTopicsInNamespace = Pattern.compile("persistent://public/default/foo.*");
Consumer allTopicsConsumer = consumerBuilder
   .topicsPattern(someTopicsInNamespace)
   .subscribe();

列表訂閱

List<String> topics = Arrays.asList(
    "topic-1",
   "topic-2",
    "topic-3"
);
Consumer multiTopicConsumer = consumerBuilder
   .topics(topics)
   .subscribe();
*// Alternatively:*
Consumer multiTopicConsumer = consumerBuilder
  .topics(
     "topic-1",
     "topic-2",
     "topic-3"
   )
  .subscribe();

異步訂閱

Pattern allTopicsInNamespace = Pattern.compile("persistent://public/default.*");
consumerBuilder
  .topics(topics)
   .subscribeAsync()
   .thenAccept(this::receiveMessageFromConsumer);
private void receiveMessageFromConsumer(Consumer consumer) {
  consumer.receiveAsync().thenAccept(message -> {
     // Do something with the received message
       receiveMessageFromConsumer(consumer);
});
}

訂閱模式

Pulsar 提供了多樣化的訂閱模式來滿足實際中的業(yè)務場景。

為了能更好的理解訂閱多樣化的模式,我們通過創(chuàng)建一個生產(chǎn)者,往指定主題名稱為my-topic的主題發(fā)送10條消息,來進行分析對比。

發(fā)送示例:

Producer<String> 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();

Exclusive訂閱模式

獨占模式,只能有一個消費者綁定到訂閱(subscription)上。如果多于一個消費者嘗試以同樣方式去訂閱主題,消費者將會收到錯誤。

Consumer consumer = client.newConsumer()
    .topic("my-topic")
    .subscriptionName("my-subscription")
    .subscriptionType(SubscriptionType.Exclusive)
    .subscribe()

Failover訂閱模式

災備模式中,多個consumer可以綁定到同一個subscription。consumer將會按字典順序排序,第一個consumer被初始化為唯一接受消息的消費者。這個consumer被稱為master consumer。當master consumer斷開時,所有的消息(未被確認和后續(xù)進入的)將會被分發(fā)給隊列中的下一個consumer。

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.

Shared 訂閱模式

shared或者round robin模式中,多個消費者可以綁定到同一個訂閱上。消息通過round robin輪詢機制分發(fā)給不同的消費者,并且每個消息僅會被分發(fā)給一個消費者。當消費者斷開連接,所有被發(fā)送給他,但沒有被確認的消息將被重新安排,分發(fā)給其它存活的消費者。

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.

運行結果

消費者1

("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")

消費者2

("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")

Key_Shared訂閱模式

2.4.0 版本之后新擴展的訂閱模式

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.

運行結果

消費者1

("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")
 

消費者2

("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")
 

Reader

使用Pulsar的 讀取器接口, 應用程序可以手動管理游標。 當使用讀取器連接到一個主題而非消費者時,在讀取器連接到主題的時候就需要指定讀取器從哪個位置開始讀消息。當連接到某個主題時, 讀取器從以下位置開始讀消息:

  • ? 主題中最早的可用消息
  • ? 主題中最新可用消息
  • ? 指定的消息ID

示例:

ReaderConfiguration conf = new ReaderConfiguration();
     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*
      }

 

Reader 配置

Reader 配置

參數(shù)類型 參數(shù)名稱 描述 默認值
String topicName None
int receiverQueueSize 1000
ReaderListener<T> readerListener None
String readerName null
String subscriptionRolePrefix null
CryptoKeyReader cryptoKeyReader null
ConsumerCryptoFailureAction cryptoFailureAction ConsumerCryptoFailureAction.FAIL
boolean readCompacted false
boolean resetIncludeHead false

reader范圍讀取策略

范圍值區(qū)間0~65535,最大值不能超過65535

示例

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

Schema

Pulsar應用中,如果開發(fā)者沒有為topic指定schema,producer和consumer將會處理原始字節(jié)。但實際情況我們的期望不是這樣的,我們期望能按自己的數(shù)據(jù)格式進行發(fā)送,我們需要支持不同的數(shù)據(jù)類型。 Message schemas 就能很好的支持這種業(yè)務場景。

Schema示例

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<SensorReading> producer = client.newProducer(JSONSchema.of(SensorReading.class))
    .topic("sensor-readings")
    .create();

目前java client支持shcema 如下

  • Schema.BYTES
Producer<byte[]> bytesProducer = client.newProducer(Schema.BYTES)

   .topic("some-raw-bytes-topic")

   .create();

//or

Producer<byte[]> bytesProducer = client.newProducer()

   .topic("some-raw-bytes-topic")

   .create();
  • Schema.STRING
Producer<String> stringProducer = client.newProducer(Schema.STRING)
   .topic("some-string-topic")
   .create();

 
  • Schema.JSON
Producer<MyPojo> pojoProducer = client.newProducer(Schema.JSON(MyPojo.class))

   .topic("some-pojo-topic")

   .create();
  • Schema.PROTOBUF
Producer<MyProtobuf> protobufProducer = client.newProducer(Schema.PROTOBUF(MyProtobuf.class))

   .topic("some-protobuf-topic")

   .create(); 
  • Schema.AVRO
Producer<MyAvro> avroProducer = client.newProducer(Schema.AVRO(MyAvro.class))

   .topic("some-avro-topic")

   .create();

身份驗證與認證

Pulsar支持兩種方式

  • TLS
  • Athenz

TLS 身份認證使用需要將setUseTls設置為true ,同時需要設置TLS cert 路徑

示例

Map<String, String> 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();

Athenz 需要設置TLS,同時需要初始化如下參數(shù)

  • tenantDomain
  • tenantService
  • providerDomain
  • privateKey

privateKey支持三種格式

  • file:///path/to/file
  • file:/path/to/file
  • data:application/x-pem-file;base64,<base64-encoded value>

示例

Map<String, String> 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();
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。