Spring Boot使用RocketMQ

712 阅读7分钟

RocketMQ部署

juejin.cn/post/720028…

Spring Boot接入

RocketMQ-Spring 项目,RocketMQ 对 Spring 的集成支持。主要有两方面的功能:

  • 功能一:支持 Spring Message 规范,方便开发者从其它 MQ 快速切换到 RocketMQ 。
  • 功能二:帮助开发者在 Spring Boot 中快速集成 RocketMQ

www.iocoder.cn/Spring-Boot…

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 的自动化选项和一些定制化的属性。

www.iocoder.cn/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);
    }

}

事务消息

事务消息的基本流程:

  1. mq先发送消息,该消息(半事务消息)并不会马上被消费者消费。
  2. mq会自动回调方法,执行本地事务,根据本地事物的执行结果,决定已发出的消息是否可消费或回滚。
  3. 如过该条事务消息长时间未消费或回滚时,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)判定和识别,发送顺序消息时需要为每条消息设置归属的消息组,相同消息组的多条消息之间遵循先进先出的顺序关系,不同消息组、无消息组的消息之间不涉及顺序性。 基于消息组的顺序判定逻辑,支持按照业务逻辑做细粒度拆分,可以在满足业务局部顺序的前提下提高系统的并行度和吞吐能力。

rocketmq.apache.org/zh/docs/fea…

顺序消息分为两个部分,消息产生的顺序与消息消费的顺序。 如需保证消息生产的顺序性,则必须满足以下条件:

  • 单一生产者
  • 串行发送消息

满足以上条件的生产者,将顺序消息发送至 Apache RocketMQ 后,会保证设置了同一消息组的消息,按照发送顺序存储在同一队列中。服务端顺序存储逻辑如下:

  • 相同消息组的消息按照先后顺序被存储在同一个队列。
  • 不同消息组的消息可以混合在同一个队列中,且不保证连续。

image.png

如需保证消息消费的顺序性,则必须满足以下条件:

  • 业务方消费消息时需要严格按照接收---处理---应答的语义处理消息,避免因异步处理导致消息乱序
  • 有限重试,避免消息阻塞。

一般业务场景下,同一个生产者可能对接多个下游消费者,不一定所有的消费者业务都需要顺序消费,您可以将生产顺序性和消费顺序性进行差异化组合,应用于不同的业务场景

生产顺序消费顺序顺序性效果
设置消息组,保证消息顺序发送。顺序消费按照消息组粒度,严格保证消息顺序。 同一消息组内的消息的消费顺序和发送顺序完全一致。
设置消息组,保证消息顺序发送。并发消费并发消费,尽可能按时间顺序处理。
未设置消息组,消息乱序发送。顺序消费按队列存储粒度,严格顺序。 基于 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);
    }

}

参考文章

www.iocoder.cn/Spring-Boot… github.com/apache/rock…