一、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. 动态添加html事件无响应

    问题描述:在页面中动态使用js添加的html中设置了onclick事件,生产页面后点击事件无效并提示:Cannot read property 'xxx' of undefined 如: $('.te ...

  2. C#添加log4日志

    第一步导入log4net 在vs的程序包管理器控制台中执行命令 NuGet\Install-Package log4net -Version 2.0.0 第二步加帮助类HttpHelper using ...

  3. excel表格粘贴到网页的功能

    背景 项目有表格功能,表格过大,一个一个填,过于麻烦. 需要从excel表复制的功能. 过程 监听paste事件,根据事件提供的clipboardData属性,获取数据. 根据换行符 \n 和tab符 ...

  4. Linux通配符和转移字符(扩展匹配文件名)、man帮助文档的使用

  5. 我喜欢 amy 同学的腿……

    1.我是人间之屑,这点我先承认大约不会错,但并不代表我的一切都是 hitonokuzu 为了防止对成语的吴勇(迫真),这就是人渣的意思. 2.看来亚里士多德的观点还是没有过时,老祖宗说的对.老祖宗指的 ...

  6. CF1326G 题解

    题意: 蛛网树是一颗平面树,满足点是该树的凸包的顶点上等价于其是叶子. 给定一个平面树,求有多少种对点集的划分,使得每个划分出来的集合都是蛛网树. Solution 考虑树形 dp.设 \(f_u\) ...

  7. Yarn提交Flink任务参数介绍

    一.参数介绍 作业模式:yarn-per-job 基本参数 描述 -ynm(Custom Yarnname) 自定义的Yarn名字 -yqu 指定Yarn队列名 -yn (TaskManager)  ...

  8. 告别 DeepSeek 系统繁忙,七个 DeepSeek 曲线救国平替入口,官网崩溃也能用!

    前言 DeepSeek作为一款备受瞩目的国产大模型,以其强大的功能和卓越的性能赢得了众多用户的青睐.然而,随着用户量的激增,DeepSeek官网近期频繁遭遇服务器繁忙甚至崩溃的问题,给广大用户带来了不 ...

  9. 【杂谈】主键ID如何选择——自增数 OR UUID?

    1.生成位置如何影响选择? 数据库往返时间 使用自增数时,ID是由数据库在执行INSERT操作时生成的:而UUID则可以在应用层生成. 考虑这样的场景: 一个方法需要插入A和B两个实体.其中B的数据需 ...

  10. [HAOI2018] 染色 题解

    第一眼肯定想到容斥.设 \(G(k)\) 表示至少有 \(k\) 种颜色符合要求,\(F(k)\) 表示恰好有 \(k\) 种颜色符合要求.显然 \(k\) 的上界 \(t=\min(m,\lfloo ...