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é)議來進行通訊,類型列表如下
- 集群模式:pulsar://localhost:6650
- Brokers :pulsar://localhost:6550,localhost:6651,localhost:6652
- 生產(chǎn)集群:pulsar://pulsar.us-west.example.com:6650
- TLS 認證:pulsar+ssl://pulsar.us-west.example.com:6651
如何創(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的列表:
- 通過基礎的正則表達式(regex),例如 persistent://public/default/finance-.*
- 通過明確定義的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();