一、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]

rabbitmq的消息的有顺序性的更多相关文章

  1. RabbitMQ保证消息的顺序性

    当我们的系统中引入了MQ之后,不得不考虑的一个问题是如何保证消息的顺序性,这是一个至关重要的事情,如果顺序错乱了,就会导致数据的不一致.       比如:业务场景是这样的:我们需要根据mysql的b ...

  2. 分布式场景下Kafka消息顺序性的思考

    如果业务中,对于kafka发送消息异步消费的场景,在业务上需要实现在消费时实现顺序消费, 利用kafka在partition内消息有序的特点,消息消费时的有序性. 1.在发送消息时,通过指定parti ...

  3. Pulsar の 保证消息的顺序性、幂等性和可靠性

    原文链接:Pulsar の 保证消息的顺序性.幂等性和可靠性 一.背景 前面两篇文章,已经介绍了关于Pulsar消费者的详细使用和自研的Pulsar组件. 接下来,将简单分析如何保证消息的顺序性.幂等 ...

  4. RabbitMQ多消费者顺序性消费消息实现

    最近起了个项目消息中心,用来中转各个系统中产生的消息,用到的是RabbitMQ,由于UAT环境.生产环境每台消费者服务都是多台,有些消息要求按顺序消费,所以需要采取一定的措施保证消息的顺序消费,下面讲 ...

  5. Kafka如何保证消息的顺序性

    1. 问题 比如说我们建了一个 topic,有三个 partition.生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到 ...

  6. MQ如何解决消息的顺序性

    一.消息的顺序性 1.延迟队列:设置一个全局变量index,根据实际情况一次按照index++的逻辑一次给消息队列设置延迟时间段,可以是0.5s,甚至1s; 弊端:如果A,B,C..消息队列消费时间不 ...

  7. kafka如何保证消息得顺序性

    1. 问题 比如说我们建了一个 topic,有三个 partition.生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到 ...

  8. mq中如何保证消息的顺序性

    先说结论 不建议在mq当中使用消息的投递顺序来保证消息的顺序一致性 反思为什么需要保留消息的顺序性 日常思维中,顺序大部分情况会和时间关联起来,即时间的先后表示事件的顺序关系.消息队列中的若干消息如果 ...

  9. 如何保证RabbitMQ的消息按照顺序执行???

    可以采用单线程的消费保证消息的顺序性.对消息进行编号,1,2,3,4--消费时按照编号的顺序去消费消息.这样就可以保证消息 按照一定顺序执行.

  10. 如何保证MQ的顺序性?比如Kafka

    三.如何保证消息的顺序性 1. rabbitmq 拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点:或者就一个queue但是对应一个consumer,然后 ...

随机推荐

  1. w3cschool-MyBatis 教程

    参考 https://www.w3cschool.cn/mybatis/mybatis-dyr53b5w.html MyBatis 入门 SqlSessionFactoryBuilder用 SqlSe ...

  2. 字符流:FileReader/FileWriter的使用

    读取文件 1.建立一个流对象,将已存在的一个文件加载进流. FileReader fr = new FileReader(new File("Test.txt"));2.创建一个 ...

  3. JavaScript 中的组合继承 :ES5 与 ES6 中最近似的写法

    JavaScript 的继承写法较多,在此并不一一讨论,仅对最常用的组合式继承做一个说明: 组合式继承主要利用了原型链继承和构造函数继承. 一.ES5 中的写法 function Person(nam ...

  4. uni-app使用阿里矢量字体图标

    在app.vue下,引入 <style> @font-face { font-family: 'iconfont'; /* project id 1951514 */ src: url(' ...

  5. Project Euler 457 题解

    初等数论小题目 求 \[n^2-3n-1\equiv 0\pmod {p^2} \] 配方,得到: \[(2n-3)^2\equiv 13 \pmod {p^2} \] 根据亨泽尔引理,只需得到 \( ...

  6. [CERC2014] Parades 题解

    感觉长脑子了. 考虑在路线两端点的 \(lca\) 计算贡献,那么线段可以分两类: \(u\) 为 \(v\) 祖先. \(u,v\) 互不为祖先. 设 \(dp_i\) 表示只考虑 \(i\) 子树 ...

  7. 图解MySQL【日志】——Binlog

    Binlog(Binary Log,归档日志) 为什么需要 Binlog? Binlog 是 MySQL 中的二进制日志,用于记录数据库的所有写操作(INSERT.UPDATE.DELETE 等) 1 ...

  8. Ruoyi-vue 左侧菜单栏默认保持收缩

    在项目的 src\store\modules\app.js 路径下 sidebar: { opened: Cookies.get('sidebarStatus') ? !!+Cookies.get(' ...

  9. Qt QFileSystemModel 的使用

    Model 指的是数据 View 指的是界面,View不用设置,只需要和Model进行绑定,绑定完成之后就是Model的格式了 例子:本例子中QListView QTableView QTreeVie ...

  10. 001.GItLab介绍及云原生部署

    目录 gitlab介绍 gitlab cicd简介 gitlab特点 git主要目录 版本区别 gitlab部署 Omnibus 部署 部署需求 安装依赖 导入软件源 正式安装 准备证书 配置SSL及 ...