☠️ 死信队列的作用和处理策略:消息的"急诊室"!

83 阅读12分钟

📖 开场:医院的急诊室

想象你去医院看病 🏥:

普通门诊(正常队列)

患者排队 → 医生诊治 → 治疗成功 → 出院 ✅

急诊室(死信队列)

患者病情复杂 → 普通门诊治不了 → 转到急诊室 🚑
    ↓
专家会诊 → 特殊处理 → 最终治愈(或转院)

这就是死信队列(Dead Letter Queue, DLQ)!

定义:无法正常处理的消息,会被转移到死信队列,进行特殊处理


🤔 什么是死信(Dead Letter)?

死信的定义

死信 = 无法被正常消费的消息 ☠️

三种情况会产生死信

1️⃣ 消息被拒绝(Reject/Nack)

Consumer拉取消息 → 处理失败 → 拒绝消息 ❌
    ↓
消息变成死信 ☠️

场景

  • 消息格式错误(JSON解析失败)
  • 业务逻辑无法处理
  • 数据校验失败

2️⃣ 消息过期(TTL超时)

消息在队列中等待 → 超过TTL时间 → 过期 ⏰
    ↓
消息变成死信 ☠️

场景

  • 消息设置了TTL(生存时间)
  • Consumer来不及消费
  • 延迟消息场景

3️⃣ 队列满了(Queue Full)

队列已满 → 新消息进不来 → 被拒绝 🚫
    ↓
消息变成死信 ☠️

场景

  • 队列设置了最大长度
  • 积压严重
  • 内存不足

🎯 死信队列的作用

1️⃣ 消息兜底处理 🛡️

没有死信队列

消息处理失败 → 直接丢弃 💀
    ↓
数据永久丢失!😱

有死信队列

消息处理失败 → 进入死信队列 ☠️
    ↓
人工处理 → 重新发送 → 最终成功 ✅

2️⃣ 问题排查 🔍

作用

  • 保留失败的消息
  • 分析失败原因
  • 发现系统问题

示例

死信队列积累了1000条消息 ☠️☠️☠️
    ↓
分析发现:都是订单金额为负数的消息
    ↓
发现Bug:订单金额校验有问题 🐛
    ↓
修复Bug → 重新处理死信消息 ✅

3️⃣ 业务降级 📉

场景:第三方服务故障

调用第三方API失败 → 消息进死信队列
    ↓
第三方恢复后 → 批量重新处理死信消息 ✅

4️⃣ 限流保护 🚦

场景:防止雪崩

Consumer处理过慢 → 消息积压 → 队列满
    ↓
新消息进死信队列(而不是压垮系统)✅

🔧 死信队列的实现

RabbitMQ的死信队列

架构设计

Producer
    
┌─────────────────────────────────────────┐
         普通交换机                       
      (Normal Exchange)                  
└─────────────┬───────────────────────────┘
              
              
┌─────────────────────────────────────────┐
          普通队列                        
       (Normal Queue)                    
                                         
  设置:                                  
  - x-dead-letter-exchange: dlx         
  - x-dead-letter-routing-key: dlq.key  
  - x-message-ttl: 60000 (可选)         
  - x-max-length: 10000 (可选)          
└─────────────┬───────────────────────────┘
              
               消息变成死信:
               1. 被拒绝(requeue=false)
               2. TTL过期
               3. 队列满
              
┌─────────────────────────────────────────┐
       死信交换机                         
   (Dead Letter Exchange)                
└─────────────┬───────────────────────────┘
              
              
┌─────────────────────────────────────────┐
         死信队列                         
      (Dead Letter Queue)                
└─────────────┬───────────────────────────┘
              
              
        Dead Letter Consumer
      (专门处理死信的消费者)

