RabbitMQ 如何保证消息幂等?

313 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

RabbitMQ的自动重试功能:

当消费者消费消息处理业务逻辑时,如果抛出异常,或者不向RabbitMQ返回响应,默认情况下,RabbitMQ会无限次数的重复进行消息消费。

处理幂等问题,

  • 首先要设定RabbitMQ的重试次数 。在SpringBoot集成RabbitMQ时,可以在配置文件中指定spring.rabbitmq.listener.simple.retry开头的一系列属性,来制定重试策略。
  • 然后需要在业务上处理幂等问题。处理幂等问题的关键是要给每个消息一个唯一的标识。在SpringBoot框架集成RabbitMQ后,可以给每个消息指定一个全局唯一的MessageID,在消费者端针对MessageID做幂等性判断。
//发送者指定ID字段 
Message message2 = MessageBuilder.withBody(message.getBytes())
    .setMessageId(UUID.randomUUID().toString()).build(); 
rabbitTemplate.send(message2); 

//消费者获取MessageID,自己做幂等性判断 
@RabbitListener(queues = "fanout_email_queue") 
public void process(Message message) throws Exception { 
    // 获取消息Id 
    String messageId = message.getMessageProperties().getMessageId();  
    //...
}

要注意下这里用的message要是org.springframework.amqp.core.Message

在原生API当中,也是支持MessageId的。当然,在实际工作中,最好还是能够添加一个具有业务意义的数据作为唯一键会更好,这样能更好的防止重复消费问题对业务的影响。比如,针对订单消息,那就用订单ID来做唯一键。在RabbitMQ中,消息的头部就是一个很好的携带数据的地方。

// ==== 发送消息时,携带sequenceNumber和orderNo 
AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); 
builder.deliveryMode(MessageProperties.PERSISTENT_TEXT_PLAIN.getDeliveryMode()); 
builder.priority(MessageProperties.PERSISTENT_TEXT_PLAIN.getPriority()); 
//携带消息ID 
builder.messageId(""+channel.getNextPublishSeqNo()); 
Map<String, Object> headers = new HashMap<>(); 
//携带订单号 
headers.put("order", "123"); 
builder.headers(headers); 
channel.basicPublish("", QUEUE_NAME, builder.build(), message.getBytes("UTF-8")); 





// ==== 接收消息时,拿到sequenceNumber 
Consumer myconsumer = new DefaultConsumer(channel) { 
    @Override public void handleDelivery(
        String consumerTag, 
        Envelope envelope, 
        BasicProperties properties, 
        byte[] body
    ) throws IOException { 
        //获取消息ID 
        System.out.println("messageId:"+properties.getMessageId()); 
        //获取订单ID
        properties.getHeaders().forEach((key,value)-> System.out.println("key: "+key +"; value: "+value)); 
        // (process the message components here ...) 
        //消息处理完后,进行答复。答复过的消息,服务器就不会再次转发。 
        //没有答复过的消息,服务器会一直不停转发。 
        channel.basicAck(deliveryTag, false); 
    } 
}; 
channel.basicConsume(QUEUE_NAME, false, myconsumer);