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

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

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

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

选择建议

适合直接重试的场景

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

适合数据库记录的场景

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

相关推荐
西瓜本瓜@2 小时前
在Android中如何使用Protobuf上传协议
android·java·开发语言·git·学习·android-studio
言之。2 小时前
别学了,打会王者吧
java·python·mysql·容器·spark·php·html5
机智的人猿泰山2 小时前
java kafka
java·开发语言·kafka
Algorithm15763 小时前
谈谈接口和抽象类有什么区别?
java·开发语言
龙仔7253 小时前
离线安装rabbitmq全流程
分布式·rabbitmq·ruby
细心的莽夫3 小时前
SpringCloud 微服务复习笔记
java·spring boot·笔记·后端·spring·spring cloud·微服务
264玫瑰资源库5 小时前
问道数码兽 怀旧剧情回合手游源码搭建教程(反查重优化版)
java·开发语言·前端·游戏
pwzs5 小时前
Java 中 String 转 Integer 的方法与底层原理详解
java·后端·基础
东阳马生架构5 小时前
Nacos简介—2.Nacos的原理简介
java
普if加的帕5 小时前
java Springboot使用扣子Coze实现实时音频对话智能客服
java·开发语言·人工智能·spring boot·实时音视频·智能客服