SpringBoot集成Pulsar开发步骤

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

多主题订阅默认是,消费者的 subscriptionTopicsModePersistentOnlysubscriptionTopicsMode 的可用选项有 PersistentOnlyNonPersistentOnlyAllTopics

        .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();
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值