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);
            }
        }
    }
}

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

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

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

选择建议

适合直接重试的场景

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

适合数据库记录的场景

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

相关推荐
2601_949818092 分钟前
头歌答案--爬虫实战
java·前端·爬虫
2601_9498179212 分钟前
大厂Java进阶面试解析笔记文档
java·笔记·面试
郭wes代码13 分钟前
大三Java课设:一行行敲出来的贪吃蛇,老师以为我是CV的
java·开发语言
IGAn CTOU1 小时前
王炸级更新!Spring Boot 3.4 正式发布,新特性真香!
java·spring boot·后端
C雨后彩虹1 小时前
最多等和不相交连续子序列
java·数据结构·算法·华为·面试
Ssan PRIN1 小时前
深度掌握 RabbitMQ 消息确认(ACK)机制,确保消息万无一失
分布式·rabbitmq
tycooncool1 小时前
Spring中的IOC详解
java·后端·spring
014-code1 小时前
日志规范:怎么写才不算写废话
java·开发语言·设计模式·日志
CQU_JIAKE2 小时前
4.17[Q]
java·linux·服务器
亦暖筑序2 小时前
Spring AI Alibaba 报错合集:我踩过的那些坑
java·后端