原文链接

1、使用前准备

引入依赖:

<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client</artifactId>
<version>2.6.1</version>
</dependency>

2、PulsarClient

在尝试使用Producer和Consumer前,我们先讲一下Pulsar客户端,因为不管是Producer还是Consumer,都是依靠PulsarClient来创建的:

/**
* Pulsar工具类
* @author winfun
**/
public class PulsarUtils { /**
* 根据serviceUrl创建PulsarClient
* @param serviceUrl 服务地址
* @return 客户端
* @throws PulsarClientException 异常
*/
public static PulsarClient createPulsarClient(String serviceUrl) throws PulsarClientException {
return PulsarClient.builder()
.serviceUrl(serviceUrl)
.build();
}
}

我们这里简单使用,只借用ServiceUrl创建客户端,其实还有很多比较重要的参数,下面稍微列举一下:

  • ioThreads:Set the number of threads to be used for handling connections to brokers (default: 1 thread)
  • listenerThreads:Set the number of threads to be used for message listeners (default: 1 thread). 一条线程默认只为一个消费者服务
  • enableTcpNoDelay:No-delay features make sure packets are sent out on the network as soon as possible
  • ....

3、Producer

Producer这里我们也先简单使用,只负责往指定Topic发送消息,其他功能不用,例如异步发送、延时发送等

/**
* 初次使用Pulsar生产者,无任何封装
* @author winfun
**/
public class FirstProducerDemo { public static void main(String[] args) throws PulsarClientException {
PulsarClient client = PulsarClient.builder()
.serviceUrl("pulsar://127.0.0.1:6650")
.build(); ProducerBuilder<String> productBuilder = client.newProducer(Schema.STRING).topic("winfun/study/test-topic")
.blockIfQueueFull(Boolean.TRUE).batchingMaxMessages(100).enableBatching(Boolean.TRUE).sendTimeout(3, TimeUnit.SECONDS); Producer<String> producer = productBuilder.create();
for (int i = 0; i < 100; i++) {
producer.send("hello"+i);;
}
producer.close();
}
}

4、Consumer

下面我们将比较详细地介绍消费者的使用方式,因为这里能拓展的东西稍微多一点,下面开始使用旅程。

4.1 第一次使用:

我们利用PulsarClient创建Consumer;接着在死循环中利用Consumer#receive方法接收消息然后进行消费。

/**
* 初次使用Pulsar消费者,无任何封装
* @author winfun
**/
@Slf4j
public class FirstConsumerDemo { public static void main(String[] args) throws PulsarClientException {
PulsarClient client = PulsarClient.builder()
.serviceUrl("pulsar://127.0.0.1:6650")
.build(); /**
* The subscribe method will auto subscribe the consumer to the specified topic and subscription.
* One way to make the consumer listen on the topic is to set up a while loop.
* In this example loop, the consumer listens for messages, prints the contents of any received message, and then acknowledges that the message has been processed.
* If the processing logic fails, you can use negative acknowledgement to redeliver the message later.
*/
Consumer<String> consumer = client.newConsumer(Schema.STRING)
.topic("winfun/study/test-topic")
.subscriptionName("my-subscription")
.ackTimeout(10, TimeUnit.SECONDS)
.subscriptionType(SubscriptionType.Exclusive)
.subscribe();
// 死循环接收
while (true){
Message<String> message = consumer.receive();
String msgContent = message.getValue();
log.info("接收到消息: {}",msgContent);
consumer.acknowledge(message);
}
}
}

4.2 第二次使用:

上面我们可以看到,我们是利用死循环来保证及时消费,但是这样会导致主线程;所以下面我们可以使用Pulsar提供的MessageListener,即监听器,当消息来了,会回调监听器指定的方法,从而避免阻塞主线程。

