rabbitmq的消息的有顺序性

一、rabbitmq:拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理;


RabbitMQ 实现消息顺序消费的完整方案


RabbitMQ 本身是一个基于消息队列的异步消息中间件,要保证严格的顺序消费需要特殊设计和配置。以下是实现消息顺序消费的几种有效方案:


一、单队列单消费者模式(最简单方案)


实现原理

  • 一个队列只对应一个消费者

  • 天然保证先进先出(FIFO)顺序


实现代码

      // 生产者
      rabbitTemplate.convertAndSend("order.queue", "消息1");
      rabbitTemplate.convertAndSend("order.queue", "消息2");

      // 消费者
      @RabbitListener(queues = "order.queue")
      public void processOrder(String message) {
          // 按顺序处理消息
      }


优缺点:

  • ✅ 优点:实现简单,严格保证顺序

  • ❌ 缺点:无法水平扩展消费者,性能受限


二、通过路由键来实现消息分组有顺序性(推荐方案)


实现原理

  • 按业务ID将消息分组(如同一订单号)

  • 相同分组的消息路由到同一队列

  • 每个队列只有一个消费者


代码示例1

      // 配置交换机和队列
      @Bean
      public DirectExchange orderExchange() {
          return new DirectExchange("order.exchange");
      }

      @Bean
      public Queue orderQueue1() { return new Queue("order.queue.1"); }
      @Bean
      public Queue orderQueue2() { return new Queue("order.queue.2"); }

      // 生产者发送消息(按订单ID路由)
      public void sendOrderMessage(Order order) {
          String routingKey = "order.queue." + (order.getOrderId().hashCode() % 2 + 1);
          rabbitTemplate.convertAndSend("order.exchange", routingKey, order);
      }

      // 消费者
      @RabbitListener(queues = "order.queue.1")
      public void processOrder1(Order order) {
          // 处理订单
      }

      @RabbitListener(queues = "order.queue.2")
      public void processOrder2(Order order) {
          // 处理订单
      }


优缺点:

  • ✅ 优点:可扩展消费者数量,相同业务ID消息顺序保证

  • ❌ 缺点:需要设计合理的分组策略


代码示例2

电商订单状态变更顺序消费案例

1、场景说明

  • 订单状态变更必须严格按顺序处理:创建 → 支付 → 发货 → 完成

  • 同一订单的不同状态消息必须顺序消费

  • 不同订单之间可以并行处理


2、实现方案设计

采用消息分组方案,根据订单ID将消息路由到不同队列,每个队列一个消费者保证顺序


3、完整实现代码


配置类

    @Configuration
    public class OrderRabbitConfig {

        // 订单交换机
        @Bean
        public DirectExchange orderExchange() {
            return new DirectExchange("order.exchange", true, false);
        }

        // 订单队列(根据订单ID哈希路由到3个队列)
        @Bean
        public Queue orderQueue0() {
            return new Queue("order.queue.0", true);
        }
        
        @Bean
        public Queue orderQueue1() {
            return new Queue("order.queue.1", true);
        }
        
        @Bean
        public Queue orderQueue2() {
            return new Queue("order.queue.2", true);
        }

        // 交换机与队列进行绑定关系
        @Bean
        public Binding orderBinding0() {
            return BindingBuilder.bind(orderQueue0())
                    .to(orderExchange())
                    .with("order.0");
        }
        
        @Bean
        public Binding orderBinding1() {
            return BindingBuilder.bind(orderQueue1())
                    .to(orderExchange())
                    .with("order.1");
        }
        
        @Bean
        public Binding orderBinding2() {
            return BindingBuilder.bind(orderQueue2())
                    .to(orderExchange())
                    .with("order.2");
        }
    }

订单状态消息

    @Data
    public class OrderStatusMessage {
        private String orderId;      // 订单ID
        private String status;      // 订单状态
        private Long timestamp;     // 消息时间戳
        private Integer sequence;   // 消息序列号
        
        // 状态枚举
        public static final String CREATED = "CREATED";
        public static final String PAID = "PAID";
        public static final String SHIPPED = "SHIPPED";
        public static final String COMPLETED = "COMPLETED";
    }

生产者服务

      @Service
      public class OrderProducer {
          
          @Autowired
          private RabbitTemplate rabbitTemplate;
          
          public void sendOrderStatus(OrderStatusMessage message) {
              // 根据订单ID决定路由键(相同订单始终到同一队列)
              String routingKey = "order." + Math.abs(message.getOrderId().hashCode() % 3);
              
              rabbitTemplate.convertAndSend(
                      "order.exchange", 
                      routingKey, 
                      message,
                      msg -> {
                          // 设置消息持久化
                          msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                          return msg;
                      });
              
              log.info("发送订单状态消息: {} [{}]", message.getOrderId(), message.getStatus());
          }
      }

