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

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

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

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

选择建议

适合直接重试的场景

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

适合数据库记录的场景

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

相关推荐
程序员小假16 小时前
我们来说一说 ThreadLocal 内存泄漏
java·后端
xq952716 小时前
获取Facebook 散列利器 来了 十六进制到 Base64 转换器
java
我不是混子17 小时前
聊聊Spring事件机制
java·后端
DKPT17 小时前
JVM栈溢出时如何dump栈信息?
java·jvm·笔记·学习·spring
DKPT17 小时前
JVM堆大小如何设置?
java·开发语言·jvm·笔记·学习
铅笔侠_小龙虾17 小时前
JVM 目录
java·jvm
yunxi_0517 小时前
让大模型会“说话”:基于 Spring WebSocket 的毫秒级流式 RAG 对话
java·后端
用户61204149221317 小时前
jsp+servlet做的医院挂号看诊管理系统
java·javascript·mysql
€81117 小时前
Java入门级教程21——Java 缓存技术、RMI远程方法调用、多线程分割大文件
java·开发语言·java缓存代理模式的实现·java rmi远程方法调用·多线程分割大文件
渣哥17 小时前
Java线程池那些坑:我与线程池的恩怨情仇
java