【九】kafka延迟队列、重试队列、死信队列

本文探讨Kafka如何实现延时队列与重试队列,通过内部主题与自定义服务,实现消息的延时投递及重试机制,确保消息处理的可靠性和准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、延迟队列

实现方案:

在发送延时消息的时候并不是先投递到要发送的真实主题(real_topic)中,而是先投递到一些 Kafka 内部的主题(delay_topic)中,这些内部主题对用户不可见,

然后通过一个自定义的服务拉取这些内部主题中的消息,并将满足条件的消息再投递到要发送的真实的主题中,消费者所订阅的还是真实的主题。

如果采用这种方案,那么一般是按照不同的延时等级来划分的,比如设定5s、10s、30s、1min、2min、5min、10min、20min、30min、45min、1hour、2hour这些按延时时间递增的延时等级,延时的消息按照延时时间投递到不同等级的主题中,投递到同一主题中的消息的延时时间会被强转为与此主题延时等级一致的延时时间,这样延时误差控制在两个延时等级的时间差范围之内(比如延时时间为17s的消息投递到30s的延时主题中,之后按照延时时间为30s进行计算,延时误差为13s)。虽然有一定的延时误差,但是误差可控,并且这样只需增加少许的主题就能实现延时队列的功能。

发送到内部主题(delaytopic*)中的消息会被一个独立的 DelayService 进程消费,这个 DelayService 进程和 Kafka broker 进程以一对一的配比进行同机部署(参考下图),以保证服务的可用性。

针对不同延时级别的主题,在 DelayService 的内部都会有单独的线程来进行消息的拉取,以及单独的 DelayQueue(这里用的是 JUC 中 DelayQueue)进行消息的暂存。与此同时,在 DelayService 内部还会有专门的消息发送线程来获取 DelayQueue 的消息并转发到真实的主题中。从消费、暂存再到转发,线程之间都是一一对应的关系。如下图所示,DelayService 的设计应当尽量保持简单,避免锁机制产生的隐患。

为了保障内部 DelayQueue 不会因为未处理的消息过多而导致内存的占用过大,DelayService 会对主题中的每个分区进行计数,当达到一定的阈值之后,就会暂停拉取该分区中的消息。

因为一个主题中一般不止一个分区,分区之间的消息并不会按照投递时间进行排序,DelayQueue的作用是将消息按照再次投递时间进行有序排序,这样下游的消息发送线程就能够按照先后顺序获取最先满足投递条件的消息。

 二、重试队列和死信队列

死信可以看作消费者不能处理收到的消息,也可以看作消费者不想处理收到的消息,还可以看作不符合处理要求的消息。比如消息内包含的消息内容无法被消费者解析,为了确保消息的可靠性而不被随意丢弃,故将其投递到死信队列中,这里的死信就可以看作消费者不能处理的消息。再比如超过既定的重试次数之后将消息投入死信队列,这里就可以将死信看作不符合处理要求的消息。

重试队列其实可以看作一种回退队列,具体指消费端消费消息失败时,为了防止消息无故丢失而重新将消息回滚到 broker 中。与回退队列不同的是,重试队列一般分成多个重试等级,每个重试等级一般也会设置重新投递延时,重试次数越多投递延时就越大。

理解了他们的概念之后我们就可以为每个主题设置重试队列,消息第一次消费失败入重试队列 Q1,Q1 的重新投递延时为5s,5s过后重新投递该消息;如果消息再次消费失败则入重试队列 Q2,Q2 的重新投递延时为10s,10s过后再次投递该消息。

然后再设置一个主题作为死信队列,重试越多次重新投递的时间就越久,并且需要设置一个上限,超过投递次数就进入死信队列。重试队列与延时队列有相同的地方,都需要设置延时级别。

 

参考https://www.luozhiyun.com/archives/58

### 实现死信队列的方法 在 Kafka 中,死信队列(Dead Letter Queue, DLQ)用于存储多次消费失败的消息,以防止这些消息无限次重试并影响系统稳定性。实现死信队列的关键在于定义消息进入死信队列的条件,并将这些消息发送到特定的主题中。以下是一个典型的实现方法: 1. **定义重试策略**:为消息设置最大重试次数,当消息消费失败次数超过该阈值时,将其发送到死信队列。 2. **配置死信队列主题**:为死信队列指定一个独立的 Kafka 主题,用于存储失败的消息。 3. **消息处理逻辑**:在消费,当消息消费失败时,记录失败原因,并将消息发送到死信队列主题。 ```java @KafkaListener(topics = "input-topic") public void processMessage(ConsumerRecord<String, String> record, Acknowledgment acknowledgment) { try { // 消息处理逻辑 handleMessage(record.value()); acknowledgment.acknowledge(); } catch (Exception e) { // 记录失败次数 int retryCount = getRetryCount(record); if (retryCount >= MAX_RETRY_ATTEMPTS) { // 发送到死信队列 sendToDeadLetterQueue(record); } else { // 重新入队或进入重试队列 retryMessage(record, retryCount + 1); } acknowledgment.acknowledge(); } } ``` 通过这种方式,Kafka 能够有效地管理消费失败的消息,确保系统的健壮性[^2]。 ### 实现延迟队列的方法 延迟队列(Delay Queue)用于在特定时间之后重新投递消息Kafka 本身不直接支持延迟队列,但可以通过一些策略来模拟这一功能。常见的实现方法包括: 1. **使用时间戳和轮询机制**:在消息中添加时间戳,消费者定期检查消息是否满足投递条件。 2. **利用 Kafka 的时间索引**:Kafka 提供了基于时间戳的索引功能,可以用来实现延迟队列。 3. **使用外部存储**:将需要延迟的消息存储在外部存储(如 Redis 或数据库)中,并在指定时间后将其重新发送到 Kafka。 ```java // 示例:使用时间戳和轮询机制实现延迟队列 @KafkaListener(topics = "delay-topic") public void processDelayMessage(ConsumerRecord<String, String> record, Acknowledgment acknowledgment) { long delayUntil = extractDelayTimestamp(record); long currentTime = System.currentTimeMillis(); if (currentTime >= delayUntil) { // 处理消息 handleMessage(record.value()); acknowledgment.acknowledge(); } else { // 重新入队,等待下一次处理 retryWithDelay(record, delayUntil - currentTime); acknowledgment.acknowledge(); } } ``` 通过上述方法,可以有效地实现延迟队列,确保消息在指定时间后被处理[^4]。 ### 死信队列延迟队列的联系区别 死信队列延迟队列Kafka 中都用于处理消息失败的情况,但它们的作用和实现方式有所不同: - **死信队列**:主要用于存储多次消费失败的消息,防止系统因无限次重试而崩溃。死信队列通常由消费重试逻辑触发,当消息的失败次数超过设定的阈值时,消息会被发送到死信队列[^2]。 - **延迟队列**:用于在特定时间之后重新投递消息延迟队列通常由内部触发,例如通过时间戳或外部存储来控制消息的投递时间。延迟队列的作用是一次性的,而重试队列的作用范围会向后传递[^4]。 两者的结合可以提供更强大的消息处理能力,确保消息在失败时能够被妥善处理,同时避免消息丢失或重复处理的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值