/**
* 使用MessageListener,避免死循环代码&阻塞主线程
* @author winfun
**/
@Slf4j
public class SecondConsumerDemo { public static void main(String[] args) throws PulsarClientException {
PulsarClient client = PulsarUtils.createPulsarClient("pulsar://127.0.0.1:6650");
/**
* If you don't want to block your main thread and rather listen constantly for new messages, consider using a MessageListener.
*
*/
Consumer<String> consumer = client.newConsumer(Schema.STRING)
.topic("winfun/study/test-topic")
.subscriptionName("my-subscription")
.ackTimeout(10, TimeUnit.SECONDS)
.subscriptionType(SubscriptionType.Exclusive)
.messageListener((MessageListener<String>) (consumer1, msg) -> { /**
* 当接收到一个新的消息,就会回调 MessageListener的receive方法。
* 消息将会保证按顺序投放到单个消费者的同一个线程,因此可以保证顺序消费
* 除非应用程序或broker崩溃,否则只会为每条消息调用此方法一次
* 应用程序负责调用消费者的确认方法来确认消息已经被消费
* 应用程序负责处理消费消息时可能出现的异常
*/
log.info("接收到消息:{}",msg.getValue());
try {
consumer1.acknowledge(msg);
} catch (PulsarClientException e) {
e.printStackTrace();
}
}).subscribe();
}
}

4.3 第三次使用:

上面利用监听器来解决死循环代码和阻塞主线程问题;但是我们可以发现,每次消费都是单线程,当一个消息消费完才能进行下一个消息的消费,这样会导致消费效率非常的低;

如果如果追求高吞吐量,不在乎消息消费的顺序性,那么我们可以接入线程池;一有消息来就丢进线程池中,这样不但可以支持异步消费,还能保证消费的效率非常的高。

/**
* MessageListener 内使用线程池进行异步消费
* @author winfun
**/
@Slf4j
public class ThirdConsumerDemo { public static void main(String[] args) throws PulsarClientException { Executor executor = new ThreadPoolExecutor(
10,
10,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100)
);
PulsarClient client = PulsarUtils.createPulsarClient("pulsar://127.0.0.1:6650");
/**
* If you don't want to block your main thread and rather listen constantly for new messages, consider using a MessageListener.
*
*/
Consumer<String> consumer = client.newConsumer(Schema.STRING)
.topic("winfun/study/test-topic")
.subscriptionName("my-subscription")
.ackTimeout(10, TimeUnit.SECONDS)
.subscriptionType(SubscriptionType.Exclusive)
.messageListener((MessageListener<String>) (consumer1, msg) -> {
/**
* MessageListener还是保证了接收的顺序性
* 但是利用线程池进行异步消费后不能保证消费顺序性
*/
executor.execute(() -> handleMsg(consumer1, msg));
}).subscribe();
} /**
* 线程池异步处理
* @param consumer 消费者
* @param msg 消息
*/
public static void handleMsg(Consumer consumer, Message msg){ ThreadUtil.sleep(RandomUtil.randomInt(3),TimeUnit.SECONDS);
log.info("接收到消息:{}",msg.getValue());
try {
consumer.acknowledge(msg);
} catch (PulsarClientException e) {
e.printStackTrace();
}
}
}

4.4 第四次使用:

我们可以发现,在上面的三个例子中,如果在调用Consumer#acknowledge方法前,因为代码问题导致抛异常了,我们是没有做处理的,那么会导致消费者会一直重试没有被确认的消息。

那么我们此时需要接入Pulsar提供的死信队列:当Consumer消费消息时抛异常,并达到一定的重试次数,则将消息丢入死信队列;但需要注意的是,单独使用死信队列,Consumer的订阅类型需要是 Shared/Key_Shared;否则不会生效。

