一、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. SM3

    算法过程 代码实现 国标 /************************************************************************ File name: SM ...

  2. ASP.NET Core 快速轻量级的浏览器检测和设备检测库

    在 .NET Framework 4.7 中那样,通过 HttpContext.Request 的 Browser 属性轻松获取发起 HTTP 请求的浏览器信息,ASP.NET Core 并未直接提供 ...

  3. linxu7下安装pacemaker+corosync集群-01

    1.yum仓库的配置-自行配置 2.安装软件包: yum -y install pacemaker* corosync* pcs* psmisc yum -y install pcs fence-ag ...

  4. 谈谈天翼云VPCE

    本文分享自天翼云开发者社区<谈谈天翼云VPCE>,作者:天枫霁月 一.VPCE产品出现的背景 跨VPC通信,且能够严格限制访问,任意两个租户之间都能互通,性能高,花费少,通过VPCE产品实 ...

  5. 喜讯!天翼云斩获NLP国际顶会比赛两项荣誉

    近日,NLP国际顶会ACL(The Association for Computational Linguistics)进行的国际赛事WASSA 2023(13th Workshop on Compu ...

  6. Project Euler 307 题解

    主要是规避误差.即求 \[\frac{k![x^k](1+x+\frac {x^2}2)^n}{n^k} \] 微分一下得到递推式.然后根据斯特林近似(byd 这里还需要 \(1\) 后的第一项..) ...

  7. ABB机器人IRB 6700维修保养技巧

    通过与子锐机器人维修保养服务定制合理的机器人保养工作,可以确保ABB机器人IRB 6700的持续稳定运行,延长其使用寿命,为企业的生产提供有力保障. 一.ABB机器人IRB 6700日常检查与维护 外 ...

  8. Android开发之定时任务(AlarmManager、WorkManager)

    Android 程序的定时任务主要有AlarmManager.WorkManager两种. 一.AlarmManager AlarmManager,又称闹钟,可以设置一次性任务,周期重复任务,定时重复 ...

  9. Sort operation used more than the maximum 33554432 bytes of RAM

    在数据量超大的情形下,任何数据库系统在创建索引时都是一个耗时的大工程,下面这篇文章主要给大家介绍了关于MongoDB排序时内存大小限制与创建索引的注意事项的相关资料,需要的朋友可以参考下

  10. ABC393F题解

    大概评级:绿. 一看到这种题目,就知道肯定是数据结构题,我们首先用一个众所周知的二分来求出 \(pos\) 数组,\(pos_i\) 表示以 \(i\) 结尾的最长上升子序列的大小,然后将询问离线,弄 ...