消息发送
同步消息
// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 设置NameServer的地址
producer.setNamesrvAddr("192.168.80.138:9876");
// 启动Producer实例
producer.start();
for (int i = 0; i < 100; i++) {
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message("yourNormalTopic", "TagA",
("Hello RocketMQ " + i).getBytes(StandardCharsets.UTF_8));
// 发送消息到一个Broker
SendResult sendResult = producer.send(msg);
// 通过sendResult返回消息是否成功送达
System.out.println(sendResult.toString());
}
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
异步消息
// 初始化一个 producer 并设置群组名(Group Name)
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 设置 NameServer 地址,多个地址用逗号分隔
producer.setNamesrvAddr("192.168.80.138:9876");
// 启动 producer
producer.start();
// 设置在异步发送模式下不进行重试(retryTimesWhenSendAsyncFailed 设置为 0 表示不重试)
producer.setRetryTimesWhenSendAsyncFailed(0);
// 创建一条消息,并指定 topic、tag、body 等信息
// Topic 是消息发送的主题,Tag 是消息的标签,用于在消费端进行消息过滤
Message msg = new Message("TopicTest", "TagA", "OrderID188", "Hello world".getBytes(StandardCharsets.UTF_8));
// 异步发送消息,发送结果通过 callback 返回给客户端
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
// 发送成功时的回调函数
System.out.println("消息发送成功!消息ID:" + sendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
// 发送失败时的回调函数
System.out.println("消息发送失败!");
e.printStackTrace();
}
});
// 主线程休眠,保持程序运行,以便能够观察异步发送结果
Thread.sleep(Long.MAX_VALUE);
// 一旦 producer 不再使用,关闭 producer
producer.shutdown();
单向发送消息
发送消息无结果

// 实例化消息生产者Producer
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 设置NameServer的地址
producer.setNamesrvAddr("192.168.80.137:9876");
// 启动Producer实例
producer.start();
// 创建消息,并指定Topic,Tag和消息体
Message msg = new Message("TopicTest2", "TagA211",
("Hello RocketMQ ").getBytes(StandardCharsets.UTF_8));
// 发送单向消息,没有任何返回结果
producer.sendOneway(msg);
// 如果不再发送消息,关闭Producer实例。
producer.shutdown();
消费消息
消费者采用负载均衡方式消费消息,多个消费者共同消费队列消息,每个消费者处理的消息不同
下面的案例:需要开启消费者
广播消费
指同一个消息可以被多个消费者同时消费
关键:consumer.setMessageModel(MessageModel.BROADCASTING);
MessageModel.BROADCASTING:广播模式
// 实例化消息生产者,指定组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
// 指定Namesrv地址信息.
consumer.setNamesrvAddr("192.168.80.138:9876");
// 订阅Topic
consumer.subscribe("TopicTest2", "TagA");
//负载均衡模式消费
consumer.setMessageModel(MessageModel.BROADCASTING);
// 注册回调函数,处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
System.out.printf("新消息: %s %n", new String(msgs.get(0).getBody()));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消息者
consumer.start();
负载均衡模式
关键:consumer.setMessageModel(MessageModel.CLUSTERING);
MessageModel.CLUSTERING:负载均衡
顺序消息
消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。
原理解析:
在默认的情况下消息发送会采取 Round Robin 轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。
但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。