/**
* 超过最大重试次数,进入死信队列
* @author: winfun
**/
@Slf4j
public class FourthConsumerDemo { public static void main(String[] args) throws PulsarClientException {
/**
* 如果指定了死信队列策略,但是没指定死信队列
* 死信队列:String.format("%s-%s-DLQ", topic, this.subscription)
* 这里的this.subscription为上面指定的 subscriptionName。
*
* 一般在生产环境,会将pulsar的自动创建topic功能给关闭掉,所以上线前,记得先提工单创建指定的死信队列。
*
* 重点信息:
* 如果是单单使用死信队列,subscriptionType为 Shared/Key_Shared,否则死信队列不生效。
*/
PulsarClient client = PulsarUtils.createPulsarClient("pulsar://127.0.0.1:6650");
Consumer<String> consumer = client.newConsumer(Schema.STRING)
.topic("winfun/study/test-topic")
.subscriptionName("my-subscription")
.receiverQueueSize(100)
.ackTimeout(1, TimeUnit.SECONDS)
.subscriptionType(SubscriptionType.Key_Shared)
.negativeAckRedeliveryDelay(1,TimeUnit.SECONDS)
.deadLetterPolicy(DeadLetterPolicy.builder()
//可以指定最大重试次数,最大重试三次后,进入到死信队列
.maxRedeliverCount(3)
//可以指定死信队列
.deadLetterTopic("winfun/study/test-topic-dlq3")
.build())
.messageListener((MessageListener<String>) (consumer1, msg) -> {
log.info("接收到队列「{}」消息:{}",msg.getTopicName(),msg.getValue()); if (msg.getValue().equals("hello3")) {
throw new RuntimeException("hello3消息消费失败!");
}else {
try {
consumer1.acknowledge(msg);
} catch (PulsarClientException e) {
e.printStackTrace();
}
}
}).subscribe();
}
}

4.5 第五次使用:

死信队列一般是不做消费的,我们会关注死信队列的情况,从而作出下一步的动作。

而且,一般做消息重试,我们不希望在原Topic中做重试,这样会影响原有消息的消费进度。

那么我们可以同时使用重试队列和死信队列。

当代码抛出异常时,我们可以捕获住,然后调用Consumer#reconsumeLater方法,将消息丢入重试队列;当消息重试指定次数后还无法正常完成消费,即会将消息丢入死信队列。

/**
* 重试队列
* @author winfun
**/
@Slf4j
public class FifthConsumerDemo { public static void main(String[] args) throws PulsarClientException {
PulsarClient client = PulsarUtils.createPulsarClient("pulsar://127.0.0.1:6650");
/**
* 注意点:
* 1、使用死信策略,但是没有指定重试topic和死信topic名称
* 死信队列:String.format("%s-%s-DLQ", topic, this.subscription)
* 重试队列:String.format("%s-%s-RETRY", topic, this.subscription)
* 这里的this.subscription为上面指定的 subscriptionName。
*
* 2、是否限制订阅类型
* 同时开启重试队列和死信队列,不限制subscriptionType只能为Shared/Key_Shared;
* 如果只是单独使用死信队列,需要限制subscriptionType为Shared
*
* 3、重试原理
* 如果使用重试队列,需要保证 enableRetry 是开启的,否则调用 reconsumeLater 方法时会抛异常:org.apache.pulsar.client.api.PulsarClientException: reconsumeLater method not support!
* 如果配置了重试队列,consumer会同时监听原topic和重试topic,consumer的实现类对应是:MultiTopicsConsumerImpl
* 如果消费消息时调用了 reconsumeLater 方法,会将此消息丢进重试topic
* 如果在重试topic重试maxRedeliverCount次后都无法正确ack消息,即将消息丢到死信队列。
* 死信队列需要另起Consumer进行监听消费。
*
* 4、直接抛异常
* 如果我们不是业务层面上调用 reconsumeLater 方法来进行重试,而是代码层面抛异常了,如果subscriptionType不为Shared/Key_Shared,消息无法进入重试队列和死信队列,是当前消费者无限在原topic进行消费。
* 而如果如果subscriptionType为Shared/Key_Shared,则消息进行maxRedeliverCount次消费后,会直接进入到死信队列,此时不会用到重试队列。
* 因此,重试队列是仅仅针对 reconsumeLater 方法的,而不针对异常的重试。
*/
Consumer<String> consumer = client.newConsumer(Schema.STRING)
.topic("winfun/study/test-retry-topic")
.subscriptionName("my-subscription")
.receiverQueueSize(100)
.ackTimeout(1, TimeUnit.SECONDS)
.subscriptionType(SubscriptionType.Exclusive)
.negativeAckRedeliveryDelay(1,TimeUnit.SECONDS)
.enableRetry(true)
.deadLetterPolicy(DeadLetterPolicy.builder()
//可以指定最大重试次数,最大重试三次后,进入到死信队列
.maxRedeliverCount(3)
.retryLetterTopic("winfun/study/test-retry-topic-retry")
//可以指定死信队列
.deadLetterTopic("winfun/study/test-retry-topic-dlq")
.build())
.messageListener((MessageListener<String>) (consumer1, msg) -> {
log.info("接收到队列「{}」消息:{}",msg.getTopicName(),msg.getValue()); if (msg.getValue().equals("hello3")) {
try {
consumer1.reconsumeLater(msg,1,TimeUnit.SECONDS);
} catch (PulsarClientException e) {
e.printStackTrace();
}
//throw new RuntimeException("hello3消息消费失败!");
}else {
try {
consumer1.acknowledge(msg);
} catch (PulsarClientException e) {
e.printStackTrace();
}
}
}).subscribe();
}
}

