RocketMQ部署
Spring Boot接入
RocketMQ-Spring 项目,RocketMQ 对 Spring 的集成支持。主要有两方面的功能:
- 功能一:支持 Spring Message 规范,方便开发者从其它 MQ 快速切换到 RocketMQ 。
- 功能二:帮助开发者在 Spring Boot 中快速集成 RocketMQ 。
Spring Message
Spring Messaging 是 Spring Framework 4 中添加的模块,是Spring 与消息系统集成的一个扩展性的支持。它实现了从基于 JmsTemplate 的简单的使用 JMS 接口到异步接收消息的一整套完整的基础架构,Spring AMQP 提供了该协议所要求的类似的功能集。在与 Spring Boot 的集成后,它拥有了自动配置能力,能够在测试和运行时与相应的消息传递系统进行集成。 单纯对于客户端而言,Spring Messaging 提供了一套抽象的 API 或者说是约定的标准,对消息发送端和消息接收端的模式进行规定,不同的消息中间件提供商可以在这个模式下提供自己的 Spring 实现:
- 在消息发送端,需要实现的是一个 XXXTemplate 形式的 Java Bean ,结合 Spring Boot 的自动化配置选项提供多个不同的发送消息方法;
- 在消息的消费端,是一个 XXXMessageListener 接口(实现方式通常会使用一个注解来声明一个消息驱动的 POJO ),提供回调方法来监听和消费消息,这个接口同样可以使用 Spring Boot 的自动化选项和一些定制化的属性。
可参考文章:
juejin.cn/post/714025… xie.infoq.cn/article/4b9…
引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>pers.yefeng</groupId>
<artifactId>RocketMQ</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>RocketMQ</name>
<description>RocketMQ</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
本来是使用的Spring Boot 3.0.2版本,但在接入时存在问题,无法自动装配RocketMQTemplate
等对象。将版本改回2.7.8,马上就可以了。
配置
# rocketmq 配置项,对应 RocketMQProperties 配置类
rocketmq:
name-server: 10.211.55.5:9876 # RocketMQ Namesrv
# Producer 配置项
producer:
group: demo-producer-group # 生产者分组
send-message-timeout: 9000 # 发送消息超时时间,单位:毫秒。默认为 3000 。
compress-message-body-threshold: 4096 # 消息压缩阀值,当消息体的大小超过该阀值后,进行消息压缩。默认为 4 * 1024B
max-message-size: 4194304 # 消息体的最大允许大小。。默认为 4 * 1024 * 1024B
retry-times-when-send-failed: 2 # 同步发送消息时,失败重试次数。默认为 2 次。
retry-times-when-send-async-failed: 2 # 异步发送消息时,失败重试次数。默认为 2 次。
retry-next-server: false # 发送消息给 Broker 时,如果发送失败,是否重试另外一台 Broker 。默认为 false
enable-msg-trace: true # 是否开启消息轨迹功能。默认为 true 开启。可阅读 https://github.com/apache/rocketmq/blob/master/docs/cn/msg_trace/user_guide.md 文档
# customized-trace-topic: RMQ_SYS_TRACE_TOPIC # 自定义消息轨迹的 Topic 。默认为 RMQ_SYS_TRACE_TOPIC 。
debug: true
发送消息
发送消息,可以使用默认的的生产者:DefaultMQProducer
,也可以使用RocketMQTemplate
实现。
@Component
@Slf4j
public class Producer {
@Autowired
private DefaultMQProducer defaultMQProducer;
/**
* 同步发送
*/
public boolean send(String topic, String tag, String key, String content) {
Message message = new Message();
message.setTopic(topic);
message.setKeys(key);
message.setTags(tag);
message.setBody(content.getBytes());
try {
SendResult send = defaultMQProducer.send(message);
if (null == send) {
return false;
}
log.info("发送消息成功:{}", JSON.toJSONString(message));
return true;
} catch (Exception e) {
log.error("发送消息失败。", e);
}
return false;
}
/**
* 异步发送
*/
public void asyncSend(String topic, String tag, String key, String content) {
Message message = new Message();
message.setTopic(topic);
message.setKeys(key);
message.setTags(tag);
message.setBody(content.getBytes());
try {
defaultMQProducer.send(message, new SendCallback() {
@Override
public void onSuccess(SendResult var1) {
log.info("异步发送消息成功 SendResult:{}", JSON.toJSONString(var1));
}
@Override
public void onException(Throwable var1) {
log.info("异步发送消息失败 ", var1);
}
});
} catch (Exception e) {
log.error("异步发送消息失败。", e);
}
}
监听消息
直接使用注解即可
/**
* @author yefeng
*/
@Slf4j
@Service
@RocketMQMessageListener(topic = "TOPIC_TEST_1", consumerGroup = "my-consumer_test-topic-1")
public class Consumer implements RocketMQListener<String> {
public void onMessage(String message) {
log.info("received message: {}", message);
}
}
事务消息
事务消息的基本流程:
- mq先发送消息,该消息(半事务消息)并不会马上被消费者消费。
- mq会自动回调方法,执行本地事务,根据本地事物的执行结果,决定已发出的消息是否可消费或回滚。
- 如过该条事务消息长时间未消费或回滚时,mq会调用一个回查接口,再次查询本地事务的执行情况。
@Resource private RocketMQTemplate rocketMQTemplate;
/**
*发送事务消息
*/
public void sendTransaction(String topic, String tag, String key, String content) { try {
org.springframework.messaging.Message<String> message1 = MessageBuilder.withPayload(content)
.setHeader(RocketMQHeaders.KEYS, key)
.build();
rocketMQTemplate.sendMessageInTransaction(topic + ":" + tag, message1, null);
log.info("发送事务消息成功:{}", JSON.toJSONString(message1));
} catch (Exception e) {
log.error("发送消息失败。", e);
} }
@RocketMQTransactionListener static class TransactionListenerImpl implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(org.springframework.messaging.Message msg, Object arg) {
// ... local transaction process, return bollback, commit or unknown
log.info("[executeLocalTransaction][执行本地事务,消息:{} arg:{}]", JSON.toJSONString(msg), arg);
// 注意,这是一个模板方法。在调用这个方法之前,RocketMQTemplate 已经使用 Producer 发送了一条事务消息。然后根据该方法执行的返回的 RocketMQLocalTransactionState 结果,提交还是回滚该事务消息。
// 这里,我们为了模拟 RocketMQ 回查 Producer 来获得事务消息的状态,所以返回了 RocketMQLocalTransactionState.UNKNOWN 未知状态。
return RocketMQLocalTransactionState.ROLLBACK;
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(org.springframework.messaging.Message msg) {
// ... check transaction status and return bollback, commit or unknown
log.info("[checkLocalTransaction][回查消息:{}]", JSON.toJSONString(msg));
// 在事务消息长时间未被提交或回滚时,RocketMQ 会回查事务消息对应的生产者分组下的 Producer ,获得事务消息的状态。此时,该方法就会被调用。
// 这里,我们直接返回 RocketMQLocalTransactionState.COMMIT 提交状态。
return RocketMQLocalTransactionState.COMMIT;
}
}
顺序消息
顺序消息是 Apache RocketMQ 提供的一种高级消息类型,支持消费者按照发送消息的先后顺序获取消息,从而实现业务场景中的顺序处理。 相比其他类型消息,顺序消息在发送、存储和投递的处理过程中,更多强调多条消息间的先后顺序关系。 Apache RocketMQ 顺序消息的顺序关系通过消息组(MessageGroup)判定和识别,发送顺序消息时需要为每条消息设置归属的消息组,相同消息组的多条消息之间遵循先进先出的顺序关系,不同消息组、无消息组的消息之间不涉及顺序性。 基于消息组的顺序判定逻辑,支持按照业务逻辑做细粒度拆分,可以在满足业务局部顺序的前提下提高系统的并行度和吞吐能力。
顺序消息分为两个部分,消息产生的顺序与消息消费的顺序。 如需保证消息生产的顺序性,则必须满足以下条件:
- 单一生产者
- 串行发送消息
满足以上条件的生产者,将顺序消息发送至 Apache RocketMQ 后,会保证设置了同一消息组的消息,按照发送顺序存储在同一队列中。服务端顺序存储逻辑如下:
- 相同消息组的消息按照先后顺序被存储在同一个队列。
- 不同消息组的消息可以混合在同一个队列中,且不保证连续。
如需保证消息消费的顺序性,则必须满足以下条件:
- 业务方消费消息时需要严格按照接收---处理---应答的语义处理消息,避免因异步处理导致消息乱序
- 有限重试,避免消息阻塞。
一般业务场景下,同一个生产者可能对接多个下游消费者,不一定所有的消费者业务都需要顺序消费,您可以将生产顺序性和消费顺序性进行差异化组合,应用于不同的业务场景
生产顺序 | 消费顺序 | 顺序性效果 |
---|---|---|
设置消息组,保证消息顺序发送。 | 顺序消费 | 按照消息组粒度,严格保证消息顺序。 同一消息组内的消息的消费顺序和发送顺序完全一致。 |
设置消息组,保证消息顺序发送。 | 并发消费 | 并发消费,尽可能按时间顺序处理。 |
未设置消息组,消息乱序发送。 | 顺序消费 | 按队列存储粒度,严格顺序。 基于 Apache RocketMQ 本身队列的属性,消费顺序和队列存储的顺序一致,但不保证和发送顺序一致。 |
未设置消息组,消息乱序发送。 | 并发消费 | 并发消费,尽可能按照时间顺序处理。 |
顺序发送消息:
/**
* 发送顺序消息
* =
*/
public void sendSequentialMessage(String topic, String tag, String key, String content) {
org.springframework.messaging.Message<String> message = MessageBuilder.withPayload(content)
.setHeader(RocketMQHeaders.KEYS, key)
.build();
try {
rocketMQTemplate.sendOneWayOrderly(topic + ":" + tag, message, "消息分组key");
log.info("发送消息成功:{}", JSON.toJSONString(message));
} catch (Exception e) {
log.error("发送消息失败。", e);
}
}
顺序消费消息,关键在于consumeMode = ConsumeMode.ORDERLY
。
@Slf4j
@Service
@RocketMQMessageListener(topic = "TOPIC_TEST_1", consumerGroup = "consumerGroup-2",
consumeMode = ConsumeMode.ORDERLY
)
public class SequentialMessageConsumer implements RocketMQListener<String> {
public void onMessage(String message) {
log.info("received message: {}", message);
}
}