配置代码

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DeadLetterQueueConfig {
    
    // ========== 普通队列配置 ==========
    
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable("normal.queue")
            // ⭐ 配置死信交换机
            .withArgument("x-dead-letter-exchange", "dlx.exchange")
            // ⭐ 配置死信路由键
            .withArgument("x-dead-letter-routing-key", "dlq.routing.key")
            // ⭐ 可选:设置TTL(60秒)
            .withArgument("x-message-ttl", 60000)
            // ⭐ 可选:设置队列最大长度
            .withArgument("x-max-length", 10000)
            .build();
    }
    
    @Bean
    public DirectExchange normalExchange() {
        return new DirectExchange("normal.exchange");
    }
    
    @Bean
    public Binding normalBinding(Queue normalQueue, DirectExchange normalExchange) {
        return BindingBuilder.bind(normalQueue)
            .to(normalExchange)
            .with("normal.routing.key");
    }
    
    // ========== 死信队列配置 ==========
    
    @Bean
    public Queue deadLetterQueue() {
        return new Queue("dead.letter.queue", true);
    }
    
    @Bean
    public DirectExchange deadLetterExchange() {
        return new DirectExchange("dlx.exchange");
    }
    
    @Bean
    public Binding deadLetterBinding(Queue deadLetterQueue, DirectExchange deadLetterExchange) {
        return BindingBuilder.bind(deadLetterQueue)
            .to(deadLetterExchange)
            .with("dlq.routing.key");
    }
}

生产者

@Service
public class MessageProducer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 发送普通消息
     */
    public void sendMessage(String message) {
        System.out.println("发送消息: " + message);
        
        rabbitTemplate.convertAndSend(
            "normal.exchange",
            "normal.routing.key",
            message
        );
    }
    
    /**
     * 发送带TTL的消息
     */
    public void sendMessageWithTTL(String message, long ttl) {
        System.out.println("发送消息(TTL=" + ttl + "ms): " + message);
        
        rabbitTemplate.convertAndSend(
            "normal.exchange",
            "normal.routing.key",
            message,
            msg -> {
                // ⭐ 设置消息TTL
                msg.getMessageProperties().setExpiration(String.valueOf(ttl));
                return msg;
            }
        );
    }
}

普通消费者(会拒绝消息)

@Component
public class NormalConsumer {
    
    /**
     * 消费普通队列
     */
    @RabbitListener(queues = "normal.queue")
    public void consumeMessage(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        
        System.out.println("收到消息: " + msg);
        
        try {
            // 业务处理
            processMessage(msg);
            
            // ⭐ 处理成功,确认
            channel.basicAck(deliveryTag, false);
            System.out.println("✅ 消息处理成功");
            
        } catch (Exception e) {
            System.err.println("❌ 消息处理失败: " + e.getMessage());
            
            // ⭐ 拒绝消息,不重新入队(进入死信队列)
            channel.basicNack(deliveryTag, false, false);
            //                                      ↑
            //                            requeue=false(不重新入队)
        }
    }
    
    private void processMessage(String msg) throws Exception {
        // 模拟处理逻辑
        if (msg.contains("error")) {
            throw new Exception("消息格式错误");
        }
        
        // 正常处理...
    }
}

死信消费者(处理死信)

@Component
@Slf4j
public class DeadLetterConsumer {
    
    @Autowired
    private MessageProducer producer;
    
    /**
     * 消费死信队列
     */
    @RabbitListener(queues = "dead.letter.queue")
    public void handleDeadLetter(Message message) {
        String msg = new String(message.getBody());
        
        // 获取死信原因
        String reason = getDeadLetterReason(message);
        
        log.warn("☠️ 收到死信消息: {}, 原因: {}", msg, reason);
        
        // ⭐ 根据不同原因,采取不同策略
        switch (reason) {
            case "rejected":
                // 被拒绝:记录日志,人工处理
                handleRejectedMessage(msg, message);
                break;
                
            case "expired":
                // 过期:可能需要重新发送
                handleExpiredMessage(msg);
                break;
                
            case "maxlen":
                // 队列满:等待后重试
                handleQueueFullMessage(msg);
                break;
                
            default:
                log.error("未知的死信原因: {}", reason);
        }
    }
    