重试机制源码分析

关于重试机制,其实是比较有意思的,下面我们会简单分析一下源码。

  1. 判断是否开启重试机制,如果没有开启重试机制,则直接抛异常
public void reconsumeLater(Message<?> message, long delayTime, TimeUnit unit) throws PulsarClientException {
// 如果没开启重试机制,直接抛异常
if (!this.conf.isRetryEnable()) {
throw new PulsarClientException("reconsumeLater method not support!");
} else {
try {
// 当然了,reconsumeLaterAsync里面也会判断是否开启重试机制
this.reconsumeLaterAsync(message, delayTime, unit).get();
} catch (Exception var7) {
Throwable t = var7.getCause();
if (t instanceof PulsarClientException) {
throw (PulsarClientException)t;
} else {
throw new PulsarClientException(t);
}
}
}
}

还有我们可以发现,pulsar很多方法是支持同步和异步的,而同步就是直接调用异步方法,再后调用get()方法进行同步阻塞等待即可。

  1. 调用 reconsumeLaterAsunc 方法,接着调用 get() 进行同步阻塞等待结果
public CompletableFuture<Void> reconsumeLaterAsync(Message<?> message, long delayTime, TimeUnit unit) {
if (!this.conf.isRetryEnable()) {
return FutureUtil.failedFuture(new PulsarClientException("reconsumeLater method not support!"));
} else {
try {
return this.doReconsumeLater(message, AckType.Individual, Collections.emptyMap(), delayTime, unit);
} catch (NullPointerException var6) {
return FutureUtil.failedFuture(new InvalidMessageException(var6.getMessage()));
}
}
}
  1. 调用 doReconsumeLater 方法

我们知道,在 Pulsar 的 Consumer 中,可以支持多 Topic 监听,而如果我们加入了重试机制,默认是同个 Consumer 同时监听原队列和重试队列,所以 Consumer 接口的实现此时为 MultiTopicsConsumerImpl,而不是 ComsumerImpl。

那我们看看 MultiConsumerImpl 的 doReconsumeLater 是如何进行重新消费的:

