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  阅读(310)  评论(0)    收藏  举报