    /**
     * 获取死信原因
     */
    private String getDeadLetterReason(Message message) {
        MessageProperties props = message.getMessageProperties();
        
        // 从x-death头获取死信信息
        List<Map<String, ?>> xDeath = props.getXDeathHeader();
        if (xDeath != null && !xDeath.isEmpty()) {
            Map<String, ?> death = xDeath.get(0);
            return (String) death.get("reason");
        }
        
        return "unknown";
    }
    
    /**
     * 处理被拒绝的消息
     */
    private void handleRejectedMessage(String msg, Message message) {
        log.info("处理被拒绝的消息: {}", msg);
        
        // 策略1:记录到数据库,人工处理
        saveToDatabase(msg, message);
        
        // 策略2:发送告警
        sendAlert("消息被拒绝: " + msg);
        
        // 策略3:如果可以修复,重新发送
        if (canRetry(msg)) {
            String fixedMsg = fixMessage(msg);
            producer.sendMessage(fixedMsg);
            log.info("消息已修复并重新发送: {}", fixedMsg);
        }
    }
    
    /**
     * 处理过期的消息
     */
    private void handleExpiredMessage(String msg) {
        log.info("处理过期的消息: {}", msg);
        
        // 策略1:判断是否还需要处理
        if (isStillValid(msg)) {
            // 重新发送
            producer.sendMessage(msg);
            log.info("消息仍然有效,重新发送: {}", msg);
        } else {
            log.info("消息已过期且无效,丢弃: {}", msg);
        }
    }
    
    /**
     * 处理队列满的消息
     */
    private void handleQueueFullMessage(String msg) {
        log.info("处理队列满的消息: {}", msg);
        
        // 策略:等待一段时间后重试
        try {
            Thread.sleep(5000);  // 等待5秒
            producer.sendMessage(msg);
            log.info("重新发送消息: {}", msg);
        } catch (InterruptedException e) {
            log.error("等待被中断", e);
        }
    }
    
    // 辅助方法(示意)
    private void saveToDatabase(String msg, Message message) {
        // 保存到数据库
    }
    
    private void sendAlert(String alert) {
        // 发送告警
    }
    
    private boolean canRetry(String msg) {
        return !msg.contains("fatal");
    }
    
    private String fixMessage(String msg) {
        return msg.replace("error", "fixed");
    }
    
    private boolean isStillValid(String msg) {
        return true;  // 判断逻辑
    }
}

RocketMQ的死信队列

特点

RocketMQ会自动创建死信队列

普通Topic: OrderTopic
    ↓
消息消费失败,重试16次后
    ↓
自动进入死信Topic: %DLQ%ConsumerGroupName

死信Topic命名规则

%DLQ% + 消费者组名

例如:
消费者组:order-consumer-group
死信Topic:%DLQ%order-consumer-group

消费者代码

@Component
@Slf4j
public class OrderConsumer {
    
    /**
     * 消费普通消息
     */
    @RocketMQMessageListener(
        topic = "OrderTopic",
        consumerGroup = "order-consumer-group"
    )
    public class OrderMessageListener implements RocketMQListener<String> {
        
        @Override
        public void onMessage(String message) {
            log.info("收到消息: {}", message);
            
            try {
                // 业务处理
                processOrder(message);
                log.info("✅ 消息处理成功");
                
            } catch (Exception e) {
                log.error("❌ 消息处理失败", e);
                // ⭐ 抛异常,RocketMQ会自动重试
                // 重试16次后,自动进入死信队列
                throw new RuntimeException("处理失败", e);
            }
        }
        
        private void processOrder(String message) throws Exception {
            // 处理逻辑
            if (message.contains("error")) {
                throw new Exception("订单格式错误");
            }
        }
    }
}

死信消费者

@Component
@Slf4j
public class DeadLetterConsumer {
    
    @Autowired
    private OrderService orderService;
    
    /**
     * 消费死信队列
     * Topic名称:%DLQ% + 消费者组名
     */
    @RocketMQMessageListener(
        topic = "%DLQ%order-consumer-group",
        consumerGroup = "dlq-order-consumer-group"
    )
    public class DLQMessageListener implements RocketMQListener<MessageExt> {
        