protected CompletableFuture<Void> doReconsumeLater(Message<?> message, AckType ackType, Map<String, Long> properties, long delayTime, TimeUnit unit) {
MessageId messageId = message.getMessageId();
Preconditions.checkArgument(messageId instanceof TopicMessageIdImpl);
TopicMessageIdImpl topicMessageId = (TopicMessageIdImpl)messageId;
if (this.getState() != State.Ready) {
return FutureUtil.failedFuture(new PulsarClientException("Consumer already closed"));
} else {
MessageId innerId;
if (ackType == AckType.Cumulative) {
Consumer individualConsumer = (Consumer)this.consumers.get(topicMessageId.getTopicPartitionName());
if (individualConsumer != null) {
innerId = topicMessageId.getInnerMessageId();
return individualConsumer.reconsumeLaterCumulativeAsync(message, delayTime, unit);
} else {
return FutureUtil.failedFuture(new NotConnectedException());
}
} else {
ConsumerImpl<T> consumer = (ConsumerImpl)this.consumers.get(topicMessageId.getTopicPartitionName());
innerId = topicMessageId.getInnerMessageId();
return consumer.doReconsumeLater(message, ackType, properties, delayTime, unit).thenRun(() -> {
this.unAckedMessageTracker.remove(topicMessageId);
});
}
}
}
  • 首先判断客户端是否为准备状态
  • 接着判断 AckType 是累计的还是单独的,如果是累计的话,subscriptionType 一定要是 exclusive
  • 不管是累计还是单独的,最后都是调用 ConsumerImpl 的 doReconsumerLater 方法