消费者服务

      @Service
      public class OrderConsumer {
          
          // 记录每个订单的最后处理状态
          private final Map<String, String> lastStatusMap = new ConcurrentHashMap<>();
          
          @RabbitListener(queues = "order.queue.0")
          public void processQueue0(OrderStatusMessage message, Channel channel, 
                                  @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
              processOrderMessage(message, channel, tag);
          }
          
          @RabbitListener(queues = "order.queue.1")
          public void processQueue1(OrderStatusMessage message, Channel channel, 
                                  @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
              processOrderMessage(message, channel, tag);
          }
          
          @RabbitListener(queues = "order.queue.2")
          public void processQueue2(OrderStatusMessage message, Channel channel, 
                                  @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
              processOrderMessage(message, channel, tag);
          }
          
          private void processOrderMessage(OrderStatusMessage message, Channel channel, long tag) 
                  throws IOException {
              try {
                  // 检查消息顺序
                  String lastStatus = lastStatusMap.get(message.getOrderId());
                  if (!checkStatusSequence(lastStatus, message.getStatus())) {
                      log.warn("订单状态顺序错误! 订单ID: {}, 当前状态: {}, 收到状态: {}", 
                              message.getOrderId(), lastStatus, message.getStatus());
                      channel.basicNack(tag, false, false); // 丢弃错误顺序消息
                      return;
                  }
                  
                  // 模拟业务处理
                  processOrderStatus(message);
                  
                  // 更新最后状态
                  lastStatusMap.put(message.getOrderId(), message.getStatus());
                  
                  // 确认消息
                  channel.basicAck(tag, false);
                  
              } catch (Exception e) {
                  log.error("处理订单消息异常", e);
                  channel.basicNack(tag, false, false); // 处理失败不再重试
              }
          }
          
          private boolean checkStatusSequence(String lastStatus, String newStatus) {
              if (lastStatus == null) {
                  return OrderStatusMessage.CREATED.equals(newStatus);
              }
              
              switch (lastStatus) {
                  case OrderStatusMessage.CREATED:
                      return OrderStatusMessage.PAID.equals(newStatus);
                  case OrderStatusMessage.PAID:
                      return OrderStatusMessage.SHIPPED.equals(newStatus);
                  case OrderStatusMessage.SHIPPED:
                      return OrderStatusMessage.COMPLETED.equals(newStatus);
                  default:
                      return false;
              }
          }
          
          private void processOrderStatus(OrderStatusMessage message) {
              // 模拟处理耗时
              try {
                  Thread.sleep(new Random().nextInt(100));
              } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
              }
              log.info("处理订单状态: {} [{}]", message.getOrderId(), message.getStatus());
          }
      }

测试验证

      @SpringBootTest
      public class OrderMessageTest {
          
          @Autowired
          private OrderProducer orderProducer;
          
          @Test
          public void testOrderSequence() throws InterruptedException {
              String orderId1 = "order-1001";
              String orderId2 = "order-1002";
              
              // 正确顺序的消息
              sendOrderMessages(orderId1, 
                      OrderStatusMessage.CREATED,
                      OrderStatusMessage.PAID,
                      OrderStatusMessage.SHIPPED,
                      OrderStatusMessage.COMPLETED);
              
              // 乱序的消息(将被拒绝)
              sendOrderMessages(orderId2, 
                      OrderStatusMessage.PAID,  // 错误:缺少CREATED
                      OrderStatusMessage.CREATED,
                      OrderStatusMessage.COMPLETED,  // 错误:缺少SHIPPED
                      OrderStatusMessage.SHIPPED);
              
              // 等待消费者处理
              Thread.sleep(3000);
          }
          
          private void sendOrderMessages(String orderId, String... statuses) {
              for (int i = 0; i < statuses.length; i++) {
                  OrderStatusMessage message = new OrderStatusMessage();
                  message.setOrderId(orderId);
                  message.setStatus(statuses[i]);
                  message.setTimestamp(System.currentTimeMillis());
                  message.setSequence(i + 1);
                  
                  orderProducer.sendOrderStatus(message);
              }
          }
      }

预期输出

    发送订单状态消息: order-1001 [CREATED]
    发送订单状态消息: order-1001 [PAID]
    发送订单状态消息: order-1001 [SHIPPED]
    发送订单状态消息: order-1001 [COMPLETED]
    发送订单状态消息: order-1002 [PAID]
    发送订单状态消息: order-1002 [CREATED]
    发送订单状态消息: order-1002 [COMPLETED]
    发送订单状态消息: order-1002 [SHIPPED]

    处理订单状态: order-1001 [CREATED]
    处理订单状态: order-1001 [PAID]
    处理订单状态: order-1001 [SHIPPED]
    处理订单状态: order-1001 [COMPLETED]
    订单状态顺序错误! 订单ID: order-1002, 当前状态: null, 收到状态: PAID
    处理订单状态: order-1002 [CREATED]
    订单状态顺序错误! 订单ID: order-1002, 当前状态: CREATED, 收到状态: COMPLETED
    处理订单状态: order-1002 [SHIPPED]
posted @ 2021-05-30 20:03  jock_javaEE  阅读(419)  评论(0)    收藏  举报