        @Override
        public void onMessage(MessageExt message) {
            String body = new String(message.getBody());
            
            log.warn("☠️ 收到死信消息: msgId={}, body={}", 
                message.getMsgId(), body);
            
            // 获取重试次数
            int reconsumeTimes = message.getReconsumeTimes();
            log.info("重试次数: {}", reconsumeTimes);
            
            // ⭐ 处理死信消息
            handleDeadLetter(body, message);
        }
        
        private void handleDeadLetter(String body, MessageExt message) {
            // 策略1:保存到数据库,人工处理
            DeadLetterRecord record = new DeadLetterRecord();
            record.setMsgId(message.getMsgId());
            record.setBody(body);
            record.setTopic(message.getTopic());
            record.setRetryTimes(message.getReconsumeTimes());
            record.setCreateTime(new Date());
            
            deadLetterRepository.save(record);
            log.info("死信消息已保存到数据库");
            
            // 策略2:发送告警
            alertService.send("死信队列有新消息: " + message.getMsgId());
            
            // 策略3:尝试修复并重新发送
            if (canFix(body)) {
                String fixedBody = fix(body);
                orderService.republishMessage(fixedBody);
                log.info("消息已修复并重新发送");
            }
        }
        
        private boolean canFix(String body) {
            // 判断是否可以修复
            return !body.contains("fatal");
        }
        
        private String fix(String body) {
            // 修复逻辑
            return body.replace("error", "fixed");
        }
    }
}

Kafka的"死信队列"(自己实现)

Kafka本身不支持死信队列,需要自己实现

实现方案

@Component
@Slf4j
public class KafkaConsumerWithDLQ {
    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    private static final String NORMAL_TOPIC = "orders";
    private static final String DLQ_TOPIC = "orders-dlq";  // 死信Topic
    private static final int MAX_RETRY = 3;
    
    @KafkaListener(topics = NORMAL_TOPIC, groupId = "order-consumer-group")
    public void consumeMessage(ConsumerRecord<String, String> record) {
        String message = record.value();
        log.info("收到消息: {}", message);
        
        try {
            // 业务处理
            processMessage(message);
            log.info("✅ 消息处理成功");
            
        } catch (Exception e) {
            log.error("❌ 消息处理失败", e);
            
            // ⭐ 获取重试次数(从消息头)
            int retryCount = getRetryCount(record);
            
            if (retryCount < MAX_RETRY) {
                // 还可以重试,重新发送到原Topic
                retryCount++;
                republishWithRetryCount(NORMAL_TOPIC, message, retryCount);
                log.info("消息重新发送,第{}次重试", retryCount);
                
            } else {
                // ⭐ 重试次数用完,发送到死信Topic
                sendToDeadLetterQueue(record, e);
                log.warn("☠️ 消息发送到死信队列");
            }
        }
    }
    
    /**
     * 获取重试次数
     */
    private int getRetryCount(ConsumerRecord<String, String> record) {
        // 从消息头获取重试次数
        Header header = record.headers().lastHeader("retry-count");
        if (header != null) {
            return Integer.parseInt(new String(header.value()));
        }
        return 0;
    }
    
    /**
     * 重新发送消息(带重试次数)
     */
    private void republishWithRetryCount(String topic, String message, int retryCount) {
        ProducerRecord<String, String> record = new ProducerRecord<>(topic, message);
        
        // ⭐ 添加重试次数到消息头
        record.headers().add("retry-count", String.valueOf(retryCount).getBytes());
        
        kafkaTemplate.send(record);
    }
    
    /**
     * 发送到死信队列
     */
    private void sendToDeadLetterQueue(ConsumerRecord<String, String> record, Exception e) {
        String message = record.value();
        
        // 构造死信消息
        DeadLetterMessage dlqMsg = new DeadLetterMessage();
        dlqMsg.setOriginalTopic(record.topic());
        dlqMsg.setOriginalPartition(record.partition());
        dlqMsg.setOriginalOffset(record.offset());
        dlqMsg.setMessage(message);
        dlqMsg.setErrorMessage(e.getMessage());
        dlqMsg.setTimestamp(System.currentTimeMillis());
        
        // ⭐ 发送到死信Topic
        kafkaTemplate.send(DLQ_TOPIC, JSON.toJSONString(dlqMsg));
    }
    