protected CompletableFuture<Void> doReconsumeLater(Message<?> message, AckType ackType, Map<String, Long> properties, long delayTime, TimeUnit unit) {
MessageId messageId = message.getMessageId();
if (messageId instanceof TopicMessageIdImpl) {
messageId = ((TopicMessageIdImpl)messageId).getInnerMessageId();
} Preconditions.checkArgument(messageId instanceof MessageIdImpl);
if (this.getState() != State.Ready && this.getState() != State.Connecting) {
this.stats.incrementNumAcksFailed();
PulsarClientException exception = new PulsarClientException("Consumer not ready. State: " + this.getState());
if (AckType.Individual.equals(ackType)) {
this.onAcknowledge(messageId, exception);
} else if (AckType.Cumulative.equals(ackType)) {
this.onAcknowledgeCumulative(messageId, exception);
} return FutureUtil.failedFuture(exception);
} else {
if (delayTime < 0L) {
delayTime = 0L;
}
// 如果 retryLetterProducer 为null,则尝试创建
if (this.retryLetterProducer == null) {
try {
this.createProducerLock.writeLock().lock();
if (this.retryLetterProducer == null) {
this.retryLetterProducer = this.client.newProducer(this.schema).topic(this.deadLetterPolicy.getRetryLetterTopic()).enableBatching(false).blockIfQueueFull(false).create();
}
} catch (Exception var28) {
log.error("Create retry letter producer exception with topic: {}", this.deadLetterPolicy.getRetryLetterTopic(), var28);
} finally {
this.createProducerLock.writeLock().unlock();
}
}
// 如果 retryLetterProcuder 不为空,则尝试将消息丢进重试队列中
if (this.retryLetterProducer != null) {
try {
MessageImpl<T> retryMessage = null;
String originMessageIdStr = null;
String originTopicNameStr = null;
if (message instanceof TopicMessageImpl) {
retryMessage = (MessageImpl)((TopicMessageImpl)message).getMessage();
originMessageIdStr = ((TopicMessageIdImpl)message.getMessageId()).getInnerMessageId().toString();
originTopicNameStr = ((TopicMessageIdImpl)message.getMessageId()).getTopicName();
} else if (message instanceof MessageImpl) {
retryMessage = (MessageImpl)message;
originMessageIdStr = ((MessageImpl)message).getMessageId().toString();
originTopicNameStr = ((MessageImpl)message).getTopicName();
} SortedMap<String, String> propertiesMap = new TreeMap();
int reconsumetimes = 1;
if (message.getProperties() != null) {
propertiesMap.putAll(message.getProperties());
} // 如果包含 RECONSUMETIMES,则最递增
if (propertiesMap.containsKey("RECONSUMETIMES")) {
reconsumetimes = Integer.valueOf((String)propertiesMap.get("RECONSUMETIMES"));
++reconsumetimes;
// 否则先加入「原始队列」和「原始messageId」信息
} else {
propertiesMap.put("REAL_TOPIC", originTopicNameStr);
propertiesMap.put("ORIGIN_MESSAGE_IDY_TIME", originMessageIdStr);
}
// 加入重试次数信息
propertiesMap.put("RECONSUMETIMES", String.valueOf(reconsumetimes));
// 加入延时时间信息
propertiesMap.put("DELAY_TIME", String.valueOf(unit.toMillis(delayTime)));
TypedMessageBuilder typedMessageBuilderNew;
// 判断是否超过最大重试次数,如果还未超过,则重新投放到重试队列
if (reconsumetimes <= this.deadLetterPolicy.getMaxRedeliverCount()) {
typedMessageBuilderNew = this.retryLetterProducer.newMessage().value(retryMessage.getValue()).properties(propertiesMap);
if (delayTime > 0L) {
typedMessageBuilderNew.deliverAfter(delayTime, unit);
} if (message.hasKey()) {
typedMessageBuilderNew.key(message.getKey());
}
// 发送延时消息
typedMessageBuilderNew.send();
// 确认当前消息
return this.doAcknowledge(messageId, ackType, properties, (TransactionImpl)null);
}
// 先忽略
this.processPossibleToDLQ((MessageIdImpl)messageId);
// 判断 deadLetterProducer 是否为null,如果为null,尝试创建
if (this.deadLetterProducer == null) {
try {
if (this.deadLetterProducer == null) {
this.createProducerLock.writeLock().lock();
this.deadLetterProducer = this.client.newProducer(this.schema).topic(this.deadLetterPolicy.getDeadLetterTopic()).blockIfQueueFull(false).create();
}
} catch (Exception var25) {
log.error("Create dead letter producer exception with topic: {}", this.deadLetterPolicy.getDeadLetterTopic(), var25);
} finally {
this.createProducerLock.writeLock().unlock();
}
}
// 如果 deadLetterProducer 不为null
if (this.deadLetterProducer != null) {
// 加入「原始队列」信息
propertiesMap.put("REAL_TOPIC", originTopicNameStr);
// 加入「原始MessageId」信息
propertiesMap.put("ORIGIN_MESSAGE_IDY_TIME", originMessageIdStr);
typedMessageBuilderNew = this.deadLetterProducer.newMessage().value(retryMessage.getValue()).properties(propertiesMap);
// 将消息内容发往死信队列中
typedMessageBuilderNew.send();
// 确认当前消息
return this.doAcknowledge(messageId, ackType, properties, (TransactionImpl)null);
}
} catch (Exception var27) {
log.error("Send to retry letter topic exception with topic: {}, messageId: {}", new Object[]{this.deadLetterProducer.getTopic(), messageId, var27});
Set<MessageId> messageIds = new HashSet();
messageIds.add(messageId);
this.unAckedMessageTracker.remove(messageId);
this.redeliverUnacknowledgedMessages(messageIds);
}
} return CompletableFuture.completedFuture((Object)null);
}
}

分析了一波,我们可以看到和上面代码的注释描述的基本一致。

4.6 第六次使用

上面我们提到,当Consumer指定了重试队列,Consumer会同时监听原Topic和重试Topic,那么如果我们想多个Consumer消费重试Topic时,需要将Consumer的订阅类型指定为 Shared/Key_Shared,让重试队列支持多Consumer监听消费,提升重试队列的消费效率。

