目录
消费者(consumer)是通过订阅附加到一个消息主题,然后接收消息的程序。// 订阅+主题
消费者向代理发送许可请求获取消息。消费者端有一个队列,用于接收从代理推送的消息。可以使用 receiverQueueSize 参数配置队列的大小(默认大小为1000)。每调用一次 receive() 方法,会从缓存队列中出队一条消息。
1、消息的两种接收方式
从代理(brokers)接收消息可以是同步的也可以是异步的:
Mode | Description |
---|---|
Sync receive // 同步 | A sync receive is blocked until a message is available. |
Async receive // 异步 | An async receive returns immediately with a future value—for example, a CompletableFuture in Java—that completes once a new message is available. // 异步接收立即返回一个future值,例如,Java中的CompletableFuture,一旦有新消息可用,它就会完成。 |
2、消息监听器
客户端为消费者提供侦听器的实现。例如,Java 客户端提供 MessageListener 接口。在此接口中,每当接收到新消息时,都会调用 received() 方法。
3、消息的确认
消费者在成功消费消息后会向代理(broker)发送确认请求。消息(message )将被永久存储,注意,只有在所有订阅者都确认后才会被删除。如果要存储消费者已消费的消息,则需要配置消息的保留策略。
对于批量处理的消息,可以启用批量索引确认机制,以避免将已确认的消息重复发送给消费者。有关批量索引确认的详细信息,请参阅批量处理文档。
消费者可以通过以下两种方式之一确认消息:
- 单独确认。消费者每消费一条消息,都会向代理发送确认请求。
- 累计确认。消费者只确认其收到的最后一条消息。确认后,Stream 流中所有的消息都不会重新传递给该消费者。
单独确认消息,可以使用下边 API
consumer.acknowledge(msg);
累计确认消息,可以使用下边 API:
consumer.acknowledgeCumulative(msg);
注意事项
累积确认不能用于共享订阅类型( Shared subscription type),因为共享订阅类型涉及多个可以访问同一订阅的消费者。在共享订阅类型中,消息是单独确认的。
4、消息的否定确认
否定确认机制允许消费者向代理发送通知,指示消费者未处理消息。当消费者未能处理消息并需要重新处理它时,消费者会向代理(broker)发送否定确认(nack),触发代理将此消息重新传递给消费者。
消费者也可以单独的或累积的对消息进行否定确认,这取决于消费的订阅模式:
- 在 Exclusive(独占)和 Failover subscription(容错订阅)模式中,消费者只会对他们收到的最后一条消息进行否定确认。
- 在 Shared 和 Key_Shared 订阅模式中,消费者可以单独的对每一条消息进行否定确认。
需要注意的是,对有排序的订阅模式(如 Exclusive、Failover 和 Key_Shared)的否定确认可能会导致重新发送的消息超出它原来的顺序。
如果要对消息使用否定确认,请确保在超时确认之前对其进行否定确认:// 虽然都会触发消息重传,但超时确认后,否定确认就无意义了
Consumer<byte[]> consumer = pulsarClient.newConsumer()
.topic(topic)
.subscriptionName("sub-negative-ack")
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
.negativeAckRedeliveryDelay(2, TimeUnit.SECONDS) // the default value is 1 min
.subscribe();
Message<byte[]> message = consumer.receive(); // 消费消息
// call the API to send negative acknowledgement,在超时之前进行否定确认
consumer.negativeAcknowledge(message); // 否定确认
message = consumer.receive();
consumer.acknowledge(message);
可以通过设置传递消息的重试次数来设置消息重传的不同时延:
Use the following API to enable Negative Redelivery Backoff
.
Consumer<byte[]> consumer = pulsarClient.newConsumer()
.topic(topic)
.subscriptionName("sub-negative-ack")
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
.negativeAckRedeliveryBackoff(MultiplierRedeliveryBackoff.builder()
.minDelayMs(1000)
.maxDelayMs(60 * 1000)
.build())
.subscribe();
消息重传的时延如下所示:// 时延会随着重传次数的增加而增加
Redelivery count | Redelivery delay |
---|---|
1 | 10 + 1 seconds // 11s |
2 | 10 + 2 seconds // 12s |
3 | 10 + 4 seconds |
4 | 10 + 8 seconds |
5 | 10 + 16 seconds |
6 | 10 + 32 seconds |
7 | 10 + 60 seconds |
8 | 10 + 60 seconds |
注意事项
如果启用了批量处理,则同一批量中的所有消息都会重新传递给消费者。
5、消息的超时确认
超时确认机制可以为客户端跟踪未确认消息设置一个时间范围。如果客户端在规定(ackTimeout)时间内没有发送确认消息,那么客户端会向代理(broker)发送否定确认请求,触发 broker 重传。
如果客户端在规定时间内不发送确认消息,或者使用定时任务在超时时间内去检查超时确认的消息,都可以通过配置超时确认机制来触发消息重传。
超时确认机制可以通过设置重试次数来实现重传消息的不同时延。
如果要使用重传机制,可以使用以下API:
consumer.ackTimeout(10, TimeUnit.SECOND)
.ackTimeoutRedeliveryBackoff(MultiplierRedeliveryBackoff.builder() // 启用消息重传
.minDelayMs(1000)
.maxDelayMs(60000)
.multiplier(2).build())
消息重传的时延如下所示:// 跟否定确认的消息重传时延是一样的,同一个机制
Redelivery count | Redelivery delay |
---|---|
1 | 10 + 1 seconds |
2 | 10 + 2 seconds |
3 | 10 + 4 seconds |
4 | 10 + 8 seconds |
5 | 10 + 16 seconds |
6 | 10 + 32 seconds |
7 | 10 + 60 seconds |
8 | 10 + 60 seconds |
注意事项
与超时确认相比,推荐优先选择确认否定。首先,超时时间很难确定,其次,当消息处理时间超过规定的时间时,超时确认会通知代理会重新发送消息,但这些消息可能并不需要重新被消费。
Use the following API to enable acknowledgement timeout(确认超时).
Consumer<byte[]> consumer = pulsarClient.newConsumer()
.topic(topic)
.ackTimeout(2, TimeUnit.SECONDS) // the default value is 0
.ackTimeoutTickTime(1, TimeUnit.SECONDS)
.subscriptionName("sub")
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
.subscribe();
Message<byte[]> message = consumer.receive();
// wait at least 2 seconds
message = consumer.receive();
consumer.acknowledge(message);
6、重试消息主题( retry letter topic)
“retry letter” 主题,会存储消费失败的消息,并在之后尝试重新消费。通过这个方式,可以自定义消息重传的时间间隔。订阅原始主题的消费者也会自动订阅 “retry letter” 主题。一旦 “retry letter” 主题中的消息达到最大重传次数,该消息将会被移动到死亡消息主题中,死亡消息主题中的消息需要手动进行处理。
下图说明了重试消息主题的概念:
使用 “retry letter” 主题的目的与使用消息延迟传递的目的不同,尽管两者都传递消息。“retry letter” 主题在消息消费失败后,通过消息重传机制,确保关键数据不会丢失,而延迟消息传递是为了在指定的延迟时间传递消息。
默认情况下,Pulsar 会禁用自动重传。你可以将 enableRetry 设置为 true,对消费者启用自动重传。
使用以下 API 从重传消息主题中消费消息。当消息重传次数达到最大重传次数(maxRedeliverCount
)后,该消息会被移动到死亡消息主题中:
Consumer<byte[]> consumer = pulsarClient.newConsumer(Schema.BYTES)
.topic("my-topic")
.subscriptionName("my-subscription")
.subscriptionType(SubscriptionType.Shared)
.enableRetry(true) // 开启自动重试
.deadLetterPolicy(DeadLetterPolicy.builder()
.maxRedeliverCount(maxRedeliveryCount)
.build())
.subscribe();
“retry letter” 主题默认使用以下格式:
// 主题-订阅名称-RETRY
<topicname>-<subscriptionname>-RETRY
也可以使用 Java client 指定自己的 “retry letter” 主题名称:
Consumer<byte[]> consumer = pulsarClient.newConsumer(Schema.BYTES)
.topic("my-topic")
.subscriptionName("my-subscription")
.subscriptionType(SubscriptionType.Shared)
.enableRetry(true)
.deadLetterPolicy(DeadLetterPolicy.builder()
.maxRedeliverCount(maxRedeliveryCount)
.retryLetterTopic("my-retry-letter-topic-name") //重试主题
.build())
.subscribe();
“retry letter” 主题中的消息包含一些由客户端自动创建的特殊属性:
Special property | Description |
---|---|
| The real topic name. |
| The origin message ID. It is crucial for message tracking. // 对于消息跟踪非常关键 |
| The number of retries to consume messages. |
| Message retry interval in milliseconds. // 消息重试间隔(毫秒) |
例如:
REAL_TOPIC = persistent://public/default/my-topic
ORIGIN_MESSAGE_ID = 1:0:-1:0
RECONSUMETIMES = 6
DELAY_TIME = 3000
使用以下 API 将消息存储在重传队列中:
consumer.reconsumeLater(msg, 3, TimeUnit.SECONDS);
使用以下 API 为 Reconsumerater 函数添加自定义属性。在下一次尝试消费时,可以通过 message#getProperty 获取自定义属性。
Map<String, String> customProperties = new HashMap<String, String>();
customProperties.put("custom-key-1", "custom-value-1");
customProperties.put("custom-key-2", "custom-value-2");
consumer.reconsumeLater(msg, customProperties, 3, TimeUnit.SECONDS);
注意事项
当前,在共享订阅类型中启用了 “retry letter” 主题。
与否定确认相比,“retry letter” 主题更适合配置了重试时间间隔且需要进行大量消息重传的场景。因为 “retry letter” 主题中的消息持久化在 BookKeeper 中(broker中),而否定确认后需要重传的消息缓存在客户端。// 两者存储的方式不一样
7、死亡消息主题(dead letter topic)
死亡消息主题(DLQ)允许继续使用消费未成功的消息。存储无法被消费的消息的主题,称为死亡消息主题(dead letter topic)。你可以决定如何处理死亡消息主题中的消息。
在 Java 客户端中启用默认的死亡消息主题。
Consumer<byte[]> consumer = pulsarClient.newConsumer(Schema.BYTES)
.topic("my-topic")
.subscriptionName("my-subscription")
.subscriptionType(SubscriptionType.Shared)
.deadLetterPolicy(DeadLetterPolicy.builder() // 启用死信主题
.maxRedeliverCount(maxRedeliveryCount)
.build())
.subscribe();
死亡消息主题的默认格式:
// 主题-订阅名称-DLQ
<topicname>-<subscriptionname>-DLQ
你也可以使用 Java client 自定义死亡消息主题的名字:
Consumer<byte[]> consumer = pulsarClient.newConsumer(Schema.BYTES)
.topic("my-topic")
.subscriptionName("my-subscription")
.subscriptionType(SubscriptionType.Shared)
.deadLetterPolicy(DeadLetterPolicy.builder()
.maxRedeliverCount(maxRedeliveryCount)
.deadLetterTopic("my-dead-letter-topic-name") // 定义死信主题的名字
.build())
.subscribe();
默认情况下,死亡消息主题不会被订阅。如果不能实时订阅死亡消息主题,那么可能会丢失这些消息。如果需要自动创建 DLQ 主题的初始化订阅,可以指定 initialSubscriptionName 参数。但是如果设置了此参数,代理的 allowAutoSubscriptionCreation 被禁用, DLQ 的生产者也会创建失败。
Consumer<byte[]> consumer = pulsarClient.newConsumer(Schema.BYTES)
.topic("my-topic")
.subscriptionName("my-subscription")
.subscriptionType(SubscriptionType.Shared)
.deadLetterPolicy(DeadLetterPolicy.builder()
.maxRedeliverCount(maxRedeliveryCount)
.deadLetterTopic("my-dead-letter-topic-name")
.initialSubscriptionName("init-sub") // 创建自动订阅
.build())
.subscribe();
DLQ 主题的消息可以进行重传,重传机制由超时确认、否定确认、重试消息主题等触发。
注意事项
目前,已经在 Shared 和 Key_Shared 订阅类型中启用死亡消息主题。