    private void processMessage(String message) throws Exception {
        // 业务逻辑
        if (message.contains("error")) {
            throw new Exception("消息格式错误");
        }
    }
}

@Data
class DeadLetterMessage {
    private String originalTopic;
    private int originalPartition;
    private long originalOffset;
    private String message;
    private String errorMessage;
    private long timestamp;
}

死信消费者

@Component
@Slf4j
public class KafkaDLQConsumer {
    
    @KafkaListener(topics = "orders-dlq", groupId = "dlq-consumer-group")
    public void handleDeadLetter(ConsumerRecord<String, String> record) {
        String dlqMsgJson = record.value();
        DeadLetterMessage dlqMsg = JSON.parseObject(dlqMsgJson, DeadLetterMessage.class);
        
        log.warn("☠️ 收到死信消息: topic={}, offset={}, error={}", 
            dlqMsg.getOriginalTopic(), 
            dlqMsg.getOriginalOffset(),
            dlqMsg.getErrorMessage());
        
        // 处理死信
        handleDeadLetter(dlqMsg);
    }
    
    private void handleDeadLetter(DeadLetterMessage dlqMsg) {
        // 保存到数据库
        deadLetterRepository.save(dlqMsg);
        
        // 发送告警
        alertService.send("死信队列有新消息");
        
        // 尝试修复
        if (canFix(dlqMsg.getMessage())) {
            String fixed = fix(dlqMsg.getMessage());
            kafkaTemplate.send(dlqMsg.getOriginalTopic(), fixed);
        }
    }
}

🎯 死信处理策略

策略1:人工处理 👨‍💼

适用:消息量少,问题复杂

/**
 * 死信管理后台
 */
@RestController
@RequestMapping("/api/dlq")
public class DeadLetterController {
    
    @Autowired
    private DeadLetterRepository dlqRepo;
    
    @Autowired
    private MessageProducer producer;
    
    /**
     * 查询死信列表
     */
    @GetMapping("/list")
    public List<DeadLetterRecord> listDeadLetters() {
        return dlqRepo.findAll();
    }
    
    /**
     * 重新发送死信消息
     */
    @PostMapping("/{id}/retry")
    public void retryDeadLetter(@PathVariable Long id) {
        DeadLetterRecord record = dlqRepo.findById(id)
            .orElseThrow(() -> new NotFoundException("死信不存在"));
        
        // ⭐ 重新发送到原Topic
        producer.sendMessage(record.getOriginalTopic(), record.getMessage());
        
        // 标记为已处理
        record.setStatus("RETRIED");
        record.setRetryTime(new Date());
        dlqRepo.save(record);
    }
    
    /**
     * 删除死信消息
     */
    @DeleteMapping("/{id}")
    public void deleteDeadLetter(@PathVariable Long id) {
        dlqRepo.deleteById(id);
    }
}

策略2:自动重试(有限次数)🔄

@Component
@Slf4j
public class AutoRetryDeadLetterHandler {
    
    private static final int MAX_AUTO_RETRY = 3;
    
    @Scheduled(fixedRate = 60000)  // 每分钟执行一次
    public void autoRetryDeadLetters() {
        // 查询待重试的死信
        List<DeadLetterRecord> records = dlqRepo.findByStatus("PENDING");
        
        for (DeadLetterRecord record : records) {
            if (record.getAutoRetryCount() < MAX_AUTO_RETRY) {
                try {
                    // ⭐ 自动重试
                    producer.sendMessage(record.getOriginalTopic(), record.getMessage());
                    
                    record.setAutoRetryCount(record.getAutoRetryCount() + 1);
                    record.setLastRetryTime(new Date());
                    dlqRepo.save(record);
                    
                    log.info("自动重试死信消息: id={}, 第{}次", 
                        record.getId(), record.getAutoRetryCount());
                    
                } catch (Exception e) {
                    log.error("自动重试失败", e);
                }
            } else {
                // 重试次数用完,标记为需要人工处理
                record.setStatus("MANUAL_REQUIRED");
                dlqRepo.save(record);
                
                // 发送告警
                alertService.send("死信消息需要人工处理: id=" + record.getId());
            }
        }
    }
}

