You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TIP: Complex Schema types that are currently supported are JSON, AVRO, PROTOBUF, and KEY_VALUE. For KEY_VALUE schemata, only INLINE encoding is supported.
829
829
830
+
==== Message Redelivery and Error Handling
831
+
832
+
Now that we have seen both `PulsarListener` and the message listener container infrastructure, and its various functions, let us now try to understand message redelivery and error handling.
833
+
Apache Pulsar provides various native strategies for message redelivery and error handling, and we are going to take a look at them first and see how we can leverage them through Spring for Apache Pulsar.
834
+
835
+
===== Specifying Acknowledgment Timeout for Message Redelivery
836
+
837
+
By default, Pulsar consumers will not redeliver messages unless the consumer crashes, but you can change this behavior by setting an ack timeout on the Pulsar consumer.
838
+
When using Spring for Apache Pulsar, we can enable this property by setting the Boot property `spring.pulsar.consumer.ack-timeout-millis`.
839
+
If this property has a value above zero, then if Pulsar consumer does not acknowledge a message within that timeout period, then the message will be redelivered.
840
+
841
+
You can also specify this property directly as a Pulsar consumer property on the `PulsarListener` itself as shown below:
When specifying `ackTimeoutMillis` as seen in the above `PulsarListener` method, then if the consumer does not send an acknowledgement within 60 seconds, the message will be redelivered by Pulsar to the consumer.
855
+
856
+
If you want to specify some advanced backoff options for ack timeout with different delays, then you can do the following:
In the example above, we are specifying a bean for Pulsar's `RedeliveryBackoff` with a minimum delay of 1 second and a maximum delay of 10 seconds with a backoff multiplier of 2.
884
+
After the initial ack timeout occurs, then the message redeliveries will be controlled through this backoff bean.
885
+
We provide the backoff bean to the `PulsarListener` annotation by setting the `ackTimeoutRedeliveryBackoff` property to the actual bean name - `ackTimeoutRedeliveryBackoff` in this case.
Here also, you can specify different delays and backoff mechanisms with a multiplier by providing a `RedeliveryBackoff` bean and provide the bean name as the `negativeAckRedeliveryBackoff` property on the PulsarProducer.
===== Using Dead Letter Topic from Apache Pulsar for Message Redelivery and Error Handling
932
+
933
+
Apache Pulsar allows applications to use a dead letter topic on consumers with a `Shared` subscription type.
934
+
For subscription types `Exclusive` and `Failover`, this feature is not available.
935
+
The basic idea is that if a message is retried for a certain number of times, maybe due to an ack timeout or nack redelivery, and once the number of retries are exhausted, then the message can be sent to a special topic called DLQ.
936
+
Let us see some details around this feature in action by inspecting some code snippets.
First, we have a special bean for `DeadLetterPolicy` and it's named as `deadLetterPolicy` (it acn be any name as you wish).
968
+
This bean specifies a number of things, such as the max delivery - 10 in this case, and the name of the dead letter topic - `my-dlq-topic`.
969
+
If you don't specify a DLQ topic name, then it defaults to `<topicname>-<subscriptionname>-DLQ` in Pulsar.
970
+
Next, we provide this bean name to `PulsarListener` using the property `deadLetterPolicy`.
971
+
Note that the `PulsarListener` has a subscription type of `Shared`, as the DLQ feature only works with shared subscriptions.
972
+
This code is primarily for demonstration purposes, so we provide an `ackTimeoutMillis` value of 1 millisecond.
973
+
The idea is that the code throws the exception and if Pulsar does not receive an ack within 1 millisecond, it does a retry.
974
+
If that cycle continues for 10 times, (as that is our max redelivery count in the `DeadLetterPolicy`), then Pulsar consumer publishes the messages to the DQL topic.
975
+
We have another `PulsarListener` that is listening on the DLQ topic to receive data as it is published to the DLQ topic.
976
+
977
+
**Special note on DLQ topics when using partitioned topics**: If the main topic is partitioned, then behind the scenes, each partition is treated as a separate topic by Pulsar.
978
+
Pulsar appends `partition-<n>` where `n` stands for the partition number to the main topic name.
979
+
The problem is that, if you do not specify a DLQ topic (as opposed to what we did above), then Pulsar will publish to a default topic name that has this ``partition-<n>` info in it - for ex: `topic-with-dlp-partition-0-deadLetterPolicySubscription-DLQ`.
980
+
The easy way to solve this is to provide a DLQ topic name always.
981
+
982
+
===== Native Error Handling in Spring for Apache Pulsar
983
+
984
+
As we have noted above, the DLQ feature in Apache Pulsar only works for shared subscriptions.
985
+
What does an application do if they need to use some similar feature for non-shared subscriptions?
986
+
The main reason why Pulsar does not support DLQ on exclusive and failover subscriptions, is because those subscription types are order-guaranteed.
987
+
By allowing redeliveries, DLQ etc. it effectively receives messages in out-of-order.
988
+
But, what if some applications are okay with that, but more importantly needs this DLQ feature for non-shared subscriptions?
989
+
For that, Spring for Apache Pulsar provides a `PulsarConsumerErrorHandler` which can be used across any subscription types in Pulsar - `Exclusive`, `Failover`, `Shared`, `Key_Shared`.
990
+
991
+
When using `PulsarConsumerErrorHandler` from Spring for Apache Pulsar, make sure not to set the ack timeout properties on the listener.
992
+
993
+
Let us see some details by examining a few code snippets.
994
+
995
+
====
996
+
[source, java]
997
+
----
998
+
@EnablePulsar
999
+
@Configuration
1000
+
static class PulsarConsumerErrorHandlerConfig {
1001
+
1002
+
@Bean
1003
+
public PulsarConsumerErrorHandler<String> pulsarConsumerErrorHandler(
1004
+
PulsarTemplate<String> pulsarTemplate) {
1005
+
return new DefaultPulsarConsumerErrorHandler<>(
1006
+
new PulsarDeadLetterPublishingRecoverer<>(pulsarTemplate, (c, m) -> "my-foo-dlt"), new FixedBackOff(100, 10));
Let us take a look at the `pulsarConsumerErrorHandler` bean provided.
1026
+
This creates a bean of type `PulsarConsumerErrorHandler` and uses the default implementation provided out of the box by Spring for Apache Pulsar - `DefaultPulsarConsumerErrorHandler`.
1027
+
`DefaultPulsarConsumerErrorHandler` has a constructor that takes a `PulsarMessageRecovererFactory` and a `org.springframework.util.backoff.Backoff`.
1028
+
`PulsarMessageRecovererFactory` is a functional interface with the following API:
1029
+
1030
+
====
1031
+
[source, java]
1032
+
----
1033
+
@FunctionalInterface
1034
+
public interface PulsarMessageRecovererFactory<T> {
1035
+
1036
+
/**
1037
+
* Provides a message recoverer {@link PulsarMessageRecoverer}.
Spring for Apache Pulsar provides an implementation for `PulsarMessageRecovererFactory` called `PulsarDeadLetterPublishingRecoverer` that provides a default implementation that is capable of recovering the message by sending it to a DLT - (Dead Letter Topic).
1068
+
This is the implementation that we are providing to the constructor for `DefaultPulsarConsumerErrorHandler` above.
1069
+
As the second argument, we are providing a `FixedBackOff`.
1070
+
You can also provide the `ExponentialBackoff` from Spring for advanced backoff features.
1071
+
Then we provide this bean name for the `PulsarConsumerErrorHandler` as a property to the `PulsarListener`.
1072
+
The property is called `pulsarConsumerErrorHandler`.
1073
+
Each time the `PulsarListener` method fails for a message, it gets retried.
1074
+
The number of retries are controlled by the `Backoff` implementation values provided - in our example, we do 10 retries - 11 total tries all in all - the first one and then the 10 retries.
1075
+
Once all the retries are exhausted, the message is sent to the DLT topic.
1076
+
1077
+
The `PulsarDeadLetterPublishingRecoverer` implementation we provide use a `PulsarTemplate` that is uses for publishing the message to the DLT.
1078
+
In most cases, the same auto-configured `PulsarTemplate` from Spring Boot is sufficient with the caveat for partitioned topics.
1079
+
When using partitioned topics and using custom message routing for the main topic, you must use a different `PulsarTemplate` that does not take the autoconfigured `PulsarProducerFactory` that is populated with a value of `custompartition` for `message-routing-mode`.
1080
+
Towards this extent, you can use a `PulsarConsumerErrorHandler` with the following blueprint.
1081
+
1082
+
====
1083
+
[source, java]
1084
+
----
1085
+
@Bean
1086
+
public PulsarConsumerErrorHandler<Integer> pulsarConsumerErrorHandler(PulsarClient pulsarClient) {
1087
+
PulsarProducerFactory<Integer> pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, Map.of());
1088
+
PulsarTemplate<Integer> pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory);
final PulsarDeadLetterPublishingRecoverer<Integer> pulsarDeadLetterPublishingRecoverer =
1094
+
new PulsarDeadLetterPublishingRecoverer<>(pulsarTemplate, destinationResolver);
1095
+
1096
+
return new DefaultPulsarConsumerErrorHandler<>(pulsarDeadLetterPublishingRecoverer,
1097
+
new FixedBackOff(100, 5));
1098
+
}
1099
+
----
1100
+
====
1101
+
1102
+
Note that, we are providing a destination resolver to the `PulsarDeadLetterPublishingRecoverer` as the second constructor argument.
1103
+
If not provided, `PulsarDeadLetterPublishingRecoverer` will use `<subscription-name>-<topic-name>-DLT>` as the DLT topic name.
1104
+
When using this feature, it is recommended to use a properr destination name by setting the destination resolver rather than using the default.
1105
+
1106
+
When using a single record message listener as we did above with `PulsarConsumerErrorHnadler` and if you are using manual acknowledgement, make sure not to negatively acknowledge the message when an exception is thrown.
1107
+
Rather, just simply rethrow the exception back to the container; otherwise, the container thinks that the message is handled separately and the error handling will not be triggered.
1108
+
1109
+
Finally, we have a second `PulsarListener` above that is receiving messages from the DLT topic.
1110
+
1111
+
In the examples provided in this section so far, we only saw how to use `PulsarConsumerErrorHandler` with a single record message listener.
1112
+
Next, we will look how can use this on batch listeners.
1113
+
1114
+
**Batch listener with PulsarConsumerErrorHandler**
1115
+
1116
+
First, let us look at a batch `PulsarListener` method.
Once again, we re providing the property `pulsarConsumerErrorHandler` with the `PulsarConsumerErrorHandler` bean name.
1149
+
When you are using a batch listener as above and want to use the `PulsarConsumerErrorHandler` from Spring for Apache Pulsar, then you need to use manual acknowledgment
1150
+
This way you can acknowledge all the successful individual messages.
1151
+
For the ones that fail, you must throw a `PulsarBatchListenerFailedException` with the message that it fails on.
1152
+
Without this exception, the framework will not know what to do with the failure.
1153
+
On retry, the container will send a new batch of messages, starting with the failed message to the listener.
1154
+
If it fails again, it is retried, until the retries are exhausted, at which point the message will be sent to the DLT.
1155
+
At that point, the message is acknowledged by the container and the listener will be handed over with the subsequent messages in the original batch.
0 commit comments