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]
rabbitmq的消息的有顺序性的更多相关文章
- RabbitMQ保证消息的顺序性
当我们的系统中引入了MQ之后,不得不考虑的一个问题是如何保证消息的顺序性,这是一个至关重要的事情,如果顺序错乱了,就会导致数据的不一致. 比如:业务场景是这样的:我们需要根据mysql的b ...
- 分布式场景下Kafka消息顺序性的思考
如果业务中,对于kafka发送消息异步消费的场景,在业务上需要实现在消费时实现顺序消费, 利用kafka在partition内消息有序的特点,消息消费时的有序性. 1.在发送消息时,通过指定parti ...
- Pulsar の 保证消息的顺序性、幂等性和可靠性
原文链接:Pulsar の 保证消息的顺序性.幂等性和可靠性 一.背景 前面两篇文章,已经介绍了关于Pulsar消费者的详细使用和自研的Pulsar组件. 接下来,将简单分析如何保证消息的顺序性.幂等 ...
- RabbitMQ多消费者顺序性消费消息实现
最近起了个项目消息中心,用来中转各个系统中产生的消息,用到的是RabbitMQ,由于UAT环境.生产环境每台消费者服务都是多台,有些消息要求按顺序消费,所以需要采取一定的措施保证消息的顺序消费,下面讲 ...
- Kafka如何保证消息的顺序性
1. 问题 比如说我们建了一个 topic,有三个 partition.生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到 ...
- MQ如何解决消息的顺序性
一.消息的顺序性 1.延迟队列:设置一个全局变量index,根据实际情况一次按照index++的逻辑一次给消息队列设置延迟时间段,可以是0.5s,甚至1s; 弊端:如果A,B,C..消息队列消费时间不 ...
- kafka如何保证消息得顺序性
1. 问题 比如说我们建了一个 topic,有三个 partition.生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到 ...
- mq中如何保证消息的顺序性
先说结论 不建议在mq当中使用消息的投递顺序来保证消息的顺序一致性 反思为什么需要保留消息的顺序性 日常思维中,顺序大部分情况会和时间关联起来,即时间的先后表示事件的顺序关系.消息队列中的若干消息如果 ...
- 如何保证RabbitMQ的消息按照顺序执行???
可以采用单线程的消费保证消息的顺序性.对消息进行编号,1,2,3,4--消费时按照编号的顺序去消费消息.这样就可以保证消息 按照一定顺序执行.
- 如何保证MQ的顺序性?比如Kafka
三.如何保证消息的顺序性 1. rabbitmq 拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点:或者就一个queue但是对应一个consumer,然后 ...
随机推荐
- 动态添加html事件无响应
问题描述:在页面中动态使用js添加的html中设置了onclick事件,生产页面后点击事件无效并提示:Cannot read property 'xxx' of undefined 如: $('.te ...
- C#添加log4日志
第一步导入log4net 在vs的程序包管理器控制台中执行命令 NuGet\Install-Package log4net -Version 2.0.0 第二步加帮助类HttpHelper using ...
- excel表格粘贴到网页的功能
背景 项目有表格功能,表格过大,一个一个填,过于麻烦. 需要从excel表复制的功能. 过程 监听paste事件,根据事件提供的clipboardData属性,获取数据. 根据换行符 \n 和tab符 ...
- Linux通配符和转移字符(扩展匹配文件名)、man帮助文档的使用
- 我喜欢 amy 同学的腿……
1.我是人间之屑,这点我先承认大约不会错,但并不代表我的一切都是 hitonokuzu 为了防止对成语的吴勇(迫真),这就是人渣的意思. 2.看来亚里士多德的观点还是没有过时,老祖宗说的对.老祖宗指的 ...
- CF1326G 题解
题意: 蛛网树是一颗平面树,满足点是该树的凸包的顶点上等价于其是叶子. 给定一个平面树,求有多少种对点集的划分,使得每个划分出来的集合都是蛛网树. Solution 考虑树形 dp.设 \(f_u\) ...
- Yarn提交Flink任务参数介绍
一.参数介绍 作业模式:yarn-per-job 基本参数 描述 -ynm(Custom Yarnname) 自定义的Yarn名字 -yqu 指定Yarn队列名 -yn (TaskManager) ...
- 告别 DeepSeek 系统繁忙,七个 DeepSeek 曲线救国平替入口,官网崩溃也能用!
前言 DeepSeek作为一款备受瞩目的国产大模型,以其强大的功能和卓越的性能赢得了众多用户的青睐.然而,随着用户量的激增,DeepSeek官网近期频繁遭遇服务器繁忙甚至崩溃的问题,给广大用户带来了不 ...
- 【杂谈】主键ID如何选择——自增数 OR UUID?
1.生成位置如何影响选择? 数据库往返时间 使用自增数时,ID是由数据库在执行INSERT操作时生成的:而UUID则可以在应用层生成. 考虑这样的场景: 一个方法需要插入A和B两个实体.其中B的数据需 ...
- [HAOI2018] 染色 题解
第一眼肯定想到容斥.设 \(G(k)\) 表示至少有 \(k\) 种颜色符合要求,\(F(k)\) 表示恰好有 \(k\) 种颜色符合要求.显然 \(k\) 的上界 \(t=\min(m,\lfloo ...