策略3:定期清理 🗑️

@Component
@Slf4j
public class DeadLetterCleaner {
    
    /**
     * 每天凌晨清理7天前的死信
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void cleanOldDeadLetters() {
        Date expireTime = DateUtils.addDays(new Date(), -7);
        
        // 查询7天前的死信
        List<DeadLetterRecord> oldRecords = dlqRepo.findByCreateTimeBefore(expireTime);
        
        if (!oldRecords.isEmpty()) {
            // 备份到归档表
            dlqArchiveRepo.saveAll(oldRecords);
            
            // 删除
            dlqRepo.deleteAll(oldRecords);
            
            log.info("清理旧死信消息: count={}", oldRecords.size());
        }
    }
}

策略4:分类处理 🏷️

@Component
@Slf4j
public class DeadLetterClassifier {
    
    public void handleDeadLetter(DeadLetterMessage dlqMsg) {
        // ⭐ 根据错误类型分类处理
        String errorType = classifyError(dlqMsg.getErrorMessage());
        
        switch (errorType) {
            case "FORMAT_ERROR":
                // 格式错误:尝试修复
                handleFormatError(dlqMsg);
                break;
                
            case "BUSINESS_ERROR":
                // 业务错误:人工处理
                handleBusinessError(dlqMsg);
                break;
                
            case "TIMEOUT":
                // 超时:重试
                handleTimeout(dlqMsg);
                break;
                
            case "THIRD_PARTY_ERROR":
                // 第三方错误:等待第三方恢复后重试
                handleThirdPartyError(dlqMsg);
                break;
                
            default:
                log.error("未知错误类型: {}", errorType);
        }
    }
    
    private String classifyError(String errorMessage) {
        if (errorMessage.contains("JSON") || errorMessage.contains("format")) {
            return "FORMAT_ERROR";
        } else if (errorMessage.contains("timeout")) {
            return "TIMEOUT";
        } else if (errorMessage.contains("API")) {
            return "THIRD_PARTY_ERROR";
        } else {
            return "BUSINESS_ERROR";
        }
    }
    
    private void handleFormatError(DeadLetterMessage dlqMsg) {
        // 尝试修复格式错误
        log.info("处理格式错误: {}", dlqMsg.getMessage());
        // ...
    }
    
    private void handleBusinessError(DeadLetterMessage dlqMsg) {
        // 业务错误,标记为需要人工处理
        log.warn("业务错误,需要人工处理: {}", dlqMsg.getMessage());
        // ...
    }
    
    private void handleTimeout(DeadLetterMessage dlqMsg) {
        // 超时,重新发送
        log.info("超时,重新发送: {}", dlqMsg.getMessage());
        // ...
    }
    
    private void handleThirdPartyError(DeadLetterMessage dlqMsg) {
        // 第三方错误,延迟重试
        log.info("第三方错误,5分钟后重试: {}", dlqMsg.getMessage());
        // ...
    }
}

📊 对比总结

消息队列死信队列支持实现方式死信原因
RabbitMQ✅ 原生支持配置x-dead-letter-exchange拒绝、过期、队列满
RocketMQ✅ 原生支持自动创建%DLQ%GroupName重试16次后
Kafka❌ 不支持需自己实现自定义逻辑

🎓 面试题速答

Q1: 什么是死信队列?

A: 死信队列(DLQ) = 存放无法正常处理的消息的队列

三种情况产生死信

  1. 消息被拒绝(Reject/Nack)
  2. 消息过期(TTL超时)
  3. 队列满了(Queue Full)

作用

  • 消息兜底,防止数据丢失
  • 问题排查,分析失败原因
  • 业务降级,延迟处理

Q2: RabbitMQ如何配置死信队列?

A: 普通队列配置

QueueBuilder.durable("normal.queue")
    .withArgument("x-dead-letter-exchange", "dlx.exchange")  // 死信交换机
    .withArgument("x-dead-letter-routing-key", "dlq.key")    // 死信路由键
    .build();

消费者拒绝消息

channel.basicNack(deliveryTag, false, false);
//                                     ↑
//                             requeue=false(进入死信队列)

Q3: RocketMQ的死信队列是如何工作的?

A: 自动创建死信Topic

普通Topic: OrderTopic
消费者组: order-consumer-group

消息消费失败,重试16次后
    ↓
自动进入死信Topic: %DLQ%order-consumer-group

消费死信

@RocketMQMessageListener(
    topic = "%DLQ%order-consumer-group",
    consumerGroup = "dlq-consumer-group"
)

Q4: Kafka如何实现死信队列?

A: Kafka不支持死信队列,需自己实现

try {
    processMessage(message);
} catch (Exception e) {
    if (retryCount < MAX_RETRY) {
        // 重试
        republish(message, retryCount + 1);
    } else {
        // ⭐ 发送到死信Topic
        kafkaTemplate.send("orders-dlq", message);
    }
}

Q5: 死信消息如何处理?

A: 四种策略!

  1. 人工处理:提供管理后台,人工审核和重发
  2. 自动重试:定时任务自动重试(有限次数)
  3. 定期清理:清理过期的死信消息
  4. 分类处理:根据错误类型采取不同策略

Q6: 死信队列的最佳实践?

A:

  1. 监控告警:死信队列有新消息,立即告警
  2. 保留时间:死信保留7-30天
  3. 定期清理:过期死信归档后删除
  4. 分析原因:定期分析死信,发现系统问题
  5. 人工介入:重要业务的死信,需要人工处理

🎬 总结

              死信队列处理流程图

┌──────────────────────────────────────────────┐
│             正常队列                         │
│                                              │
│  消息 → 消费 → 处理                          │
│              ↓                               │
│           成功 ✅ → 确认                     │
│              ↓                               │
│           失败 ❌                            │
│              ├─ 重试3次                      │
│              ├─ 消息过期                     │
│              └─ 队列满                       │
│                                              │
└──────────────┬───────────────────────────────┘
               │
               ↓ 进入死信队列
┌──────────────────────────────────────────────┐
│            死信队列 ☠️                       │
│                                              │
│  消息暂存 → 分析原因 → 处理策略              │
│              ↓                               │
│         ┌────┴────┐                         │
│         │         │                         │
│    格式错误   业务错误                      │
│      ↓         ↓                            │
│    自动修复  人工处理                        │
│      ↓         ↓                            │
│    重新发送   解决后重发                     │
│                                              │
└──────────────────────────────────────────────┘

     死信队列 = 消息的"急诊室" 🏥

🎉 恭喜你!

你已经完全掌握了死信队列的作用和处理策略!🎊

核心要点

  1. 死信:无法正常处理的消息(拒绝、过期、队列满)
  2. 作用:兜底处理、问题排查、业务降级
  3. 策略:人工处理、自动重试、定期清理、分类处理

下次面试,这样回答

"死信队列是用来存放无法正常处理的消息的队列。

三种情况会产生死信:消息被拒绝、消息过期、队列满了。

RabbitMQ通过配置x-dead-letter-exchange实现,RocketMQ会自动创建死信Topic(%DLQ%GroupName),Kafka需要自己实现。

死信消息的处理策略包括:人工处理(提供管理后台)、自动重试(定时任务)、定期清理(归档后删除)、分类处理(根据错误类型)。

我们项目中对死信队列配置了监控告警,有新消息立即通知,重要业务的死信需要人工审核后才能重发。"

面试官:👍 "很好!你对死信队列理解很全面!"


本文完 🎬

上一篇: 191-消息队列的推拉模式的优劣对比.md
下一篇: 193-消息队列的削峰填谷作用和实际应用.md

作者注:写完这篇,我都想去医院急诊室实习了!🏥
如果这篇文章对你有帮助,请给我一个Star⭐!