对于Pulsar消费者,我们建议最终用户应用程序使用PulsarListener注释。要使用PulsarListener,您需要使用@EnablePulsar注释。当您使用Spring Boot支持时,它会自动启用此注释并配置PulsarListener所需的所有组件,例如消息侦听器基础设施(负责创建Pulsar消费者)。PulsarMessageListenerContainer使用PulsarConsumerFactory创建和管理Pulsar消费者——它用来消费消息的底层Pulsar消费者。
Spring Boot提供了这个消费者工厂,您可以通过指定Spring.pulser.consumer.*应用程序属性来进一步配置它。
让我们重新审视一下我们在快速浏览部分看到的PulsarListener代码片段:
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
public void listen(String message) {
System.out.println("Message Received: " + message);
}
您可以进一步简化此方法:
@PulsarListener
public void listen(String message) {
System.out.println("Message Received: " + message);
}
在这种最基本的形式中,当@PulsarListener注释上没有提供subscriptionName时,将使用自动生成的订阅名称。同样,当没有直接提供主题时,使用主题解析过程来确定目标主题。
在前面显示的PulsarListener方法中,我们以String形式接收数据,但我们没有指定任何模式类型。在内部,该框架依赖Pulsar的模式机制将数据转换为所需的类型。框架检测到您期望的String类型,然后根据该信息推断模式类型,并将该模式提供给消费者。该框架对所有原始类型都进行了这种推理。对于所有非基元类型,默认模式假定为JSON。如果复杂类型使用JSON以外的任何内容(如AVRO或KEY_VALUE),则必须使用schemaType属性在注释上提供模式类型。
以下示例显示了另一个PulsarListener方法,它接受一个Integer:
@PulsarListener(subscriptionName = "my-subscription-1", topics = "my-topic-1")
public void listen(Integer message) {
System.out.println(message);
}
以下PulsarListener方法展示了如何从主题中使用复杂类型:
@PulsarListener(subscriptionName = "my-subscription-2", topics = "my-topic-2", schemaType = SchemaType.JSON)
public void listen(Foo message) {
System.out.println(message);
}
让我们看看更多的方法。
您可以直接使用Pulsar消息:
@PulsarListener(subscriptionName = "my-subscription", topics = "my-topic")
public void listen(org.apache.pulsar.client.api.Message message) {
System.out.println(message.getValue());
}
以下示例使用Spring消息传递信封来消耗记录:
@PulsarListener(subscriptionName = "my-subscription", topics = "my-topic")
public void listen(org.springframework.messaging.Message message) {
System.out.println(message.getPayload());
}
现在让我们看看如何批量使用记录。以下示例使用PulsarListener将记录作为POJO批量消费:
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
public void listen(List messages) {
System.out.println("records received :" + messages.size());
messages.forEach((message) -> System.out.println("record : " + message));
}
请注意,在这个例子中,我们接收作为对象集合(List)的记录。此外,要在PulsarListener级别启用批处理消费,您需要将注释上的批处理属性设置为true。
根据List所包含的实际类型,框架试图推断要使用的模式。如果List包含JSON之外的复杂类型,您仍然需要在PulsarListener上提供schemaType。
以下使用Pulsar Java客户端提供的消息信封:
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
public void listen(List> messages) {
System.out.println("records received :" + messages.size());
messages.forEach((message) -> System.out.println("record : " + message.getValue()));
}
以下示例使用Spring消息类型的信封来处理批记录:
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
public void listen(List> messages) {
System.out.println("records received :" + messages.size());
messages.forEach((message) -> System.out.println("record : " + message.getPayload()));
}
最后,您还可以将Pulsar中的Messages holder对象用于批处理监听器:
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
public void listen(org.apache.pulsar.client.api.Messages> messages) {
System.out.println("records received :" + messages.size());
messages.forEach((message) -> System.out.println("record : " + message.getValue()));
}
当您使用PulsarListener时,您可以直接在注释本身上提供Pulsar消费者属性。如果您不想使用前面提到的Boot配置属性或有多个PulsarListener方法,这很方便。
以下示例直接在PulsarListener上使用Pulsar消费者属性:
@PulsarListener(properties = { "subscriptionName=subscription-1", "topicNames=foo-1", "receiverQueueSize=5000" })
void listen(String message) {
}
使用的属性是Pulsar的直接消费者属性,而不是spring.Pulsar.consumer应用程序配置属性
如果没有机会提前知道Pulsar主题的模式类型,您可以使用AUTO_CONSUME模式类型来使用通用记录。在这种情况下,主题使用与主题关联的模式信息将消息反序列化为GenericRecord对象。
要使用通用记录,请设置schemaType=schemaType。在@PulsarListener上设置AUTO_CONSUME,并使用GenericRecord类型的Pulsar消息作为消息参数,如下所示。
@PulsarListener(topics = "my-generic-topic", schemaType = SchemaType.AUTO_CONSUME)
void listen(org.apache.pulsar.client.api.Message message) {
GenericRecord record = message.getValue();
record.getFields().forEach((f) ->
System.out.printf("%s = %s%n", f.getName(), record.getField(f)));
}
GenericRecord API允许访问字段及其关联值
您可以使用PulsarListenerConsumerBuilderCustomizer自定义ConsumerBuilder中可用的任何字段,方法是提供PulsarListener ConsumerBuilderCustom类型的@Bean,然后将其提供给PulsarListener,如下所示。
@PulsarListener(topics = "hello-topic", consumerCustomizer = "myCustomizer")
public void listen(String message) {
System.out.println("Message Received: " + message);
}
@Bean
PulsarListenerConsumerBuilderCustomizer myCustomizer() {
return (builder) -> builder.consumerName("myConsumer");
}
如果您的应用程序只注册了一个@PulsarListener和一个PulsarListenerConsumerBuilderCustomizer bean,则自定义程序将自动应用。
如前所述,对于Java原语,Spring for Apache Pulsar框架可以推断出在PulsarListener上使用的正确模式。对于非基元类型,如果没有在注释上明确指定Schema,Spring For Apache Pulsar框架将尝试构建Schema。JSON类型。
目前支持的复杂模式类型有JSON、AVRO、PROTOBUF、AUTO_CONSUME、KEY_VALUE和INLINE编码。
作为在PulsarListener上为复杂类型指定模式的替代方案,模式解析器可以配置类型的映射。这消除了在侦听器上设置模式的需要,因为框架使用传入消息类型咨询解析器。
模式映射可以使用spring.pulsar.defaults.type-mappings属性进行配置。以下示例使用application.yml分别使用AVRO和JSON模式为User和Address复杂对象添加映射:
spring:
pulsar:
defaults:
type-mappings:
- message-type: com.acme.User
schema-info:
schema-type: AVRO
- message-type: com.acme.Address
schema-info:
schema-type: JSON
消息类型是消息类的完全限定名。
添加映射的首选方法是通过上述属性。但是,如果需要更多的控制,您可以提供一个模式解析器定制器来添加映射。
以下示例使用模式解析器定制器分别使用AVRO和JSON模式为User和Address复杂对象添加映射:
@Bean
public SchemaResolverCustomizer schemaResolverCustomizer() {
return (schemaResolver) -> {
schemaResolver.addCustomSchemaMapping(User.class, Schema.AVRO(User.class));
schemaResolver.addCustomSchemaMapping(Address.class, Schema.JSON(Address.class));
}
}
指定用于特定消息类型的默认模式信息的另一种选择是用@PulsarMessage注释标记消息类。可以通过注释上的schemaType属性指定架构信息。
以下示例将系统配置为在生成或使用Foo类型的消息时使用JSON作为默认模式:
@PulsarMessage(schemaType = SchemaType.JSON)
record Foo(String value) {
}
有了这个配置,就不需要在监听器上设置模式,例如:
@PulsarListener(subscriptionName = "user-sub", topics = "user-topic")
public void listen(User user) {
System.out.println(user);
}
有时,您需要直接访问Pulsar Consumer对象。以下示例显示了如何获取它:
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
public void listen(String message, org.apache.pulsar.client.api.Consumer consumer) {
System.out.println("Message Received: " + message);
ConsumerStats stats = consumer.getStats();
...
}
When accessing the Consumer object this way, do NOT invoke any operations that would change the Consumer’s cursor position by invoking any receive methods. All such operations must be done by the container. |
现在我们通过PulsarListener看到了消费者端的基本交互。现在让我们深入了解PulsarListener如何与底层Pulsar消费者交互的内部工作原理。请记住,对于最终用户应用程序,在大多数情况下,我们建议在使用Spring for Apache Pulsar时直接使用PulsarListener注释从Pulsar主题中消费,因为该模型涵盖了广泛的应用程序用例。然而,了解PulsarListener的内部工作原理非常重要。本节将详细介绍这些细节。
如前所述,当您使用Spring for Apache Pulsar时,消息监听器容器是消息消费的核心。PulsarListener在幕后使用消息监听器容器基础设施来创建和管理Pulsar消费者。Spring for Apache Pulsar通过PulsarMessageListenerContainer提供了此消息侦听器容器的合约。此消息侦听器容器的默认实现是通过DefaultPulsarMessageListenerContainer提供的。顾名思义,PulsarMessageListenerContainer包含消息侦听器。容器创建Pulsar消费者,然后运行一个单独的线程来接收和处理数据。数据由提供的消息侦听器实现处理。
消息侦听器容器通过使用消费者的batchReceive方法批量使用数据。一旦接收到数据,它就会被移交给所选的消息侦听器实现。
当您使用Spring for Apache Pulsar时,以下消息监听器类型可用。
PulsarRecordMessageListener
Pulsar确认消息监听器
PulsarBatchMessageListener
批处理确认消息监听器
我们将在以下部分中看到有关这些不同消息侦听器的详细信息。
然而,在这样做之前,让我们仔细看看容器本身。
这是一个基于消费者的消息侦听器容器。以下列表显示了它的构造函数:
public DefaultPulsarMessageListenerContainer(PulsarConsumerFactory super T> pulsarConsumerFactory,
PulsarContainerProperties pulsarContainerProperties)
}
它接收一个PulsarConsumerFactory(用于创建消费者)和一个Pulsar ContainerProperties对象(包含有关容器属性的信息)。PulsarContainerProperties具有以下构造函数:
public PulsarContainerProperties(String... topics)
public PulsarContainerProperties(Pattern topicPattern)
您可以通过PulsarContainerProperties或作为提供给消费者工厂的消费者属性提供主题信息。以下示例使用DefaultPulsarMessageListenerContainer:
Map config = new HashMap<>();
config.put("topics", "my-topic");
PulsarConsumerFactory pulsarConsumerFactorY = DefaultPulsarConsumerFactory<>(pulsarClient, config);
PulsarContainerProperties pulsarContainerProperties = new PulsarContainerProperties();
pulsarContainerProperties.setMessageListener((PulsarRecordMessageListener>) (consumer, msg) -> {
});
DefaultPulsarMessageListenerContainer pulsarListenerContainer = new DefaultPulsarMessageListenerContainer(pulsarConsumerFacotyr,
pulsarContainerProperties);
return pulsarListenerContainer;
如果在直接使用侦听器容器时未指定主题信息,则使用PulsarListener使用的相同主题解析过程,但省略了“消息类型默认”步骤。
DefaultPulsarMessageListenerContainer只创建一个消费者。如果你想通过多个线程管理多个消费者,你需要使用ConcurrentPulsarMessageListenerContainer。
ConcurrentPulsarMessageListenerContainer具有以下构造函数:
public ConcurrentPulsarMessageListenerContainer(PulsarConsumerFactory super T> pulsarConsumerFactory,
PulsarContainerProperties pulsarContainerProperties)
ConcurrentPulsarMessageListenerContainer允许您通过设置器指定并发属性。仅在非独占订阅(故障转移、共享和密钥共享)上允许并发数超过1。当您使用独占订阅模式时,并发性只能使用默认值1。
以下示例通过PulsarListener注释为故障转移订阅启用并发。
@PulsarListener(topics = "my-topic", subscriptionName = "subscription-1",
subscriptionType = SubscriptionType.Failover, concurrency = "3")
void listen(String message, Consumer consumer) {
...
System.out.println("Current Thread: " + Thread.currentThread().getName());
System.out.println("Current Consumer: " + consumer.getConsumerName());
}
在前面的监听器中,假设主题我的主题有三个分区。如果它是一个非分区主题,将并发性设置为3什么也不做。除了主要的活动消费者外,还有两个空闲消费者。如果主题有三个以上的分区,则消息将在容器创建的消费者之间进行负载平衡。如果您运行此PulsarListener,您会看到来自不同分区的消息通过不同的消费者被消费,正如前面示例中打印出的线程名称和消费者名称所暗示的那样。
当您在分区主题上以这种方式使用故障转移订阅时,Pulsar保证消息顺序。
以下清单显示了PulsarListener的另一个示例,但启用了共享订阅和并发。
@PulsarListener(topics = "my-topic", subscriptionName = "subscription-1",
subscriptionType = SubscriptionType.Shared, concurrency = "5")
void listen(String message) {
...
}
在前面的示例中,PulsarListener创建了五个不同的消费者(这次,我们假设主题有五个分区)。
在此版本中,没有消息排序,因为共享订阅不保证Pulsar中的任何消息排序。
如果您需要消息排序,但仍希望使用共享订阅类型,则需要使用Key_shared订阅类型。
让我们来看看消息侦听器容器是如何实现单记录和基于批处理的消息消费的。
为了讨论,让我们重新审视一下我们的基本PulsarListener:
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
public void listen(String message) {
System.out.println("Message Received: " + message);
}
使用此PulsarListener方法,我们必须要求Spring for Apache Pulsar每次使用单个记录调用侦听器方法。我们提到,消息侦听器容器使用消费者上的batchReceive方法分批使用数据。在这种情况下,框架检测到PulsarListener接收到一条记录。这意味着,每次调用该方法时,都需要一个单独的记录。虽然这些记录被消息侦听器容器分批使用,但它会迭代接收到的批,并通过PulsarRecordMessageListener的适配器调用侦听器方法。正如您在上一节中看到的,PulsarRecordMessageListener从Pulsar Java客户端提供的MessageListener扩展而来,它支持基本的接收方法。
以下示例显示了PulsarListener批量消费记录:
@PulsarListener(subscriptionName = "hello-batch-subscription", topics = "hello-batch", schemaType = SchemaType.JSON, batch = true)
public void listen4(List messages) {
System.out.println("records received :" + messages.size());
messages.forEach((message) -> System.out.println("record : " + message));
}
当您使用这种类型的PulsarListener时,框架会检测到您处于批处理模式。由于它已经使用Consumer的batchReceive方法批量接收了数据,因此它通过PulsarBatchMessageListener的适配器将整个批传递给侦听器方法。
Pulsar消息元数据可以作为Spring消息头使用。可用标头的列表可以在PulsarHeaders.java中找到。
以下示例显示了如何在使用单记录消费模式的应用程序中访问各种Pulsar Header:
@PulsarListener(topics = "simpleListenerWithHeaders")
void simpleListenerWithHeaders(String data, @Header(PulsarHeaders.MESSAGE_ID) MessageId messageId,
@Header(PulsarHeaders.RAW_DATA) byte[] rawData,
@Header("foo") String foo) {
}
在前面的示例中,我们访问messageId和rawData消息元数据的值以及名为foo的自定义消息属性。Spring@Header注释用于每个标头字段。
您还可以使用Pulsar的消息作为信封来携带有效载荷。这样做时,用户可以直接调用Pulsar消息上的相应方法来检索元数据。但是,为了方便起见,您还可以使用Header注释来检索它。请注意,您还可以使用Spring消息传递消息信封来携带有效载荷,然后使用@Header检索Pulsar标头。
在本节中,我们将了解如何在使用批处理消费者的应用程序中访问各种Pulsar Header:
@PulsarListener(topics = "simpleBatchListenerWithHeaders", batch = true)
void simpleBatchListenerWithHeaders(List data,
@Header(PulsarHeaders.MESSAGE_ID) List messageIds,
@Header(PulsarHeaders.TOPIC_NAME) List topicNames, @Header("foo") List fooValues) {
}
在前面的示例中,我们将数据作为List
当您使用Spring for Apache Pulsar时,消息确认由框架处理,除非应用程序选择退出。在本节中,我们将详细介绍框架如何处理消息确认。
Spring for Apache Pulsar提供了以下确认消息的模式:
BATCH确认模式是默认模式,但您可以在消息侦听器容器上更改它。在以下部分中,我们将看到当您使用PulsarListener的单个和批处理版本时,确认是如何工作的,以及它们如何转换为支持消息侦听器容器(并最终转换为Pulsar消费者)。
让我们重新审视我们基于单消息的基本PulsarListener:
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
public void listen(String message) {
System.out.println("Message Received: " + message);
}
当您使用PulsarListener时,很自然地会想知道确认是如何工作的,特别是如果您熟悉直接使用Pulsar消费者。答案归结为消息监听器容器,因为它是Apache Pulsar在Spring中的中心位置,负责协调所有与消费者相关的活动。
假设您没有覆盖默认行为,这是您使用前面的PulsarListener时幕后发生的事情:
这是正常的流程。如果原始批中的任何记录抛出异常,Spring for Apache Pulsar会单独跟踪这些记录。当处理完批处理中的所有记录后,Spring for Apache Pulsar会确认所有成功的消息,并否定(nack)所有失败的消息。换句话说,当使用PulsarRecordMessageListener使用单个记录并使用BATCH的默认ack模式时,框架会等待从batchReceive调用接收到的所有记录成功处理,然后调用Pulsar消费者上的确认方法。如果在调用处理程序方法时任何特定记录抛出异常,Spring for Apache Pulsar会跟踪这些记录,并在处理完整个批处理后分别对这些记录调用negativeAcknowledge。
如果应用程序希望每条记录都发生确认或否定确认,则可以启用记录确认模式。在这种情况下,处理每条记录后,如果没有错误,则确认消息,如果有错误,则否定确认消息。以下示例在Pulsar侦听器上启用RECORD ack模式:
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar", ackMode = AckMode.RECORD)
public void listen(String message) {
System.out.println("Message Received: " + message);
}
您可能并不总是希望框架发送确认,而是直接从应用程序本身发送确认。Spring for Apache Pulsar提供了几种启用手动消息确认的方法。以下示例显示了其中之一:
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar", ackMode = AckMode.MANUAL)
public void listen(Message message, Acknowledgment acknowledgment) {
System.out.println("Message Received: " + message.getValue());
acknowledgment.acknowledge();
}
这里有几件事值得解释。首先,我们通过在PulsarListener上设置ackMode来启用手动ack模式。当启用手动确认模式时,Spring for Apache Pulsar允许应用程序注入一个Acknowledgment对象。该框架通过选择一个兼容的消息侦听器容器来实现这一点:PulsarAcknowledgingMessageListener用于基于单记录的消费,这使您可以访问Acknowledgment对象。
Acknowledgement对象提供以下API方法:
void acknowledge();
void acknowledge(MessageId messageId);
void acknowledge(List messageIds);
void nack();
void nack(MessageId messageId);
您可以在使用MANUAL ack模式时将此Acknowledgment对象注入到PulsarListener中,然后调用相应的方法之一。
在前面的PulsarListener示例中,我们调用了一个无参数的确认方法。这是因为框架知道它当前在哪个消息下运行。当调用acknowledge()时,您不需要使用Message信封接收有效载荷,而是使用目标类型 — 在这个例子中,字符串。
与可能的确认类似,Acknowledgement API也提供了否定确认的选项。请参阅前面显示的nack方法。
您也可以直接在Pulsar消费者上呼叫确认:
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar", ackMode = AckMode.MANUAL)
public void listen(Message message, Consumer consumer) {
System.out.println("Message Received: " + message.getValue());
try {
consumer.acknowledge(message);
}
catch (Exception e) {
....
}
}
当直接在底层消费者上调用确认时,您需要自己进行错误处理。使用确认不需要这样做,因为框架可以为您做到这一点。因此,在使用手动确认时,您应该使用Acknowledgment对象方法。
当使用手动确认时,重要的是要理解框架完全不接受任何确认。因此,在设计应用程序时考虑正确的确认策略非常重要。
当您分批使用记录(请参阅“消息确认模式”)并使用默认的批处理确认模式时,如果整个批处理成功,则整个批处理都会得到确认。如果任何记录抛出异常,则整个批处理将被否定确认。请注意,这可能与生产商侧的批次不同。相反,这是在消费者上调用batchReceive返回的批处理
考虑以下批处理监听器:
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar", batch = true)
public void batchListen(List messages) {
for (Foo foo : messages) {
...
}
}
当处理完传入集合中的所有消息(本例中为消息)时,框架会确认所有消息。
在批处理模式下消费时,RECORD不是允许的ack模式。这可能会导致问题,因为应用程序可能不希望再次重新交付整个批次。在这种情况下,您需要使用手动确认模式。
如前一节所述,当在消息侦听器容器上设置MANUAL ack模式时,框架不会进行任何确认,无论是肯定的还是否定的。解决这些问题完全取决于申请。当设置了手动确认模式时,Spring for Apache Pulsar会选择一个兼容的消息侦听器容器:PulsarBatchAcknowledgingMessageListener用于批量消费,这使您可以访问Acknowledgment对象。以下是在确认API中可用的方法:
void acknowledge();
void acknowledge(MessageId messageId);
void acknowledge(List messageIds);
void nack();
void nack(MessageId messageId);
您可以在使用手动确认模式时将此确认对象注入到PulsarListener中。以下列表显示了基于批处理的侦听器的基本示例:
@PulsarListener(subscriptionName = "hello-pulsar-subscription", topics = "hello-pulsar")
public void listen(List> messgaes, Acknowlegement acknowledgment) {
for (Message message : messages) {
try {
...
acknowledgment.acknowledge(message.getMessageId());
}
catch (Exception e) {
acknowledgment.nack(message.getMessageId());
}
}
}
当您使用批处理侦听器时,消息侦听器容器无法知道它当前正在操作哪个记录。因此,要手动确认,您需要使用一个重载的确认方法,该方法接受MessageId或List
现在我们已经看到了PulsarListener和消息监听器容器基础设施及其各种功能,现在让我们试着理解消息重新传递和错误处理。Apache Pulsar为消息重新传递和错误处理提供了各种本机策略。我们来看看它们,看看如何在Spring for Apache Pulsar中使用它们。
默认情况下,Pulsar消费者不会重新传递消息,除非消费者崩溃,但您可以通过在Pulsar消费者上设置ack超时来更改此行为。如果ack timeout属性的值大于零,并且Pulsar消费者在该超时时间内未确认消息,则重新传递消息。
当您为Apache Pulsar使用Spring时,您可以通过消费者自定义程序或使用@PulsarListener的properties属性中的Pulsar ackTimeoutMillis属性来设置此属性:
@PulsarListener(subscriptionName = "subscription-1", topics = "topic-1"
properties = {"ackTimeoutMillis=60000"})
public void listen(String s) {
...
}
当您指定ack超时时,如果消费者未在60秒内发送确认,Pulsar会将消息重新传递给消费者。
如果要为具有不同延迟的回退超时指定一些高级回退选项,可以执行以下操作:
@EnablePulsar
@Configuration
class AckTimeoutRedeliveryConfig {
@PulsarListener(subscriptionName = "withAckTimeoutRedeliveryBackoffSubscription",
topics = "withAckTimeoutRedeliveryBackoff-test-topic",
ackTimeoutRedeliveryBackoff = "ackTimeoutRedeliveryBackoff",
properties = { "ackTimeoutMillis=60000" })
void listen(String msg) {
// some long-running process that may cause an ack timeout
}
@Bean
RedeliveryBackoff ackTimeoutRedeliveryBackoff() {
return MultiplierRedeliveryBackoff.builder().minDelayMs(1000).maxDelayMs(10 * 1000).multiplier(2)
.build();
}
}
在前面的示例中,我们为Pulsar的RediveryBackoff指定了一个bean,最小延迟为1秒,最大延迟为10秒,退避倍数为2。在初始回退超时发生后,通过此回退bean控制消息的重新传递。我们通过将ackTimeoutRedeliveryBackoff属性设置为实际的bean名称,将退避bean提供给PulsarListener注释 — backTimeout在这种情况下,重新交付backoff。
当确认为否定时,Pulsar消费者允许您指定应用程序希望如何重新传递消息。默认情况下是在一分钟内重新传递消息,但您可以通过消费者自定义程序或使用@PulsarListener的properties属性中的原生Pulsar negativeAckRediveryDelay属性进行更改:
@PulsarListener(subscriptionName = "subscription-1", topics = "topic-1"
properties = {"negativeAckRedeliveryDelay=10ms"})
public void listen(String s) {
...
}
您还可以通过在PulsarProducer上提供RedeliveryBackoff bean并将bean名称设置为negativeAckRedeliveryBackup属性,使用乘数指定不同的延迟和退避机制,如下所示:
@EnablePulsar
@Configuration
class NegativeAckRedeliveryConfig {
@PulsarListener(subscriptionName = "withNegRedeliveryBackoffSubscription",
topics = "withNegRedeliveryBackoff-test-topic", negativeAckRedeliveryBackoff = "redeliveryBackoff",
subscriptionType = SubscriptionType.Shared)
void listen(String msg) {
throw new RuntimeException("fail " + msg);
}
@Bean
RedeliveryBackoff redeliveryBackoff() {
return MultiplierRedeliveryBackoff.builder().minDelayMs(1000).maxDelayMs(10 * 1000).multiplier(2)
.build();
}
}
Apache Pulsar允许应用程序在共享订阅类型的消费者身上使用死信主题。对于独占和故障转移订阅类型,此功能不可用。基本思想是,如果消息被重试了一定次数(可能是由于ack超时或nack重新传递),一旦重试次数用尽,消息就可以被发送到一个称为死信队列(DLQ)的特殊主题。让我们通过检查一些代码片段来了解此功能的一些细节:
@EnablePulsar
@Configuration
class DeadLetterPolicyConfig {
@PulsarListener(id = "deadLetterPolicyListener", subscriptionName = "deadLetterPolicySubscription",
topics = "topic-with-dlp", deadLetterPolicy = "deadLetterPolicy",
subscriptionType = SubscriptionType.Shared, properties = { "ackTimeoutMillis=1000" })
void listen(String msg) {
throw new RuntimeException("fail " + msg);
}
@PulsarListener(id = "dlqListener", topics = "my-dlq-topic")
void listenDlq(String msg) {
System.out.println("From DLQ: " + msg);
}
@Bean
DeadLetterPolicy deadLetterPolicy() {
return DeadLetterPolicy.builder().maxRedeliverCount(10).deadLetterTopic("my-dlq-topic").build();
}
}
首先,我们为DeadLetterPolicy提供了一个特殊的bean,它被命名为DeadLetterPolicy(它可以是您想要的任何名称)。此bean指定了许多内容,例如最大传递率(在本例中为10)和死信主题的名称 — 在这种情况下,我的dlq主题。如果不指定DLQ主题名称,Pulsar中默认为
如果主主题在幕后被分区,Pulsar会将每个分区视为单独的主题。Pulsar在主主题名称后附加partition-
正如我们之前提到的,Apache Pulsar中的DLQ功能仅适用于共享订阅。如果应用程序需要对非共享订阅使用一些类似的功能,该怎么办?Pulsar不支持独占和故障转移订阅的DLQ的主要原因是这些订阅类型有订单保证。允许重新交付、DLQ等有效地接收乱序消息。但是,如果一个应用程序可以接受这一点,但更重要的是,它需要非共享订阅的DLQ功能呢?为此,Spring For Apache Pulsar提供了一个PulsarConsumerErrorHandler,您可以在Pulsar中的任何订阅类型中使用它:独占、故障转移、共享或密钥共享。
当您为Apache Pulsar使用Spring的PulsarConsumerErrorHandler时,请确保不要在侦听器上设置ack超时属性。
让我们通过检查一些代码片段来了解一些细节:
@EnablePulsar
@Configuration
class PulsarConsumerErrorHandlerConfig {
@Bean
PulsarConsumerErrorHandler pulsarConsumerErrorHandler(
PulsarTemplate pulsarTemplate) {
return new DefaultPulsarConsumerErrorHandler<>(
new PulsarDeadLetterPublishingRecoverer<>(pulsarTemplate, (c, m) -> "my-foo-dlt"), new FixedBackOff(100, 10));
}
@PulsarListener(id = "pulsarConsumerErrorHandler-id", subscriptionName = "pulsatConsumerErrorHandler-subscription",
topics = "pulsarConsumerErrorHandler-topic",
pulsarConsumerErrorHandler = "pulsarConsumerErrorHandler")
void listen(String msg) {
throw new RuntimeException("fail " + msg);
}
@PulsarListener(id = "pceh-dltListener", topics = "my-foo-dlt")
void listenDlt(String msg) {
System.out.println("From DLT: " + msg);
}
}
考虑一下pulsorConsumerErrorHandler bean。这将创建一个PulsarConsumerErrorHandler类型的bean,并使用Spring为Apache Pulsar提供的默认实现:DefaultPulsarConsumerError Handler。DefaultPulsarConsumerErrorHandler有一个构造函数,它接受PulsarMessageRecovererFactory和org.springframework.util.backoff。退后。PulserMessage RecovererFactory是一个具有以下API的功能接口:
@FunctionalInterface
public interface PulsarMessageRecovererFactory {
/**
* Provides a message recoverer {@link PulsarMessageRecoverer}.
* @param consumer Pulsar consumer
* @return {@link PulsarMessageRecoverer}.
*/
PulsarMessageRecoverer recovererForConsumer(Consumer consumer);
}
recoverorForConsumer方法接受Pulsar消费者并返回PulsarMessageRecoverer,这是另一个功能接口。以下是PulserMessage恢复程序的API:
public interface PulsarMessageRecoverer {
/**
* Recover a failed message, for e.g. send the message to a DLT.
* @param message Pulsar message
* @param exception exception from failed message
*/
void recoverMessage(Message message, Exception exception);
}
Spring for Apache Pulsar为PulsarMessageRecovererFactory提供了一个名为PulsarDeadLetterPublishingRecoverer的实现,该实现提供了一种默认实现,可以通过将消息发送到死信主题(DLT)来恢复消息。我们将此实现提供给前面DefaultPulsarConsumerErrorHandler的构造函数。作为第二个参数,我们提供了一个FixedBackOff。您还可以为高级回退功能提供Spring的指数回退。然后,我们为PulsarConsumerErrorHandler提供此bean名称作为PulsarListener的属性。该属性称为pulsorConsumerErrorHandler。每次PulsarListener方法对消息失败时,都会重试。重试次数由Backoff提供的实现值控制。在我们的示例中,我们进行了10次重试(总共11次尝试 — 第一次重试,然后是10次重试)。一旦所有重试都结束,消息将发送到DLT主题。
我们提供的PulsarDeadLetterPublishingRecoverer实现使用了一个PulsarTemplate,用于将消息发布到DLT。在大多数情况下,Spring Boot中相同的自动配置PulsarTemplate就足够了,但需要注意分区主题。当使用分区主题并为主主题使用自定义消息路由时,您必须使用不采用自动配置的PulsarProducerFactory的不同PulsarTemplate,该PulsarTemplate填充了消息路由模式的custompartition值。您可以使用具有以下蓝图的PulsarConsumerErrorHandler:
@Bean
PulsarConsumerErrorHandler pulsarConsumerErrorHandler(PulsarClient pulsarClient) {
PulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, Map.of());
PulsarTemplate pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory);
BiFunction, Message>, String> destinationResolver =
(c, m) -> "my-foo-dlt";
PulsarDeadLetterPublishingRecoverer pulsarDeadLetterPublishingRecoverer =
new PulsarDeadLetterPublishingRecoverer<>(pulsarTemplate, destinationResolver);
return new DefaultPulsarConsumerErrorHandler<>(pulsarDeadLetterPublishingRecoverer,
new FixedBackOff(100, 5));
}
请注意,我们为PulsarDeadLetterPublishingRecoverer提供了一个目标解析器作为第二个构造函数参数。如果没有提供,PulsarDeadLetterPublishingRecoverer将使用<订阅名称>-<主题名称>-DLT>作为DLT主题名称。使用此功能时,您应该通过设置目标解析器而不是使用默认值来使用正确的目标名称。
当使用单记录消息侦听器时,就像我们使用PulsarConsumerErrorHnadler一样,如果您使用手动确认,请确保在抛出异常时不要对消息进行否定确认。相反,将异常重新抛出到容器中。否则,容器认为消息是单独处理的,不会触发错误处理。
最后,我们有第二个PulsarListener,它从DLT主题接收消息。
在本节到目前为止提供的示例中,我们只看到了如何将PulsarConsumerErrorHandler与单个记录消息侦听器一起使用。接下来,我们看看如何在批处理监听器上使用它。
首先,让我们来看一个批处理PulsarListener方法:
@PulsarListener(subscriptionName = "batch-demo-5-sub", topics = "batch-demo-4", batch = true, concurrency = "3",
subscriptionType = SubscriptionType.Failover,
pulsarConsumerErrorHandler = "pulsarConsumerErrorHandler", ackMode = AckMode.MANUAL)
void listen(List> data, Consumer consumer, Acknowledgment acknowledgment) {
for (Message datum : data) {
if (datum.getValue() == 5) {
throw new PulsarBatchListenerFailedException("failed", datum);
}
acknowledgement.acknowledge(datum.getMessageId());
}
}
@Bean
PulsarConsumerErrorHandler pulsarConsumerErrorHandler(
PulsarTemplate pulsarTemplate) {
return new DefaultPulsarConsumerErrorHandler<>(
new PulsarDeadLetterPublishingRecoverer<>(pulsarTemplate, (c, m) -> "my-foo-dlt"), new FixedBackOff(100, 10));
}
@PulsarListener(subscriptionName = "my-dlt-subscription", topics = "my-foo-dlt")
void dltReceiver(Message message) {
System.out.println("DLT - RECEIVED: " + message.getValue());
}
我们再次为pulsarConsumerErrorHandler属性提供PulsarConsumerErrorHandlerbean名称。当您使用批处理监听器(如上例所示)并希望使用Spring for Apache Pulsar的PulsarConsumerErrorHandler时,您需要使用手动确认。这样,您就可以确认所有成功的个人消息。对于那些失败的,您必须抛出一个PulsarBatchListenerFailedException,并附上失败的消息。没有这个例外,框架不知道如何处理失败。重试时,容器会向侦听器发送一批新消息,从失败的消息开始。如果再次失败,则重试,直到重试次数用尽,此时消息将发送到DLT。此时,容器会确认消息,并将侦听器与原始批中的后续消息一起移交。
Spring for Apache Pulsar提供了一种方便的方法来定制PulsarListener使用的容器创建的消费者。应用程序可以为PulsarListenerConsumerBuilderCustomizer提供bean。这里有一个例子。
@Bean
public PulsarListenerConsumerBuilderCustomizer myCustomizer() {
return cb -> {
cb.subscriptionName("modified-subscription-name");
};
}
然后,这个定制器bean名称可以作为PusralListener注释上的属性提供,如下所示。
@PulsarListener(subscriptionName = "my-subscription",
topics = "my-topic", consumerCustomizer = "myCustomizer")
void listen(String message) {
}
该框架通过PulsarListener检测提供的bean,并在创建Pulsar Consumer之前将此定制器应用于Consumer构建器。
如果你有多个PulsarListener方法,并且每个方法都有不同的定制规则,你应该创建多个定制器bean,并在每个PulsarListener上附加适当的定制器。
在某些情况下,应用程序可能希望暂时暂停消息消费,然后稍后继续。Spring for Apache Pulsar提供了暂停和恢复底层消息侦听器容器的能力。当Pulsar消息侦听器容器暂停时,容器为从Pulsar消费者接收数据而进行的任何轮询都将暂停。同样,当容器恢复时,如果主题在暂停时添加了任何新记录,则下一次轮询将开始返回数据。
要暂停或恢复侦听器容器,首先通过PulserListenerEndpointRegistry bean获取容器实例,然后在容器实例上调用暂停/恢复API,如下面的片段所示:
@Autowired
private PulsarListenerEndpointRegistry registry;
void someMethod() {
PulsarMessageListenerContainer container = registry.getListenerContainer("my-listener-id");
container.pause();
}
@PulsarReader(id = "reader-customizer-demo-id", topics = "reader-customizer-demo-topic",
readerCustomizer = "myCustomizer")
void read(String message) {
//...
}
@Bean
public PulsarReaderReaderBuilderCustomizer myCustomizer() {
return readerBuilder -> {
readerBuilder.startMessageId(messageId); // the first message read is after this message id.
// Any other customizations on the readerBuilder
};
}
但是,如果您改为手动配置组件,则在构建消息侦听器容器工厂时,必须相应地更新容器启动属性。