生产者
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("192.168.80.138:9876");
producer.start();
// 1. 构建消息顺序集合
List<OrderStep> orderSteps = OrderStep.buildOrders();
orderSteps.forEach(orderStep -> {
Message msg = new Message("TopicTest2", "Order", "OrderID188", orderStep.toString().getBytes());
try {
// 2. 参数2 选择队列的回调函数,可以根据情况选择队列,参数3 是选择队列的唯一标识,这里是订单ID
producer.send(msg, new MessageQueueSelector() {
// 3. mqs 是所有的队列,msg 是消息对象,arg 就是我们传递的唯一标识
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
// 4. 获取订单ID,根据算法获取当前ID的索引,返回该队列即可
long orderId = (long) arg;
int index = (int) (orderId % mqs.size());
return mqs.get(index);
}
}, orderStep.getOrderId());
} catch (MQClientException | RemotingException | InterruptedException | MQBrokerException e) {
throw new RuntimeException(e);
}
});
// 一旦producer不再使用,关闭producer
producer.shutdown();
消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("TopicTest3", "*");
// 注册回调函数,处理消息--保证顺序类 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();
System.out.println("消费者启动成功!");
延迟消息
顾名思义:就是延迟一定的时间发送消息
通过 Message 的setDelayTimeLevel 方法设置延迟等级。
生产者
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("192.168.80.138:9876");
producer.start();
for (int i = 0; i < 10; i++) {
Message msg = new Message("DelayTopic", "延迟", "OrderID188", ("hello word--" + i).getBytes());
// 设置延迟等级
msg.setDelayTimeLevel(2);
SendResult result = producer.send(msg);
System.out.println("发送结果:" + result.toString());
}
producer.shutdown();
消费者没有变动
延迟等级信息
| 投递等级(delay level) | 延迟时间 | 投递等级(delay level) | 延迟时间 |
|---|---|---|---|
| 1 1s | 10 | 6min | |
| 2 | 5s | 11 | 7min |
| 3 | 10s | 12 | 8min |
| 4 | 30s | 13 | 9min |
| 5 | 1min | 14 | 10min |
| 6 | 2min | 15 | 20min |
| 7 | 3min | 16 | 30min |
| 8 | 4min | 17 | 1h |
| 9 | 5min | 18 | 2h |
批量消息
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("192.168.80.138:9876");
producer.start();
// 批量发送
ArrayList<Message> list = new ArrayList<>();
Message msg1 = new Message("DelayTopic", "批量", "OrderID188", ("hello word--1").getBytes());
Message msg2 = new Message("DelayTopic", "批量", "OrderID188", ("hello word--2").getBytes());
Message msg3 = new Message("DelayTopic", "批量", "OrderID188", ("hello word--3").getBytes());
Collections.addAll(list, msg1, msg2, msg3);
SendResult result = producer.send(list);
System.out.println("发送结果:" + result.toString());
producer.shutdown();
如果消息的总长度可能大于4MB时,这时候最好把消息进行分割
public class ListSplitter implements Iterator<List<Message>> {
private final int SIZE_LIMIT = 1024 * 1024 * 4;
private final List<Message> messages;
private int currIndex;
public ListSplitter(List<Message> messages) {
this.messages = messages;
}
@Override
public boolean hasNext() {
return currIndex < messages.size();
}
@Override
public List<Message> next() {
int nextIndex = currIndex;
int totalSize = 0;
for (; nextIndex < messages.size(); nextIndex++) {
Message message = messages.get(nextIndex);
int tmpSize = message.getTopic().length() + message.getBody().length;
Map<String, String> properties = message.getProperties();
for (Map.Entry<String, String> entry : properties.entrySet()) {
tmpSize += entry.getKey().length() + entry.getValue().length();
}
tmpSize = tmpSize + 20; // 增加日志的开销20字节
if (tmpSize > SIZE_LIMIT) {
//单个消息超过了最大的限制
//忽略,否则会阻塞分裂的进程
if (nextIndex - currIndex == 0) {
//假如下一个子列表没有元素,则添加这个子列表然后退出循环,否则只是退出循环
nextIndex++;
}
break;
}
if (tmpSize + totalSize > SIZE_LIMIT) {
break;
} else {
totalSize += tmpSize;
}
}
List<Message> subList = messages.subList(currIndex, nextIndex);
currIndex = nextIndex;
return subList;
}
}
//把大的消息分裂成若干个小的消息
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
try {
List<Message> listItem = splitter.next();
producer.send(listItem);
} catch (Exception e) {
e.printStackTrace();
//处理error
}
}
过滤消息
消费者进行消费时,可以过滤出自己想要的消息,比如根据 tag 过滤和 sql 过滤
生产者
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("192.168.80.138:9876");
producer.start();
// tag 过滤
Message msg = new Message("DelayTopic", "tag1", "OrderID188", ("hello word").getBytes());
SendResult result = producer.send(msg);
System.out.println("发送结果:" + result.toString());
producer.shutdown();
消费者
消费者 subscribe 的第二个参数,就不能写 * (全部)了,要写对应的 tag 标签或者 sql语法。
如果想同时消费多个标签。要这样写 tag1 || tag2
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("192.168.80.138:9876");
consumer.subscribe("DelayTopic", "tag1");
// 注册回调函数,处理消息--保证顺序类 MessageListenerOrderly
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
System.out.println("消息ID:【" + msg.getMsgId() + "】");
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
//启动消息者
consumer.start();
System.out.println("消费者启动成功!");
sql 过滤
RocketMQ只定义了一些基本语法来支持这个特性。你也可以很容易地扩展它。
- 数值比较,比如:>,>=,<,<=,BETWEEN,=;
- 字符比较,比如:=,<>,IN;
- IS NULL 或者 IS NOT NULL;
- 逻辑符号 AND,OR,NOT;
常量支持类型为:
- 数值,比如:123,3.1415;
- 字符,比如:'abc',必须用单引号包裹起来;
- NULL,特殊的常量
- 布尔值,TRUE 或 FALSE
遇到问题
CODE: 1 DESC: The broker does not support consumer to filter message by SQL92
需要开启对 SQL 的支持
在 broker 的配置文件中添加下面的内容
enablePropertyFilter = true
生产者
这里我通过 putUserProperty 设置的过滤属性有 i 值为数字
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("localhost:9876");
producer.start();
for (int i = 0; i < 10; i++) {
Message msg = new Message("TopicTest2", "tag2", "OrderID188", ("hello word--" + i).getBytes());
// 添加 sql 过滤属性
msg.putUserProperty("i", String.valueOf(i));
SendResult result = producer.send(msg);
System.out.println("发送结果:" + result.toString());
}
producer.shutdown();
消费者
MessageSelector.bySql("i>5") 通过 bySql 设置过滤条件
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("localhost:9876");
// 过滤 i > 5
consumer.subscribe("TopicTest2", MessageSelector.bySql("i>5"));
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
System.out.println(new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
System.out.println("消费者启动成功!");
事务消息

事务:消息发送到 mq 之后,这个时候消息是不可以被消费者知道的,需要 commit 提交之后,消费者才能去消费消息。
- 半消息:消息发送到 mq,但是没有提交
- commit:将半消息提交
- rollback:将消息回滚,重新处理
我们在提交消息或者回滚消息的时候,没有对半消息进行处理,这时候,mq 会检查消息的状态,触发本地的检查方法,我们本地的检查方法,再对消息进行提交或者回滚。
生产者
这里使用三个标签,tag1 事务成功,tag2 回滚,tag3 未处理
发送之后会进入,事务检查方法,进行区分事务是否完成,如果未处理,mq会进行最后的检查。
注意:由于需要 mq 检查,发送完不可以关闭 mq。
发送使用:sendMessageInTransaction,参数2是检查对象,传入null,表示整个对象。
// 1. 创建事务生产者
TransactionMQProducer producer = new TransactionMQProducer("group1");
producer.setNamesrvAddr("localhost:9876");
// 2. 创建事务监听器,这是本地事务入口,用于mq检查事务
producer.setTransactionListener(new TransactionListener() {
// 在该方法中执行本地事务
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
if ("tag1".equals(msg.getTags())) {
// 表示事务执行成功
return LocalTransactionState.COMMIT_MESSAGE;
} else if ("tag2".equals(msg.getTags())) {
// 表示事务执行回滚
return LocalTransactionState.ROLLBACK_MESSAGE;
} else {
// 表示事务执行未知(会触发 mq 的重新检查操作)
return LocalTransactionState.UNKNOW;
}
}
// mq 进行消息回查方法
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
System.out.println("我的消息进行了回查---" + msg.getTags());
return LocalTransactionState.COMMIT_MESSAGE;
}
});
producer.start();
String[] tags = {"tag1", "tag2", "tag3"};
for (int i = 0; i < 3; i++) {
Message msg = new Message("TopicTest2", tags[i], "OrderID188", ("hello word--" + i).getBytes());
// 传入 null 表示对整个对象进行事务控制
SendResult result = producer.sendMessageInTransaction(msg, null);
System.out.println("发送结果:" + result.toString());
}
// 不能关闭了,因为需要进行回查的,关闭了就不可以了。
// producer.shutdown();
消费者和之前一样。
注意:事务消息不支持延迟消息和批量消息,事务检查最多检查15次,超过会废弃该信息。
- 事务消息将在 Broker 配置文件中的参数 transactionMsgTimeout 这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于
transactionMsgTimeout参数。 - 事务性消息可能不止一次被检查或消费。
- 提交给用户的目标主题消息可能会失败,目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证,如果希望确保事务消息不丢失、并且事务完整性得到保证,建议使用同步的双重写入机制。
- 事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。
SpringBoot 中使用 rocketmq
发送消息
@Autowired
private RocketMQTemplate rocketMQTemplate;
// 创建消息
Message message = new Message(topic, tag, keys, body.getBytes());
// 发送消息
rocketMQTemplate.getProducer().send(message);
接收消息
广播模式接收
@Component
@RocketMQMessageListener(
topic = "${mq.order.topic}", // 订阅的消息主题
consumerGroup = "${mq.order.consumer.group}", // 消费者组名
messageModel = MessageModel.BROADCASTING // 消息模式,广播模式
)
public class CancelMQListener implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
// 在这里处理接收到的消息
}
}
实战下单功能
如何保证数据的完整性?
接收到订单信息之后,依次处理扣减库存、扣减优惠券、扣减余额,如果出现异常,分别向扣减服务发处理失败信息,使数据回滚。
如何快速给第三方支付平台做出回应?
如果我们用户支付成功后,我们通过 RPC 同步调用多个服务之后再回复平台,那这样效率太低了。
我们应该在支付成功后,使用 MQ 发送消息,多个服务同时监听支付成功消息,实现 MQ 进行数据分发,提高系统处理性能。
下单流程

