RabbitMQ消费者重试的两种方案

目录

直接重试

优点:

缺点:

保存数据库的重试方案

优点:

缺点:

选择建议

适合直接重试的场景

适合数据库记录的场景


这篇文章总结一下消费者消费重试的方案

直接重试

一种是消息消费失败然后消费者直接重试,这需要配置消费者重试机制

java 复制代码
@Component
public class DirectRetryConsumer {
    
    @RabbitListener(queues = "myQueue")
    @RabbitListener(
        queues = "myQueue",
        containerFactory = "retryContainerFactory"
    )
    public void processMessage(Message message, Channel channel) {
        try {
            // 处理消息
            processBusinessLogic(message);
            // 确认消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            
        } catch (Exception e) {
            // 重试处理
            handleRetry(message, channel, e);
        }
    }
    
    private void handleRetry(Message message, Channel channel, Exception e) {
        MessageProperties props = message.getMessageProperties();
        Long retryCount = props.getHeader("retry-count");
        
        if (retryCount == null) {
            retryCount = 0L;
        }
        
        if (retryCount < maxRetryCount) {
            // 重新入队,等待重试
            channel.basicNack(props.getDeliveryTag(), false, true);
            
        } else {
            // 超过重试次数,进入死信队列
            channel.basicNack(props.getDeliveryTag(), false, false);
        }
    }
}

@Configuration
public class RetryConfig {
    
    @Bean
    public SimpleRabbitListenerContainerFactory retryContainerFactory(
            ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = 
            new SimpleRabbitListenerContainerFactory();
            
        // 配置重试策略
        RetryTemplate retryTemplate = new RetryTemplate();
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(1000);
        backOffPolicy.setMultiplier(2.0);
        backOffPolicy.setMaxInterval(10000);
        retryTemplate.setBackOffPolicy(backOffPolicy);
        
        factory.setRetryTemplate(retryTemplate);
        return factory;
    }
}
优点:
  • 实现简单,开发成本低
  • 消息处理实时性高
  • 系统复杂度低
  • 资源消耗相对较少
缺点:
  • 重试策略不够灵活
  • 无法保存失败原因和重试历史
  • 难以进行人工干预
  • 监控和统计困难

保存数据库的重试方案

java 复制代码
@Entity
public class MessageRecord {
    @Id
    private Long id;
    private String messageId;
    private String payload;
    private String status; // PENDING, PROCESSING, FAILED, SUCCESS
    private Integer retryCount;
    private String errorMessage;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

@Service
@Transactional
public class MessageProcessService {
    
    @Autowired
    private MessageRecordRepository recordRepository;
    
    @RabbitListener(queues = "myQueue")
    public void processMessage(Message message, Channel channel) {
        MessageRecord record = null;
        try {
            // 1. 保存消息记录
            record = saveMessageRecord(message);
            
            // 2. 处理业务逻辑
            processBusinessLogic(message);
            
            // 3. 更新状态为成功
            record.setStatus("SUCCESS");
            recordRepository.save(record);
            
            // 4. 确认消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            
        } catch (Exception e) {
            // 5. 记录失败信息
            handleFailure(record, e);
            // 6. 拒绝消息
            channel.basicNack(
                message.getMessageProperties().getDeliveryTag(), 
                false, 
                false
            );
        }
    }
}

@Component
@Slf4j
public class FailedMessageRetryJob {
    
    @Autowired
    private MessageRecordRepository recordRepository;
    
    @Scheduled(fixedDelay = 300000) // 5分钟
    public void retryFailedMessages() {
        List<MessageRecord> failedRecords = 
            recordRepository.findByStatusAndRetryCountLessThan(
                "FAILED", 
                maxRetryCount
            );
            
        for (MessageRecord record : failedRecords) {
            try {
                // 重试处理
                processMessage(record);
                
                // 更新状态
                record.setStatus("SUCCESS");
                recordRepository.save(record);
                
            } catch (Exception e) {
                // 更新重试次数和错误信息
                record.setRetryCount(record.getRetryCount() + 1);
                record.setErrorMessage(e.getMessage());
                recordRepository.save(record);
                
                log.error("Retry failed for message: {}", record.getMessageId(), e);
            }
        }
    }
}

将所有的消息都记录在数据库中进行保存,并以消息是否消费成功来更改数据库中消息的状态值,可以开启一个定时任务执行从数据库取出失败的消息进行重新消费,因为可能取出消费的时候还可能会失败,可以设置一次任务取出数据消费条数,若是超出条数则等到下次定时任务再进行消费。

当然也可以通过定时任务给另一个重试队列投递消息,然后消费者收到消息就从数据库中取出失败的记录进行重试。这样的方法其实和刚刚讲的原理差不多。

优点:
  • 追踪性强,保留完整的处理历史
  • 重试策略灵活可配置
  • 支持人工干预和处理
  • 便于监控和统计
  • 可以实现定制化的重试逻辑
  • 数据不会丢失
缺点:
  • 实现复杂度高
  • 需要额外的数据库存储
  • 系统资源消耗较大
  • 实时性相对较差
  • 需要维护额外的定时任务

选择建议

适合直接重试的场景

简单的消息处理,处理简单的、非关键的消息,失败影响较小,不需要追踪历史

适合数据库记录的场景

复杂的业务处理,处理订单、支付等关键业务,需要完整的处理历史,可能需要人工介入

相关推荐
心向阳光的天域8 分钟前
黑马跟学.苍穹外卖.Day03
java·开发语言·spring boot
对酒当歌丶人生几何10 分钟前
SpringBoot实现国际化
java·spring boot·后端·il8n
雪芽蓝域zzs11 分钟前
JavaWeb开发(九)JSP技术
java·开发语言
上海拔俗网络27 分钟前
“智能筛查新助手:AI智能筛查分析软件系统如何改变我们的生活
java·团队开发
weixin_437398211 小时前
Elasticsearch学习(1) : 简介、索引库操作、文档操作、RestAPI、RestClient操作
java·大数据·spring boot·后端·学习·elasticsearch·全文检索
MasterNeverDown1 小时前
spring boot controller放到那一层
java·spring boot·后端
Yang-Never1 小时前
Kotlin->Kotlin协程的取消机制
android·java·开发语言·kotlin·android studio·idea
星迹日1 小时前
数据结构:LinkedList与链表—无头单向链表(一)
java·数据结构·经验分享·笔记·链表·单向链表
不会玩技术的技术girl2 小时前
获取淘宝商品详情高级版 API 接口 Java 示例代码
java·开发语言·前端
Allen Bright2 小时前
【JVM-1】深入解析JVM:Java虚拟机的核心原理与工作机制
java·开发语言·jvm