/**
* 重试队列-Shared
* @author winfun
**/
@Slf4j
public class SixthConsumerDemo { public static void main(String[] args) throws PulsarClientException {
PulsarClient client = PulsarUtils.createPulsarClient("pulsar://127.0.0.1:6650");
/**
* 因为如果指定了重试策略,Consumer会同时监听「原队列」和「重试队列」
* 即如果我们想「重试队列」可以让多个 Consumer 监听,从而提高消费能力,那么 Consumer 需指定为 Shared 模式。
*/
Consumer<String> consumer = client.newConsumer(Schema.STRING)
.topic("winfun/study/test-retry-topic")
.subscriptionName("my-subscription")
.receiverQueueSize(100)
.ackTimeout(1, TimeUnit.SECONDS)
.subscriptionType(SubscriptionType.Shared)
.negativeAckRedeliveryDelay(1,TimeUnit.SECONDS)
.enableRetry(true)
.deadLetterPolicy(DeadLetterPolicy.builder()
//可以指定最大重试次数,最大重试三次后,进入到死信队列
.maxRedeliverCount(3)
.retryLetterTopic("winfun/study/test-retry-topic-retry")
//可以指定死信队列
.deadLetterTopic("winfun/study/test-retry-topic-dlq")
.build())
.messageListener((MessageListener<String>) (consumer1, msg) -> {
log.info("接收到队列「{}」消息:{}",msg.getTopicName(),msg.getValue()); if (msg.getValue().contains("1") || msg.getValue().contains("2") || msg.getValue().contains("3")) {
try {
consumer1.reconsumeLater(msg,1,TimeUnit.SECONDS);
} catch (PulsarClientException e) {
e.printStackTrace();
}
//throw new RuntimeException("hello3消息消费失败!");
}else {
try {
consumer1.acknowledge(msg);
} catch (PulsarClientException e) {
e.printStackTrace();
}
}
}).subscribe();
}
} /**
* 监听重试队列-Shared订阅模式
* @author winfun
**/
@Slf4j
public class RetryConsumerDemo { public static void main(String[] args) throws PulsarClientException { PulsarClient client = PulsarUtils.createPulsarClient("pulsar://127.0.0.1:6650");
Consumer<String> deadLetterConsumer = client.newConsumer(Schema.STRING)
.topic("winfun/study/test-retry-topic-retry")
.subscriptionName("my-subscription2")
.receiverQueueSize(100)
.ackTimeout(1, TimeUnit.SECONDS)
.subscriptionType(SubscriptionType.Shared)
.messageListener((MessageListener<String>) (consumer1, msg) -> {
log.info("接收到队列「{}」消息:{}",msg.getTopicName(),msg.getValue());
try {
consumer1.acknowledge(msg);
} catch (PulsarClientException e) {
e.printStackTrace();
}
}).subscribe();
}
}

到此,我们已经将Consmuer的几种使用方式都尝试了一遍,可以说基本包含了常用的操作;但是我们可以发现,如果我们每次新建一个Consumer都需要写一堆同样的代码,那其实挺麻烦的,又不好看;并且,现在我们大部分项目都是基于 SpringBoot 来做的,而 SpringBoot 也没有一个比较大众的Starter。

所以接下来的计划就是,自己写一个编写一个关于Pulsar的SpringBoot Starter,这个组件不会特别复杂,但是会支持 Producer 和 Cousnmer 的自动配置,并且支持 Consumer 上面提到的几个点:MessageListener 监听、线程池异步并发消费、重试机制等。

