1. 引入依赖包
<!-- in your <properties> block --> <pulsar.version>2.8.0</pulsar.version> <!-- in your <dependencies> block --> <dependency> <groupId>org.apache.pulsar</groupId> <artifactId>pulsar-client</artifactId> <version>${pulsar.version}</version> </dependency>
2. 配置文件配置项
#配置pulsar集群的url,默认端口号6650 #如果有多个brokers,url可写成 pulsar://IP:6550,IP:6651,IP:6652 样式 #多台服务器上部署集群,url也可以这样写 pulsar://IP1:6650,IP2:6650,IP3:6650 #如果使用 TLS 认证,则 URL头 pulsar+ssl:// 样式 pulsar.url=pulsar://192.168.1.171:6650 #主题名(主题不存在则会自动创建,可写单个或多个) pulsar.topic=topic1,topic2 #订阅名 pulsar.subscription=topicGroup
然后在类中注入属性
@Value("${pulsar.url}") private String url; @Value("${pulsar.topic}") private String topic; @Value("${pulsar.subscription}") private String subscription;
3. 构造pulsar生产者的方式
3.1 首先通过Pulsar集群的url实例化一个PulsarClient对象
PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://192.168.1.171:6650") .build();
如果有多个 broker,可以这样创建一个 PulsarClient:
PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar://192.168.1.171:6650,192.168.1.178:6650,192.168.1.231:6650") .build();
构造PulsarClient的其他常用参数:
//构造Pulsar client client = PulsarClient.builder() .serviceUrl(url) .operationTimeout(30000, TimeUnit.MILLISECONDS) //操作超时时长,默认30000ms .ioThreads(1) //用于处理到brokers的连接的线程数 .listenerThreads(1) //用于处理消息监听器的线程数。如果您希望多个线程处理单个主题,则需要创建一个shared订阅,并为此订阅创建多个消费者。这并不能确保排序。 .maxConcurrentLookupRequests(5000) //允许在每个broker连接上发送的并发查找请求数,以防止broker过载 .maxLookupRequests(50000) //每个broker连接上允许的最大查找请求数,以防止broker过载 .keepAliveInterval(30, TimeUnit.SECONDS) //每个客户端broker连接的保持活跃间隔的秒数,默认30s .connectionTimeout(10000, TimeUnit.MILLISECONDS) //等待建立连接到broker的持续时间,如果持续时间过了,没有从broker得到响应,连接尝试将被放弃 .build();
3.2 再通过PulsarClient对象创建生产者Producer
Producer<byte[]> producer = client.newProducer() .topic("my-topic") .create();
默认情况下,生产者生成由字节数组组成的消息。你也可以自己指定消息Schema。例:
Producer<String> stringProducer = client.newProducer(Schema.STRING) .topic("my-topic") .create(); stringProducer.send("My message");
创建生产者的其他常用参数:
//创建producer Producer<byte[]> producer = client.newProducer() .topic(topicName)//此处为单个topic的名称,多个topic可采用类似 topic.split(",")[0] 写法 .enableBatching(true)//是否开启批量处理消息,默认true,需要注意的是enableBatching只在异步发送sendAsync生效,同步发送send失效。因此建议生产环境若想使用批处理,则需使用异步发送,或者多线程同步发送 .compressionType(CompressionType.LZ4)//消息压缩(四种压缩方式:LZ4,ZLIB,ZSTD,SNAPPY),consumer端不用做改动就能消费,开启后大约可以降低3/4带宽消耗和存储(官方测试) .batchingMaxPublishDelay(10, TimeUnit.MILLISECONDS) //设置将对发送的消息进行批处理的时间段,10ms;可以理解为若该时间段内批处理成功,则一个batch中的消息数量不会被该参数所影响。 .sendTimeout(0, TimeUnit.SECONDS)//设置发送超时0s;如果在sendTimeout过期之前服务器没有确认消息,则会发生错误。默认30s,设置为0代表无限制,建议配置为0 .batchingMaxMessages(1000)//批处理中允许的最大消息数。默认1000 .maxPendingMessages(1000)//设置等待接受来自broker确认消息的队列的最大大小,默认1000 .blockIfQueueFull(true)//设置当消息队列中等待的消息已满时,Producer.send 和 Producer.sendAsync 是否应该block阻塞。默认为false,达到maxPendingMessages后send操作会报错,设置为true后,send操作阻塞但是不报错。建议设置为true .roundRobinRouterBatchingPartitionSwitchFrequency(10)//向不同partition分发消息的切换频率,默认10ms,可根据batch情况灵活调整 .batcherBuilder(BatcherBuilder.DEFAULT)//key_Shared模式要用KEY_BASED,才能保证同一个key的message在一个batch里 .create();
3.3 发送消息
同步发送消息:
producer.send(msg.getBytes());
异步发送消息:
producer.sendAsync("my-async-message".getBytes()).thenAccept(msgId -> { System.out.println("Message with ID " + msgId + " successfully sent"); });
异步发送操作返回一个包装在CompletableFuture中的 MessageId。异步发送也可以按下面这种方式:
CompletableFuture<MessageId> future = producer.sendAsync(data.getBytes()); //异步发送 future.handle((v, ex) -> { if (ex == null) { logger.debug("发送Pulsar消息成功: {}", data); } else { logger.error("发送Pulsar消息失败msg:【{}】 ", data, ex); } return null; });
除了值之外,你还可以在给定的消息上设置其他项:
producer.newMessage() .key("my-message-key") .value("my-async-message".getBytes()) .property("my-key", "my-value") .property("my-other-key", "my-other-value") .send();
4. 构造pulsar消费者的方式
4.1 首先通过Pulsar集群的url实例化一个PulsarClient对象
此步骤和3.1一样,不再赘述。
4.2 再通过PulsarClient对象创建消费者Consumer
PulsarClient通过指定主题和订阅名称就能创建一个消费者
Consumer consumer = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscribe();
创建消费者的其他常用参数:
//创建consumer Consumer consumer = client.newConsumer() .topic(topicName) //主题名称 .subscriptionName(subscriptionName) //订阅名称 .subscriptionType(SubscriptionType.Exclusive)//指定消费模式,包含:Exclusive,Failover,Shared,Key_Shared。默认Exclusive模式 .ackTimeout(10, TimeUnit.MILLISECONDS) //未确认消息的超时时间,默认值 0 .subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)//指定从哪里开始消费还有Latest,valueof可选,默认Latest .negativeAckRedeliveryDelay(60, TimeUnit.SECONDS)//指定消费失败后延迟多久broker重新发送消息给consumer,默认60s .subscribe();
4.3 接收消息并确认
4.3.1 同步接收
采用主线程阻塞方式
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();
4.3.2 异步接收
CompletableFuture<Message> asyncMessage = consumer.receiveAsync(); asyncMessage.handle((v, ex) -> { if (ex == null){ System.out.println(v.getData()); }else { logger.error(ex); } return null; });
一旦有新消息可用,它会立即返回一个 CompletableFuture 对象。
异步接收操作返回包装在 CompletableFuture 中的 Message 。
4.3.3 批量接收
Messages messages = consumer.batchReceive(); for (Object message : 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();
至此,完成了从生产者创建发送消息,到消费者消费消息的全过程。
4.4 订阅方式说明
① 多主题订阅
通过namaspace或者正则表达式来实现多个主题订阅,也可直接指定主题列表(可跨命名空间)
// Subscribe to all topics in a namespace Pattern allTopicsInNamespace = Pattern.compile("public/default/.*"); Consumer allTopicsConsumer = pulsarClient.newConsumer() .subscriptionName(subscription) .topicsPattern(allTopicsInNamespace) .subscribe(); // Subscribe to a subsets of topics in a namespace, based on regex Pattern someTopicsInNamespace = Pattern.compile("public/default/foo.*"); Consumer allTopicsConsumer = pulsarClient.newConsumer() .subscriptionName(subscription) .topicsPattern(someTopicsInNamespace) .subscribe();
多主题订阅默认是,消费者的 subscriptionTopicsMode
是 PersistentOnly
。 subscriptionTopicsMode
的可用选项有 PersistentOnly
, NonPersistentOnly
和 AllTopics
。
.subscriptionTopicsMode(RegexSubscriptionMode.AllTopics) //控制订阅的主题类型(持久化/非持久化/All)
还可以异步订阅多个主题
Consumer multiTopicConsumer = consumerBuilder .topic( "topic-1", "topic-2", "topic-3" ) //直接指定主题列表 .subscribe(); 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); }); }
② 4种订阅模式
一个主题可以有多个 不同订阅模式 的订阅。但是,一个subscription一次只能有一种订阅模式。除非此subscription的所有现有消费者都处于离线状态,否则你无法更改订阅模式。
独占
Consumer consumer = client.newConsumer() .topic("my-topic") .subscriptionName("my-subscription") .subscriptionType(SubscriptionType.Exclusive) .subscribe()
若是分区topic,第一个消费者消费所有分区topic。消费顺序与生产顺序相同。
灾备
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.
多个消费者可以附加到同一个订阅,但只有第一个消费者是活跃的,其他消费者是备用的。
如果一个主题是分区主题,则每个分区只有一个活跃消费者,一个分区的消息只分发给一个消费者,多个分区的消息分发给多个消费者。
共享
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.
在共享订阅模式中,多个消费者可以附加到相同的订阅,并且消息在消费者之间以轮询分发的方式传递。
Shared 订阅有更好的灵活性,但不能提供顺序保证。
Key_shared(Pulsar2.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.
具有相同key的消息按顺序仅传递给一个消费者。
事先不知道哪些key会被分配给哪个消费者,但一个key只会在同一时间被分配给一个消费者。
如果未指定消息的key,则默认情况下将不带key的消息按顺序分发给一个消费者。
Key_shared与批处理共用的注意点
如果在生产者端启用批处理,默认情况下,具有不同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();