顺序消息
第一个疑问
队列不是本来就是先进先出,本来就保证了有序性吗,怎么还有顺序消息?
对,没错,队列本身确实是先进先出,保证了顺序,但是在RocketMQ中
一个topic对应到多个topic分片,每一个分片在每一个master broker上
每一块分片又被等分成几个queue,客户端就从这个queue中不断取消息消费
下面是架构图
第二个疑问
为什么要保证消息有序性,更新了商品详情后,发一个消息到队列中,随便哪个消费者取到消息去更新Redis和ES不就行了,要什么顺序?
因为有的消息可能是分阶段发的,比如一个订单分为四个阶段:创建、已付款、扣减库存、完成
对于每个阶段的完成可能需要做不同的事情,比如
在创建后,需要给用户发个确认订单的短信
在付款后,给用户发个恭喜您购得此宝物的消息
在扣减库存后,记录下日志啥的
在完成后,给该商品的销量加一下
注意点
写在最前面😭😭😭
广播消费模式不支持顺序消息
消息类型支持情况
Topic消息类型 | 是否支持事务消息 | 是否支持延迟消息 | 性能 |
---|---|---|---|
无序消息(普通、事务、延迟消息) | 是 | 是 | 最高 |
分区顺序消息 | 否 | 否 | 高 |
全局顺序消息 | 否 | 否 | 一般 |
发送方式支持情况
Topic消息类型 | 是否支持同步发送 | 是否支持异步发送 | 是否支持oneway发送 |
---|---|---|---|
无序消息(普通、事务、延迟消息) | 是 | 是 | 是 |
分区顺序消息 | 是 | 否 | 否 |
全局顺序消息 | 是 | 否 | 否 |
全局有序
因为每一个topic都至少有一个topic分片,每一个分片都有至少4个Queue,想要做到全局有序,那么只能使用1个Queue
这样的话,消费者端的并发度就会急剧下降,所以很少有使用全局有序的消息
分区有序
原理:对于每一个Queue,它的消息做到有序,然后因为一个Queue同时只能被一个消费者消费,就保证了消息的有序性
在默认条件下,broker会把消息按照Round Robin的方式将其放入不同的Queue,每个消费者会和至少 1 个Queue关联
要想做到分区有序,需要做到两点
-
生产者做到消息有序的放入同一个队列
-
消费者做到一个线程在一个队列中取消息
下面就以刚刚提到的订单案例说明分区有序的做法
实体类
package com.zhima.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.RandomUtils;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private long id;
private int state;
public static List<Order> generateOrders(int number) {
List<Order> ans = new ArrayList<>();
for (int i = 0; i < number; i++) {
long orderId = RandomUtils.nextLong() + RandomUtils.nextInt(0, i + 1);
for (int state = 0; state < 4; state++) {
ans.add(new Order(
orderId,
state
));
}
}
return ans;
}
}
生产者
public class Producer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("rocketmq-order-producer-group1");
producer.setNamesrvAddr("localhost:9876");
producer.start();
List<Order> orders = Order.generateOrders();
//发送消息
for (int i = 0; i < orders.size(); i++) {
String body = orders.get(i) + "";
Message message = new Message("order_topic", body.getBytes());
// 消息对象
// 消息队列的选择器
// 选择队列的业务标识(订单ID)
SendResult sendResult = producer.send(message, new MessageQueueSelector() {
/**
*
* @param mqs:队列集合
* @param msg:消息对象本身
* @param arg:业务标识的参数,即业务id
* @return
*/
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Long orderId = (Long) arg;
long queueIndex = orderId % mqs.size();
return mqs.get((int) queueIndex);
}
}, orderSteps.get(i).getOrderId());
System.out.println("发送结果:" + sendResult);
}
producer.shutdown();
}
}
消费者
public class Consumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("rocketmq-order-consumer-group1");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("order_topic", "*");
// 这里实现的是MessageListenerOrderly接口
// 注册该接口的消费者,只能用一个线程去一个队列中去取消息处理
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("线程名:【" + Thread.currentThread().getName() + "】:" + new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
}
}