深入Pulsar Consumer的使用方式&源码分析的更多相关文章

  1. spark源码分析以及优化

    第一章.spark源码分析之RDD四种依赖关系 一.RDD四种依赖关系 RDD四种依赖关系,分别是 ShuffleDependency.PrunDependency.RangeDependency和O ...

  2. Spark MLlib - Decision Tree源码分析

    http://spark.apache.org/docs/latest/mllib-decision-tree.html 以决策树作为开始,因为简单,而且也比较容易用到,当前的boosting或ran ...

  3. Fresco 源码分析(二) Fresco客户端与服务端交互(2) Fresco.initializeDrawee()分析 续

    4.2.1.2 Fresco.initializeDrawee()的过程 续 继续上篇博客的分析Fresco.initializeDrawee() sDraweeControllerBuilderSu ...

  4. 安卓MonkeyRunner源码分析之启动

    在工作中因为要追求完成目标的效率,所以更多是强调实战,注重招式,关注怎么去用各种框架来实现目的.但是如果一味只是注重招式,缺少对原理这个内功的了解,相信自己很难对各种框架有更深入的理解. 从几个月前开 ...

  5. Spring IOC源码分析之-刷新前的准备工作

    目录 ClassPathXmlApplicationContext的注册方式 加载父子容器 配置路径解析 容器刷新 刷新容器之刷新预处理 ClassPathXmlApplicationContext的 ...

  6. Spring源码分析之IOC的三种常见用法及源码实现(一)

    1.ioc核心功能bean的配置与获取api 有以下四种 (来自精通spring4.x的p175) 常用的是前三种 第一种方式 <?xml version="1.0" enc ...

  7. SOFA 源码分析 — 调用方式

    前言 SOFARPC 提供了多种调用方式满足不同的场景. 例如,同步阻塞调用:异步 future 调用,Callback 回调调用,Oneway 调用. 每种调用模式都有对应的场景.类似于单进程中的调 ...

  8. dubbo源码分析6-telnet方式的管理实现

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  9. [转]RMI方式Ehcache集群的源码分析

    RMI方式Ehcache集群的源码分析   Ehcache不仅支持基本的内存缓存,还支持多种方式将本地内存中的缓存同步到其他使用Ehcache的服务器中,形成集群.如下图所示:   Ehcache支持 ...

随机推荐

  1. python基础之列表推导式

    #列表推导式 ---> 返回的是列表 for语句 效率更高# 1*1 2*2 3*3 4*4 5*5 6*6 7*7 8*8 9*9# import time# to = time.clock( ...

  2. 【JavaWeb】请求和响应Request&Response

    请求 请求对象 关于请求 顾名思义,意思就是请求一个"对象" 请求不到的,别想了 请求,就是使用者希望从服务器端索取一些资源,向服务器发出询问.在B/S架构中,就是客户浏览器向服务 ...

  3. 《面试八股文》之 Redis 16卷

    微信公众号:moon聊技术 关注选择" 星标 ", 重磅干货,第一 时间送达! [如果你觉得文章对你有帮助,欢迎关注,在看,点赞,转发] 大家好,我是 moon. redis 作为 ...

  4. 安装Go语言支持及Gogs版本管理工具

    安装Go语言支持及Gogs版本管理工具 1. GO 语言: 1.1 介绍 1.1.1 官方介绍: The Go programming language is an open source proje ...

  5. 公有云上构建云原生 AI 平台的探索与实践 - GOTC 技术论坛分享回顾

    7 月 9 日,GOTC 2021 全球开源技术峰会上海站与 WAIC 世界人工智能大会共同举办,峰会聚焦 AI 与云原生两大以开源驱动的前沿技术领域,邀请国家级研究机构与顶级互联网公司的一线技术专家 ...

  6. 【搜索】单词方阵 luogu-1101

    题目描述 给一n×n的字母方阵,内可能蕴含多个"yizhong"单词.单词在方阵中是沿着同一方向连续摆放的.摆放可沿着8个方向的任一方向,同一单词摆放时不再改变方向,单词与单词之间 ...

  7. 【Java基础上】一、简述Java

    一.简述Java ​ Java是一种高级的面向对象的程序语言,在此处,不需要了解什么叫做面向对象,因为后面的文章中自然会谈到这方面的论述.那么,Java就是一个计算机的编程语言. 1.1 Java的历 ...

  8. odoo检查规则

    @api.multidef button_cancel(self): for move in self: if not move.journal_id.update_posted: raise Use ...

  9. linux 之awk--格式化文本信息

    https://www.cnblogs.com/xudong-bupt/p/3721210.html awk是行处理器: 相比较屏幕处理的优点,在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常 ...

  10. SQL语句(一)基础查询与过滤数据

    目录 一.数据库测试表 二.基础查询 1. 获得需要的记录的特定字段 2. 查询常量值 3. 查询表达式 4. 查询函数 5. 起别名 6. 去重 7. CONCAT函数的简单使用 三.过滤数据 大纲 ...