如何比较金额
使用 equals() 方法比较 BigDecimal 类型的值通常是安全的,因为它会考虑到两个 BigDecimal 对象的值是否相等。但是,由于 BigDecimal 是一个精确表示十进制的数字类型,因此在某些情况下可能会遇到比较问题。
equals() 方法比较的是两个 BigDecimal 对象的值是否相等,而不是它们的精度。
在比较两个 BigDecimal 对象时,最好使用 compareTo() 方法,它比较两个 BigDecimal 对象的值并返回一个整数,表示它们的相对大小。如果两个 BigDecimal 对象的值相等,则返回 0。
开启服务注册到 zookeeper,并使用dubbo-amdin查看
注意事项:下面两个端口注意切换,每个微服务只能使用一个,qos-port默认22222
dubbo:
application:
qos-port: 22223
protocol:
port: 20882

服务调用序列化报错
fastjson2.JSONException: not support none serializable class
需要再生产方yml文件配置dubbo检查关闭
dubbo:
application:
qos-port: 22225
name: shop-user-service
# Serializable 接口检查模式,Dubbo 中默认配置为 `true` 开启检查
check-serializable: false
# 检查模式分为三个级别:STRICT 严格检查,WARN 告警,DISABLE 禁用
serialize-check-status: DISABLE
设计模式
订阅发布模式
生产者生产信息,消费者订阅这个信息,这样当生产者生产信息,消费者就能获取信息。
生产者将信息发送给代理者,代理者再将信息发送给消费者。
推送模式:push
代理人拿到信息之后,统一进行封装,然后把订阅这个信息的人统一进行广播发送。
拉取模式:pull
代理人拿到信息之后,把信息进行处理,具体的内容不会告诉订阅者,比如告诉订阅者一个ID。
观察者模式
与订阅发布模式的区别