RocketMQ管理与开发

1.RocketMQ架构

  • 消息模型
    RocketMQ管理与开发_第1张图片

  • 部署模型
    RocketMQ管理与开发_第2张图片

2.安装RocketMQ

2.1 下载源文件

下载地址:https://rocketmq.apache.org/zh/download/

RocketMQ管理与开发_第3张图片

选择一个你需要的版本,Source下载的是源码,需要自行编译;Binary下载的是编译后的二进制文件,可直接运行。此处下载编译后二进制版本。注意:下载不区分操作系统。

2.2 无需安装,解压到本地磁盘即可

下载后将得到一个压缩包:
rocketmq-all-5.1.0-bin-release.zip
解压后得到一个文件夹:
rocketmq-all-5.1.0-bin-release
将解压后的文件放到磁盘任意位置:我放在了D盘下:D:\ProgramFiles\rocketmq-all-5.1.0-bin-release

3.配置RocketMQ

3.1 安装JDK并配置环境变量

RocketMQ的运行基于JDK8或以上版本,安装及配置略。

3.2 配置MQ环境变量:

RocketMQ管理与开发_第4张图片

3.3 MQ配置修改

修改bin目录下的文件:

RocketMQ管理与开发_第5张图片

修改conf目录下的文件:

RocketMQ管理与开发_第6张图片

我这里全都使用默认配置,就不作修改了。

4.启动RocketMQ

此处是Windowos平台的启动
打开cmd命令行窗口,切换到MQ安装目录下的bin目录:

cd D:\ProgramFiles\rocketmq-all-5.1.0-bin-release\bin

4.1 启动NameServer

start mqnamesrv.cmd

4.2 启动Broker

start mqbroker.cmd -n 0.0.0.0:9876

按配置文件启动:

start mqbroker.cmd -n 0.0.0.0:9876 -c D:\ProgramFiles\rocketmq-all-5.1.0-bin-release\conf\broker.conf

Tips:

启动时有可能会报错找不到JDK,是因为JAVA_HOME的路径包含了空格,
解决办法就是修改 bin/runbroker.cmd的配置,给%CLASSPATH%添加一对双引号:

RocketMQ管理与开发_第7张图片

5.RocketMQ运维控制台

5.1 下载控制台项目

下载地址:https://github.com/apache/rocketmq-dashboard

RocketMQ管理与开发_第8张图片

下载后解压,导入IDEA,按SpringBoot项目正常启动即可
启动前需要修改application.yml里面的配置:

RocketMQ管理与开发_第9张图片

5.2 控制台功能介绍

搭建好了RocketMQ的运维控制台之后,直接在浏览器打开,默认会进入到驾驶舱(Dashboard)
整体横向菜单分为九个部分:

5.2.1 运维(OPS)

主要是设置nameserver和配置vipchannel。

RocketMQ管理与开发_第10张图片

5.2.2 驾驶舱(Dashboard)

控制台的dashboard,可以分别按broker和主题来查看消息的数量和趋势。

RocketMQ管理与开发_第11张图片

5.2.3 集群(Cluster)

整个RocketMq的集群情况,包括分片,编号,地址,版本,消息生产和消息消费的TPS等,这个在做性能测试的时候可以作为数据指标。

RocketMQ管理与开发_第12张图片

5.2.4 主题(Topic)

可以新增/更新topic;也看查看topic的信息,如状态,路由,消费者管理和发送消息等。

RocketMQ管理与开发_第13张图片

5.2.5 消费者(Consumer)

可以在当前broker中查看/新建消费者group,包括消费者信息和消费进度。

RocketMQ管理与开发_第14张图片

5.2.6 生产者(Producer)

可以在当前broker中查看生产组下的生产者group,包生产者信息和生产者状态。

RocketMQ管理与开发_第15张图片

5.2.7 消息(Message)

可以按照topc,messageID,messageKey分别查询具体的消息。

RocketMQ管理与开发_第16张图片

5.2.8 死信消息(DLQMessage)

达到最大重试次数后依然不能被正常消费的消息,一般需人工处理。

RocketMQ管理与开发_第17张图片

5.2.9 消息轨迹(MessageTrace)

跟踪消息发送和消费的轨迹,默认情况下,RocketMQ 是不开启轨迹消息的,需要我们手工开启。

RocketMQ管理与开发_第18张图片

其中最常用的是集群,主题,消费者和消息这四部分。

6.RocketMQ开发

创建项目:rocketmq-clients项目,结构如下:

RocketMQ管理与开发_第19张图片

引入RocketMQ的Maven依赖,目前的最新版本为5.1.0

<dependency>      
    <groupId>org.apache.rocketmqgroupId>      
    <artifactId>rocketmq-clientartifactId>      
    <version>4.7.0version>
dependency>

引入netty的依赖,因为rocketmq基于netty开发


<dependency>      
    <groupId>io.nettygroupId>      
    <artifactId>netty-allartifactId>      
    <version>4.1.86.Finalversion>
dependency>

application.yml配置文件:

server:
  port: 8889
  servlet:
    encoding:
      charset: UTF-8
      enabled: true
      force: true

spring:
  application:
    name: rocketmq-clients

logging:
  config: classpath:logback.xml

rocketmq:
  # 生产者配置
  producer:
    # 生产组名称
    groupName: PRODUCER_GROUP_1
    # MQ NameServer地址
    nameSrvAddr: 127.0.0.1:9876
    # 消息最大长度,单位:B 默认:4M = 1024 * 1024 * 4
    maxMsgSize: 4194304
    # 消息发送超时时间,单位:ms,默认:3s
    timeOut: 3000
    # 发送失败重试次数
    retryTimes: 2
    # 事务生产者分组名
    transGroupName: TRANSACTION_PRODUCER_GROUP_1

  # 消费者配置
  consumer:
    # 消费组名称
    groupName: CONSUME_GROUP_1
    # MQ NameServer地址
    nameSrvAddr: 127.0.0.1:9876
    # 订阅的topic+tags 格式(自己约定的): topic~tag1;topic~tag2;topic~* (*代表所有tag)
    topics: TEST_TOPIC~*;TEST_TOPIC_TRANSACTION~*
    # 消费者最小线程数
    minThreads: 1
    # 消费者最大线程数
    maxThreads: 3
    # 单词最大消费数
    maxConsumeSize: 1

6.1生产者(Producer)

6.1.1 生产者配置

配置参数查看application.yml中rocketmq.producer下的配置
配置类MQProducerConfigure:

package com.vz.rocketmq.clients.config;

import com.vz.rocketmq.clients.transaction.MQTransactionListener;
import lombok.Data;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author visy.wang
 * @description: RocketMQ生产者配置
 * @date 2023/3/17 15:30
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "rocketmq.producer")
public class MQProducerConfigure {
    public static final Logger logger = LoggerFactory.getLogger(MQProducerConfigure.class);

    /**
     * 分组组名称
     */
    private String groupName;
    /**
     * NameServer地址
     */
    private String nameSrvAddr;
    /**
     * 消息最大值
     */
    private Integer maxMsgSize;
    /**
     * 消息发送超时时间
     */
    private Integer timeOut;
    /**
     * 发送失败重试次数
     */
    private Integer retryTimes;

    /**
     * 事务生产者分组名
     */
    private String transGroupName;

    @Autowired
    private MQTransactionListener transactionListener;

	//创建默认生产者
    @Bean
    public DefaultMQProducer defaultMQProducer() throws MQClientException {
        logger.info("MQ生产者正在创建...");

        DefaultMQProducer producer = new DefaultMQProducer(groupName);
        producer.setNamesrvAddr(nameSrvAddr);
        producer.setVipChannelEnabled(false);
        producer.setMaxMessageSize(maxMsgSize);
        producer.setSendMsgTimeout(timeOut);
        producer.setRetryTimesWhenSendAsyncFailed(retryTimes);
        producer.start();

        logger.info("MQ生产者创建成功!");
        return producer;
    }

	//创建事务生产者
    @Bean
    public TransactionMQProducer transactionMQProducer() throws MQClientException{
        logger.info("MQ生产者(事务消息)正在创建...");
        TransactionMQProducer producer = new TransactionMQProducer(transGroupName);
        producer.setNamesrvAddr(nameSrvAddr);
        //创建事务监听器
        //MQTransactionListener transactionListener = new MQTransactionListener();
        //创建回查的线程池
        int corePoolSize = 2, maxPoolSize = 5;
        long keepAliveTime = 100;
        ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(2000);
        ExecutorService executorService = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, blockingQueue, r -> {
            Thread thread = new Thread(r);
            thread.setName("RMQ-事务回查线程");
            return thread;
        });
        //设置事务回查的线程池,如果不设置也会默认生成一个
        producer.setExecutorService(executorService);
        //设置事务监听器
        producer.setTransactionListener(transactionListener);

        producer.start();

        logger.info("MQ生产者(事务消息)创建成功!");
        return producer;
    }
}
6.1.2 发送消息

服务接口MQProducerService:

package com.vz.rocketmq.clients.producer;

import com.vz.rocketmq.clients.enums.MQTopic;
import com.vz.rocketmq.clients.enums.MsgTag;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * @author visy.wang
 * @description: 生产者服务
 * @date 2023/3/17 13:52
 */
public interface MQProducerService {
    Logger logger = LoggerFactory.getLogger(MQProducerService.class);

    /**
     * 发送消息(同步)
     * @param topic 消息发送的目标Topic名称
     * @param msgTag 消息Tag,用于消费端根据指定Tag过滤消息
     * @param msgKey 消息索引键,可根据关键字精确查找某条消息
     * @param msg 消息内容
     * @return 是否发送成功
     * ====================================================================
     * 同步发送:
     * 同步发送是最常用的方式,是指消息发送方发出一条消息后,
     * 会在收到服务端同步响应之后才发下一条消息的通讯方式。
     * 可靠的同步传输被广泛应用于各种场景,如重要的通知消息、短消息通知等。
     */
    boolean sendMessage(MQTopic topic, MsgTag msgTag, String msgKey, Object msg);
    boolean sendMessage(MQTopic topic, MsgTag msgTag, Object msg);
    boolean sendMessage(MQTopic topic, Object msg);

    /**
     * 发送消息(顺序)
     * @param topic 消息发送的目标Topic名称
     * @param msgTag 消息Tag,用于消费端根据指定Tag过滤消息
     * @param msgKey 消息索引键,可根据关键字精确查找某条消息
     * @param msg 消息内容
     * @param buzId 业务ID(用于确定队列索引的字段)
     * @param selector 队列选择器 <队列总数,选中的队列索引>
     * @return 是否发送成功
     * ====================================================================
     * 顺序消息:
     * RocketMQ通过生产者和服务端的协议保障单个生产者串行地发送消息,并按序存储和持久化。
     * 如需保证消息生产的顺序性,则必须满足以下条件:
     * 1.单一生产者:消息生产的顺序性仅支持单一生产者,不同生产者分布在不同的系统,
     * 即使设置相同的分区键,不同生产者之间产生的消息也无法判定其先后顺序。
     * 2.串行发送:生产者客户端支持多线程安全访问,
     * 但如果生产者使用多线程并行发送,则不同线程间产生的消息将无法判定其先后顺序。
     */
    boolean sendMessageOrderly(MQTopic topic, MsgTag msgTag, String msgKey, Object msg,
                               Object buzId, Function<Integer, Integer> selector);
    boolean sendMessageOrderly(MQTopic topic, MsgTag msgTag, Object msg,
                               Object buzId, Function<Integer, Integer> selector);
    boolean sendMessageOrderly(MQTopic topic, Object msg,
                               Object buzId, Function<Integer, Integer> selector);

    /**
     * 发送消息(批量)
     * @param topic 消息发送的目标Topic名称
     * @param msgTag 消息Tag,用于消费端根据指定Tag过滤消息
     * @param msgKey 消息索引键,可根据关键字精确查找某条消息
     * @param msgList 消息内容列表
     * @return 是否发送成功
     * ====================================================================
     * 批量同步发送:
     * 在对吞吐率有一定要求的情况下,可以将一些消息聚成一批以后进行发送,
     * 可以增加吞吐率,并减少API和网络调用次数。
     * 要注意的是批量消息的大小不能超过 1MiB(否则需要自行分割),其次同一批 batch 中 topic 必须相同。
     */
    boolean sendMessageBatch(MQTopic topic, MsgTag msgTag, String msgKey, List<?> msgList);
    boolean sendMessageBatch(MQTopic topic, MsgTag msgTag, List<?> msgList);
    boolean sendMessageBatch(MQTopic topic, List<?> msgList);

    /**
     * 发送消息(延迟)
     * @param topic 消息发送的目标Topic名称
     * @param msgTag 消息Tag,用于消费端根据指定Tag过滤消息
     * @param msgKey 消息索引键,可根据关键字精确查找某条消息
     * @param msg 消息内容
     * @param delayLevel 延迟等级: 1-18
     * @return 是否发送成功
     * ====================================================================
     * 延迟消息:
     * 延时消息的实现逻辑需要先经过定时存储等待触发,延时时间到达后才会被投递给消费者。
     * 因此,如果将大量延时消息的定时时间设置为同一时刻,
     * 则到达该时刻后会有大量消息同时需要被处理,会造成系统压力过大,导致消息分发延迟,影响定时精度。
     */
    boolean sendMessageWithDelay(MQTopic topic, MsgTag msgTag, String msgKey, Object msg, int delayLevel);
    boolean sendMessageWithDelay(MQTopic topic, MsgTag msgTag, Object msg, int delayLevel);
    boolean sendMessageWithDelay(MQTopic topic, Object msg, int delayLevel);

    /**
     * 发送消息(异步)
     * @param topic 消息发送的目标Topic名称
     * @param msgTag 消息Tag,用于消费端根据指定Tag过滤消息
     * @param msgKey 消息索引键,可根据关键字精确查找某条消息
     * @param msg 消息内容
     * @param callback 异步回调 <是否发送成功,msgId(成功)或者错误信息(失败)>
     * ====================================================================
     * 异步发送:
     * 异步发送是指发送方发出一条消息后,不等服务端返回响应,接着发送下一条消息的通讯方式。
     * 异步发送一般用于链路耗时较长,对响应时间较为敏感的业务场景。
     * 例如,视频上传后通知启动转码服务,转码完成后通知推送转码结果等。
     */
    void sendMessageAsync(MQTopic topic, MsgTag msgTag, String msgKey,
                          Object msg, BiConsumer<Boolean,String> callback);
    void sendMessageAsync(MQTopic topic, MsgTag msgTag,
                          Object msg, BiConsumer<Boolean,String> callback);
    void sendMessageAsync(MQTopic topic, Object msg,
                          BiConsumer<Boolean,String> callback);

    /**
     * 发送消息(单向)
     * @param topic 消息发送的目标Topic名称
     * @param msgTag 消息Tag,用于消费端根据指定Tag过滤消息
     * @param msgKey 消息索引键,可根据关键字精确查找某条消息
     * @param msg 消息内容
     * =====================================================================
     * 单向模式发送:
     * 发送方只负责发送消息,不等待服务端返回响应且没有回调函数触发,即只发送请求不等待应答。
     * 此方式发送消息的过程耗时非常短,一般在微秒级别。
     * 适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。
     */
    void sendMessageOneway(MQTopic topic, MsgTag msgTag, String msgKey, Object msg);
    void sendMessageOneway(MQTopic topic, MsgTag msgTag, Object msg);
    void sendMessageOneway(MQTopic topic, Object msg);

    /**
     * 事务消息发送
     * @param topic 消息发送的目标Topic名称
     * @param msgTag 消息Tag,用于消费端根据指定Tag过滤消息
     * @param msgKey 消息索引键,可根据关键字精确查找某条消息
     * @param msg 消息内容
     * @return 发送结果: 本地事务执行状态
     */
    LocalTransactionState sendTransactionMessage(MQTopic topic, MsgTag msgTag, String msgKey, Object msg);
    LocalTransactionState sendTransactionMessage(MQTopic topic, MsgTag msgTag, Object msg);
    LocalTransactionState sendTransactionMessage(MQTopic topic, Object msg);
}

服务实现类MQProducerServiceImpl:

package com.vz.rocketmq.clients.producer;

import com.alibaba.fastjson.JSON;
import com.vz.rocketmq.clients.enums.MQTopic;
import com.vz.rocketmq.clients.enums.MsgTag;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author visy.wang
 * @description: 生产者服务实现
 * @date 2023/3/17 13:52
 */
@Service
public class MQProducerServiceImpl implements MQProducerService {
    /**
     * 默认生产者
     * 一般来说,创建一个生产者就够了
     */
    @Autowired
    private DefaultMQProducer defaultMQProducer;
    /**
     * 事务生产者
     */
    @Autowired
    private TransactionMQProducer transactionMQProducer;

    @Override
    public boolean sendMessage(MQTopic topic, MsgTag msgTag, String msgKey, Object msg) {
        try{
            //构建消息
            Message message = buildMessage(topic, msgTag, msgKey, msg);
            //发送消息
            SendResult sendResult = defaultMQProducer.send(message);
            logger.info("消息发送成功,msgId={}", sendResult.getMsgId());
            return true;
        }catch(Exception e){
            logger.info("消息发送异常:{}", e.getMessage(), e);
        }
        return false;
    }

    @Override
    public boolean sendMessage(MQTopic topic, MsgTag msgTag, Object msg) {
        return sendMessage(topic, msgTag, null, msg);
    }

    @Override
    public boolean sendMessage(MQTopic topic, Object msg) {
        return sendMessage(topic, null, null, msg);
    }

    @Override
    public boolean sendMessageOrderly(MQTopic topic, MsgTag msgTag, String msgKey, Object msg,
                                      Object orderId, Function<Integer, Integer> selector) {
        try{
            Message message = buildMessage(topic, msgTag, msgKey, msg);
            SendResult sendResult = defaultMQProducer.send(message, (List<MessageQueue> mqs, Message _msg, Object arg) -> {
                int index = selector.apply(mqs.size());
                return mqs.get(index);
            }, orderId);
            logger.info("消息发送成功,msgId={}", sendResult.getMsgId());
            return true;
        }catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e){
            logger.info("消息发送异常:{}", e.getMessage(), e);
        }
        return false;
    }

    @Override
    public boolean sendMessageOrderly(MQTopic topic, MsgTag msgTag, Object msg, Object buzId, Function<Integer, Integer> selector) {
        return sendMessageOrderly(topic, msgTag, null, msg, buzId, selector);
    }

    @Override
    public boolean sendMessageOrderly(MQTopic topic, Object msg, Object buzId, Function<Integer, Integer> selector) {
        return sendMessageOrderly(topic, null, null, msg, buzId, selector);
    }

    @Override
    public boolean sendMessageBatch(MQTopic topic, MsgTag msgTag, String msgKey, List<?> msgList) {
        try{
            //构建消息
            List<Message> messageList = buildMessageList(topic, msgTag, msgKey, msgList);
            //发送消息
            SendResult sendResult = defaultMQProducer.send(messageList);
            logger.info("批量消息发送成功,msgId={}", sendResult.getMsgId());
            return true;
        }catch(Exception e){
            logger.info("批量消息发送异常:{}", e.getMessage(), e);
        }
        return false;
    }

    @Override
    public boolean sendMessageBatch(MQTopic topic, MsgTag msgTag, List<?> msgList) {
        return sendMessageBatch(topic, msgTag, null, msgList);
    }

    @Override
    public boolean sendMessageBatch(MQTopic topic, List<?> msgList) {
        return sendMessageBatch(topic, null, null, msgList);
    }

    @Override
    public boolean sendMessageWithDelay(MQTopic topic, MsgTag msgTag, String msgKey, Object msg, int delayLevel) {
        try{
            //构建消息
            Message message = buildMessage(topic, msgTag, msgKey, msg);
            //设置延迟等级
            message.setDelayTimeLevel(delayLevel);
            //发送消息
            SendResult sendResult = defaultMQProducer.send(message);
            logger.info("消息发送成功,msgId={}", sendResult.getMsgId());
            return true;
        }catch(Exception e){
            logger.info("消息发送异常:{}", e.getMessage(), e);
        }
        return false;
    }

    @Override
    public boolean sendMessageWithDelay(MQTopic topic, MsgTag msgTag, Object msg, int delayLevel) {
        return sendMessageWithDelay(topic, msgTag, null, msg, delayLevel);
    }

    @Override
    public boolean sendMessageWithDelay(MQTopic topic, Object msg, int delayLevel) {
        return sendMessageWithDelay(topic, null, null, msg, delayLevel);
    }

    @Override
    public void sendMessageAsync(MQTopic topic, MsgTag msgTag, String msgKey,
                                 Object msg, BiConsumer<Boolean,String> callback) {
        try{
            //构建消息
            Message message = buildMessage(topic, msgTag, msgKey, msg);
            //发送消息(异步)
            defaultMQProducer.send(message, new SendCallback() {
                @Override
                public void onSuccess(SendResult sendResult) {
                    logger.info("异步消息发送成功,msgId={}", sendResult.getMsgId());
                    callback.accept(true, sendResult.getMsgId());
                }

                @Override
                public void onException(Throwable throwable) {
                    logger.info("异步消息发送失败:{}", throwable.getMessage(), throwable);
                    callback.accept(false, throwable.getMessage());
                }
            });
        }catch(Exception e){
            logger.info("消息发送异常:{}", e.getMessage(), e);
        }
    }

    @Override
    public void sendMessageAsync(MQTopic topic, MsgTag msgTag,
                                 Object msg, BiConsumer<Boolean, String> callback) {
        sendMessageAsync(topic, msgTag, null, msg, callback);
    }

    @Override
    public void sendMessageAsync(MQTopic topic, Object msg,
                                 BiConsumer<Boolean, String> callback) {
        sendMessageAsync(topic, null, null, msg, callback);
    }

    @Override
    public void sendMessageOneway(MQTopic topic, MsgTag msgTag, String msgKey, Object msg) {
        try{
            //构建消息
            Message message = buildMessage(topic, msgTag, msgKey, msg);
            //发送消息(单向,即只发送请求不等待应答)
            defaultMQProducer.sendOneway(message);
        }catch(Exception e){
            logger.info("消息发送异常:{}", e.getMessage(), e);
        }
    }

    @Override
    public void sendMessageOneway(MQTopic topic, MsgTag msgTag, Object msg) {
        sendMessageOneway(topic, msgTag, null, msg);
    }

    @Override
    public void sendMessageOneway(MQTopic topic, Object msg) {
        sendMessageOneway(topic, null, null, msg);
    }

    @Override
    public LocalTransactionState sendTransactionMessage(MQTopic topic, MsgTag msgTag,
                                                        String msgKey, Object msg){
        try{
            //构建消息
            Message message = buildMessage(topic, msgTag, msgKey, msg);
            //发送事务消息
            TransactionSendResult sendResult = transactionMQProducer.sendMessageInTransaction(message, null);
            logger.info("事务消息发送成功,msgId={}, sendStatus={}", sendResult.getMsgId(), sendResult.getSendStatus());
            return sendResult.getLocalTransactionState();
        }catch (MQClientException e){
            logger.info("事务消息发送异常:{}", e.getMessage(), e);
            return LocalTransactionState.UNKNOW;
        }
    }

    @Override
    public LocalTransactionState sendTransactionMessage(MQTopic topic, MsgTag msgTag, Object msg) {
        return sendTransactionMessage(topic, msgTag, null, msg);
    }

    @Override
    public LocalTransactionState sendTransactionMessage(MQTopic topic, Object msg) {
        return sendTransactionMessage(topic, null, null, msg);
    }

    /**
     * 构建消息
     * @param topic 主题
     * @param msgTag 消息Tag
     * @param msgKey 消息Key
     * @param msg 消息
     * @return 消息
     */
    private Message buildMessage(MQTopic topic, MsgTag msgTag, String msgKey, Object msg){
        String topicValue = topic.getValue();
        byte[] body = JSON.toJSONString(msg).getBytes(StandardCharsets.UTF_8);
        if(Objects.nonNull(msgTag)){
            String msgTagValue = msgTag.getValue();
            if(Objects.nonNull(msgKey)){
                return new Message(topicValue, msgTagValue, msgKey, body);
            }
            return new Message(topicValue, msgTagValue, body);
        }
        return new Message(topicValue, body);
    }

    /**
     * 构建批量消息
     * @param topic 主题
     * @param msgTag 消息Tag
     * @param msgKey 消息Key
     * @param msgList 消息列表
     * @return 消息列表
     */
    private List<Message> buildMessageList(MQTopic topic, MsgTag msgTag, String msgKey, List<?> msgList){
        return msgList.stream().map(msg -> {
            return buildMessage(topic, msgTag, msgKey, msg);
        }).collect(Collectors.toList());
    }
}
6.1.3 消息类型
  • 同步消息

同步发送是最常用的方式,是指消息发送方发出一条消息后,会在收到服务端同步响应之后才发下一条消息的通讯方式。
可靠的同步传输被广泛应用于各种场景,如重要的通知消息、短消息通知等。

//构建消息
Message message = buildMessage(topic, msgTag, msgKey, msg);
//发布消息
SendResult sendResult = defaultMQProducer.send(message);
  • 顺序消息

RocketMQ通过生产者和服务端的协议保障单个生产者串行地发送消息,并按序存储和持久化。
如需保证消息生产的顺序性,则必须满足以下条件:
1.单一生产者:消息生产的顺序性仅支持单一生产者,不同生产者分布在不同的系统, 即使设置相同的分区键,不同生产者之间产生的消息也无法判定其先后顺序。
2.串行发送:生产者客户端支持多线程安全访问,但如果生产者使用多线程并行发送,则不同线程间产生的消息将无法判定其先后顺序。

//构建消息
Message message = buildMessage(topic, msgTag, msgKey, msg);
//发送消息
SendResult sendResult = defaultMQProducer.send(message, (List<MessageQueue> mqs, Message _msg, Object arg) -> {
    int index = selector.apply(mqs.size());
    return mqs.get(index);
}, buzId);
  • 批量消息

在对吞吐率有一定要求的情况下,可以将一些消息聚成一批以后进行发送,可以增加吞吐率,并减少API和网络调用次数。
要注意的是批量消息的大小不能超过 1MiB(否则需要自行分割),其次同一批 batch 中 topic 必须相同。

 //构建消息列表
 List<Message> messageList = buildMessageList(topic, msgTag, msgKey, msgList);
 //发送消息
 SendResult sendResult = defaultMQProducer.send(messageList);
  • 延迟消息

延时消息的实现逻辑需要先经过定时存储等待触发,延时时间到达后才会被投递给消费者。
因此,如果将大量延时消息的定时时间设置为同一时刻,则到达该时刻后会有大量消息同时需要被处理,会造成系统压力过大,导致消息分发延迟,影响定时精度。
延迟等级是消息的一个属性,因此和发送方式无关,任何发送方式下都可以发送延迟消息。

//构建消息
Message message = buildMessage(topic, msgTag, msgKey, msg);
//设置延迟等级
message.setDelayTimeLevel(delayLevel);
//发送消息
SendResult sendResult = defaultMQProducer.send(message);
  • 异步消息

异步发送是指发送方发出一条消息后,不等服务端返回响应,接着发送下一条消息的通讯方式。
异步发送一般用于链路耗时较长,对响应时间较为敏感的业务场景。例如,视频上传后通知启动转码服务,转码完成后通知推送转码结果等。

//构建消息
Message message = buildMessage(topic, msgTag, msgKey, msg);
//发送消息
defaultMQProducer.send(message, new SendCallback() {
    @Override
    public void onSuccess(SendResult sendResult) {
        callback.accept(true, sendResult.getMsgId());
    }

    @Override
    public void onException(Throwable throwable) {
        callback.accept(false, throwable.getMessage());
    }
});
  • 单向消息

发送方只负责发送消息,不等待服务端返回响应且没有回调函数触发,即只发送请求不等待应答。
此方式发送消息的过程耗时非常短,一般在微秒级别。
适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。

//构建消息
Message message = buildMessage(topic, msgTag, msgKey, msg);
//发送消息
defaultMQProducer.sendOneway(message);
  • 事务消息

RocketMQ对分布式事务的支持,后面会详细介绍。
发送事务消息的生产者是一种单独类型的生产者——事务生产者。

//构建消息
Message message = buildMessage(topic, msgTag, msgKey, msg);
//发送消息
TransactionSendResult sendResult = transactionMQProducer.sendMessageInTransaction(message, null);
6.1.4 事务消息
  • 整个事务消息的详细交互流程如下图所示:

RocketMQ管理与开发_第20张图片

  • 事务生产者配置:
@Bean
public TransactionMQProducer transactionMQProducer() throws MQClientException{
    logger.info("MQ生产者(事务消息)正在创建...");
    TransactionMQProducer producer = new TransactionMQProducer(transGroupName);
    producer.setNamesrvAddr(nameSrvAddr);
    //创建事务监听器
    //MQTransactionListener transactionListener = new MQTransactionListener();
    //创建回查的线程池
    int corePoolSize = 2, maxPoolSize = 5;
    long keepAliveTime = 100;
    ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(2000);
    ExecutorService executorService = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, blockingQueue, r -> {
        Thread thread = new Thread(r);
        thread.setName("RMQ-事务回查线程");
        return thread;
    });
    //设置事务回查的线程池,如果不设置也会默认生成一个
    producer.setExecutorService(executorService);
    //设置事务监听器
    producer.setTransactionListener(transactionListener);

    producer.start();

    logger.info("MQ生产者(事务消息)创建成功!");
    return producer;
}
  • MQ事务监听器:
package com.vz.rocketmq.clients.transaction;

import com.vz.rocketmq.clients.annotaion.processor.LocalTransactionRegistryProcessor;
import com.vz.rocketmq.clients.enums.MQTopic;
import com.vz.rocketmq.clients.enums.MsgTag;
import com.vz.rocketmq.clients.service.TransactionLogCache;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author visy.wang
 * @description: MQ事务监听器
 * @date 2023/3/20 16:26
 */
@Component
public class MQTransactionListener implements TransactionListener {
    public static final Logger logger = LoggerFactory.getLogger(TransactionListener.class);
    /**
     * 使用本事务监听器的约定:
     * Topic+msgTag 对应唯一的一个处理器
     */
    //保存每个Topic-MsgTag 对应的业务处理器
    private static final Map<String, LocalTransactionHandler> localTransactionHandlers = new ConcurrentHashMap<>();

    //本地事务处理器注册
    public void registry(LocalTransactionHandler handler){
        if(!LocalTransactionRegistryProcessor.isAnnotationPresent(handler)){
            //没有注解不能注册
            return;
        }
        MQTopic topic = LocalTransactionRegistryProcessor.getTopic(handler);
        MsgTag tag = LocalTransactionRegistryProcessor.getTag(handler);
        String handlerKey = topic.getValue() + "_" + tag.getValue();
        if(Objects.isNull(localTransactionHandlers.get(handlerKey))){
            //一个 Topic+MsgTag 只注册一次
            localTransactionHandlers.put(handlerKey, handler);
            logger.info("本地事务处理器注册成功:{} -> {}", handlerKey, handler.getClass().getName());
        }
    }

    /**
     * 执行本地事务
     * 此方法是半事务消息发送成功后,执行本地事务的方法,
     * 具体执行完本地事务后,可以在该方法中返回以下三种状态:
     * 1.LocalTransactionState.COMMIT_MESSAGE:提交事务,允许消费者消费该消息
     * 2.LocalTransactionState.ROLLBACK_MESSAGE:回滚事务,消息将被丢弃不允许消费
     * 3.LocalTransactionState.UNKNOW:暂时无法判断状态,等待固定时间以后Broker端根据回查规则向生产者进行消息回查
     * @param message 半事务消息
     * @param o 自定义业务参数
     * @return 事务提交状态
     */
    @Override
    public LocalTransactionState executeLocalTransaction(Message message, Object o) {
        String transactionId = message.getTransactionId();
        logger.info("开始执行本地事务,transactionId={}", transactionId);

        //将消息分发到 Topic+MsgTag 对应的处理器
        String handlerKey = message.getTopic() + "_" + message.getTags();
        LocalTransactionHandler handler = localTransactionHandlers.get(handlerKey);
        if(Objects.isNull(handler)){
            logger.info("未找到已注册的本地事务处理器...");
            //按提交成功处理
            return LocalTransactionState.COMMIT_MESSAGE;
        }

        try{
            //执行具体的业务逻辑
            handler.execute(message);
            logger.info("执行本地事务成功,transactionId={}", transactionId);
            return LocalTransactionState.COMMIT_MESSAGE;
        }catch (Exception e){
            logger.info("执行本地事务异常:transactionId={}, message={}", transactionId, e.getMessage());
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    /**
     * 检查本地事务
     * 此方法是由于二次确认消息没有收到,Broker端回查事务状态的方法
     * 回查规则:
     * 本地事务执行完成后,若Broker端收到的本地事务返回状态为LocalTransactionState.UNKNOW,
     * 或生产者应用退出导致本地事务未提交任何状态。
     * 则Broker端会向消息生产者发起事务回查,第一次回查后仍未获取到事务状态,则之后每隔一段时间会再次回查。
     * @param messageExt 检查消息
     * @return 事务状态
     */
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
        String transactionId = messageExt.getTransactionId();
        logger.info("本地事务回查:transactionId={}", transactionId);
        Boolean state = TransactionLogCache.get(transactionId);

        if(Boolean.TRUE.equals(state)){
            logger.info("本地事务回查结果:已提交成功");
            return LocalTransactionState.COMMIT_MESSAGE;
        }else{
            logger.info("本地事务回查结果:未提交成功");
            return LocalTransactionState.UNKNOW; //继续回查
        }
    }
}
  • 本地事务处理器接口:
package com.vz.rocketmq.clients.transaction;

import org.apache.rocketmq.common.message.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author visy.wang
 * @description: 本地事务处理器接口
 * @date 2023/3/22 13:22
 */
public interface LocalTransactionHandler {
    Logger logger = LoggerFactory.getLogger(LocalTransactionHandler.class);

    /**
     * 本地事务中要执行的业务逻辑
     * 相同Topic+MsgTag的消息会被分发到同一个处理器
     * @param message MQ消息内容
     * 已添加本地事务管理注解:@Transactional
     */
    @Transactional(rollbackFor = Exception.class)
    void execute(Message message);
}
  • 本地事务处理器注册注解:
package com.vz.rocketmq.clients.annotaion;

import com.vz.rocketmq.clients.enums.MQTopic;
import com.vz.rocketmq.clients.enums.MsgTag;

import java.lang.annotation.*;

/**
 * @author visy.wang
 * @description: 本地事务注册的注解
 * @date 2023/3/23 10:23
 */
@Inherited
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LocalTransactionRegistry {
    MQTopic topic(); //消息主题
    MsgTag tag() default MsgTag.NULL; //消息标记
}
  • 本地事务处理器注册注解处理器
package com.vz.rocketmq.clients.annotaion.processor;

import com.vz.rocketmq.clients.annotaion.LocalTransactionRegistry;
import com.vz.rocketmq.clients.enums.MQTopic;
import com.vz.rocketmq.clients.enums.MsgTag;
import com.vz.rocketmq.clients.transaction.LocalTransactionHandler;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

/**
 * @author visy.wang
 * @description: LocalTransactionRegistry注解处理器
 * @date 2023/3/23 10:34
 */
public class LocalTransactionRegistryProcessor {
    private static final Map<String,MQTopic> topicsCache = new HashMap<>();
    private static final Map<String,MsgTag> tagsCache = new HashMap<>();

    public static MQTopic getTopic(LocalTransactionHandler handler){
        return getPropValue(handler, topicsCache, LocalTransactionRegistry::topic);
    }

    public static MsgTag getTag(LocalTransactionHandler handler){
        return getPropValue(handler, tagsCache, LocalTransactionRegistry::tag);
    }

    public static boolean isAnnotationPresent(LocalTransactionHandler handler){
        return isAnnotationPresent(handler.getClass());
    }

    private static boolean isAnnotationPresent(Class<?> clazz){
        return clazz.isAnnotationPresent(LocalTransactionRegistry.class);
    }

    private static <T> T getPropValue(LocalTransactionHandler handler,
                                      Map<String,T> cacheMap, Function<LocalTransactionRegistry,T> valueGetter){
        Class<?> clazz = handler.getClass();
        String clazzName = clazz.getName();

        //已缓存则直接返回
        T value = cacheMap.get(clazzName);
        if(Objects.nonNull(value)){
            return value;
        }

        //判断是否存在注解
        if(!isAnnotationPresent(clazz)){
            return null;
        }

        //获取值并缓存
        value = valueGetter.apply(clazz.getAnnotation(LocalTransactionRegistry.class));
        cacheMap.put(clazzName, value);

        return value;
    }
}
  • 本地事务处理器发现:
package com.vz.rocketmq.clients.transaction;

import com.vz.rocketmq.clients.annotaion.LocalTransactionRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Objects;

/**
 * @author visy.wang
 * @description: 本地事务处理器发现者
 * @date 2023/3/27 0:40
 */
@Component
public class LocalTransactionDiscoverer implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private MQTransactionListener mqTransactionListener;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        if(Objects.isNull(applicationContext.getParent())){
            Map<String, Object> beans = applicationContext.getBeansWithAnnotation(LocalTransactionRegistry.class);
            //自动将这些标注了注解的bean注册到MQ的事务监听器
            for(Object bean: beans.values()){
                //不为null且实现了LocalTransactionHandler接口的Bean才注册
                if(Objects.nonNull(bean) && (bean instanceof LocalTransactionHandler)){
                    mqTransactionListener.registry((LocalTransactionHandler) bean);
                }
            }
        }
    }
}
  • 本地事务处理器测试服务:
package com.vz.rocketmq.clients.service;

/**
 * @author visy.wang
 * @description: 订单服务
 * @date 2023/3/22 14:21
 */
public interface OrderService {

    void others();

    void addOrder(String orderId);
}
package com.vz.rocketmq.clients.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.vz.rocketmq.clients.annotaion.LocalTransactionRegistry;
import com.vz.rocketmq.clients.enums.MQTopic;
import com.vz.rocketmq.clients.enums.MsgTag;
import com.vz.rocketmq.clients.transaction.LocalTransactionHandler;
import org.apache.rocketmq.common.message.Message;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author visy.wang
 * @description: 订单服务(测试事务消息)
 * @date 2023/3/21 10:49
 */
@Service("orderService")
@LocalTransactionRegistry(topic = MQTopic.TEST_TOPIC_TRANSACTION, tag = MsgTag.TEST_TAG_TRANSACTION_1)
public class OrderServiceImpl implements OrderService, LocalTransactionHandler {
    //测试用
    private final AtomicInteger transactionIndex = new AtomicInteger(0);

    @Override
    public void others() {
        System.out.println("others...");
    }

    @Override
    public void addOrder(String orderId) {
        int t = transactionIndex.getAndIncrement();

        if(t%2 == 0){
            logger.info("订单添加成功:orderId={}", orderId);
        }else{
            int i = 3 / 0;
        }
    }

    @Override
    public void execute(Message message) {
        String transactionId = message.getTransactionId();
        String content = new String(message.getBody(), StandardCharsets.UTF_8);
        JSONObject json = JSON.parseObject(content);

        //分发到业务方法
        addOrder(json.getString("orderId"));

        //记录事务状态
        TransactionLogCache.add(transactionId);
    }
}
  • 事务状态记录:
package com.vz.rocketmq.clients.service;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author visy.wang
 * @description: 测试用,记录事务状态,实际应该保存在数据库中,并和业务SQL在同一个事务中
 * @date 2023/3/21 23:12
 */
public class TransactionLogCache {
    private static final Map<String, Boolean> transactionStates = new ConcurrentHashMap<>();

    public static void add(String transactionId){
        transactionStates.put(transactionId, Boolean.TRUE);
    }

    public static Boolean get(String transactionId){
        return transactionStates.get(transactionId);
    }
}
6.1.5 消息相关枚举:
package com.vz.rocketmq.clients.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author visy.wang
 * @description: MQ主题枚举
 * @date 2023/3/17 21:14
 */
@Getter
@AllArgsConstructor
public enum MQTopic {
    TEST_TOPIC("TEST_TOPIC", "测试主题"),
    TEST_TOPIC_TRANSACTION("TEST_TOPIC_TRANSACTION", "测试主题(事务消息)");

    /**
     * 主题值,必须唯一,不可重复
     */
    private final String value;
    /**
     * 主题描述
     */
    private final String desc;
}
package com.vz.rocketmq.clients.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author visy.wang
 * @description: MQ msgTag枚举
 * @date 2023/3/22 13:06
 */
@Getter
@AllArgsConstructor
public enum MsgTag {
    NULL("null", "空标记(仅用于容错,不建议使用)"),
    TEST_TAG_TRANSACTION_1("TEST_TAG_TRANSACTION_1", "测试事务消息标记1"),
    TEST_TAG_TRANSACTION_2("TEST_TAG_TRANSACTION_2", "测试事务消息标记2");

    /**
     * 标记值,必须唯一,不可重复
     */
    private final String value;
    /**
     * 标记描述
     */
    private final String desc;
}
6.1.6 消息发送测试接口:
package com.vz.rocketmq.clients.apis;

import com.vz.rocketmq.clients.enums.MQTopic;
import com.vz.rocketmq.clients.enums.MsgTag;
import com.vz.rocketmq.clients.producer.MQProducerService;
import com.vz.rocketmq.clients.transaction.LocalTransactionHandler;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author visy.wang
 * @description: 测试接口
 * @date 2023/3/17 13:51
 */
@RestController
@RequestMapping("/t")
public class MQTestController {
    public static final Logger logger = LoggerFactory.getLogger(MQTestController.class);
    @Autowired
    private MQProducerService mqProducerService;
    @Resource(name = "orderService")
    private LocalTransactionHandler orderService;

    @RequestMapping("/t")
    public Map<String,Object> test(){
        Map<String,Object> mp = new HashMap<>();
        mp.put("hello", "world");
        return mp;
    }

    @RequestMapping("/send/{msg}")
    public Boolean send(@PathVariable("msg") String msg){
        return mqProducerService.sendMessage(MQTopic.TEST_TOPIC, msg);
    }

    @RequestMapping("/sendAsync/{msg}")
    public Boolean sendAsync(@PathVariable("msg") String msg){
        mqProducerService.sendMessageAsync(MQTopic.TEST_TOPIC, msg, (isOK, message)->{
            logger.info("sendAsync:{}, message:{}", isOK, message);
        });
        return true;
    }

    @RequestMapping("/sendOrderly/{msg}")
    public Boolean sendOrderly(@PathVariable("msg") String msg){
        Integer buzId = Math.abs(msg.hashCode());
        logger.info("buzId={}", buzId);
        mqProducerService.sendMessageOrderly(MQTopic.TEST_TOPIC, msg, buzId, totalMq->{
            int index = buzId % totalMq;
            logger.info("队列索引:{}, 队列总数:{}", index, totalMq);
            return index;
        });
        return true;
    }

    @RequestMapping("/sendTrans/{orderId}")
    public Boolean sendTrans(@PathVariable("orderId") String orderId){
        Map<String,Object> msg = new HashMap<>();
        msg.put("orderId", orderId);
        logger.info("订单添加通知start:orderId={}", orderId);
        LocalTransactionState state = mqProducerService.sendTransactionMessage(MQTopic.TEST_TOPIC_TRANSACTION, MsgTag.TEST_TAG_TRANSACTION_1, msg);
        logger.info("订单添加通知end:orderId={}", orderId);
        return LocalTransactionState.COMMIT_MESSAGE.equals(state);
    }
}

6.2消费者(Consumer)

6.2.1 消费者配置

配置参数查看application.yml中rocketmq.consumer下的配置
配置类MQConsumerConfigure:

package com.vz.rocketmq.clients.config;

import com.vz.rocketmq.clients.consumer.MQConsumeMsgListener;
import lombok.Data;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author visy.wang
 * @description: RocketMQ消费者配置
 * @date 2023/3/17 17:46
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "rocketmq.consumer")
public class MQConsumerConfigure {
    public static final Logger logger = LoggerFactory.getLogger(MQConsumerConfigure.class);

    /**
     * 分组组名称
     */
    private String groupName;
    /**
     * NameServer地址
     */
    private String nameSrvAddr;
    /**
     * 订阅的主题
     */
    private String topics;
    /**
     * 消费者最小线程数
     */
    private Integer minThreads;
    /**
     * 消费者最大线程数
     */
    private Integer maxThreads;
    /**
     * 一次消费消息的条数
     */
    private Integer maxConsumeSize;
    /**
     * 消费监听器
     */
    @Autowired
    private MQConsumeMsgListener consumeMsgListener;

    /**
     * mq 消费者配置
     * @return 默认消费者
     */
    @Bean //通过容器实例化调用此方法完成消费者的创建,实际使用中并不会注入:defaultMQPushConsumer
    public DefaultMQPushConsumer defaultConsumer(){
        logger.info("MQ消费者正在创建...");
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);
        consumer.setNamesrvAddr(nameSrvAddr);
        consumer.setConsumeThreadMin(minThreads);
        consumer.setConsumeThreadMax(maxThreads);
        consumer.setConsumeMessageBatchMaxSize(maxConsumeSize);

        // 设置监听,编写具体消费的逻辑
        consumer.registerMessageListener(consumeMsgListener);

        /*
         * 设置consumer第一次启动是从队列头部开始还是队列尾部开始
         * 如果不是第一次启动,那么按照上次消费的位置继续消费
         */
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        /*
         * 设置消费模型,集群还是广播(broadcasting),默认为集群(clustering)
         */
        //consumer.setMessageModel(MessageModel.BROADCASTING);

        try {
            // 设置该消费者订阅的主题和tag,如果订阅该主题下的所有tag,则使用*,
            String[] topicArr = topics.split(";");
            for (String topic : topicArr) {
                String[] tagArr = topic.split("~");
                //可以订阅多个 topic+tag 组合
                consumer.subscribe(tagArr[0], tagArr[1]);
            }
            //启动消费者
            consumer.start();
            logger.info("MQ消费者创建成功,消费组:{}, 订阅主题:{}, NameServer地址:{}",
                    groupName, topics, nameSrvAddr);
        } catch (MQClientException e) {
            logger.info("MQ消费者创建失败,错误信息:{}", e.getMessage(), e);
        }
        return consumer;
    }
}
6.2.2 消费者监听器(并发)
package com.vz.rocketmq.clients.consumer;

import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author visy.wang
 * @description: 消费者监听器
 * @date 2023/3/17 17:50
 */
@Component
public class MQConsumeMsgListener implements MessageListenerConcurrently {
    public static final Logger logger = LoggerFactory.getLogger(MQConsumeMsgListener.class);

    /**
     * 消费消息
     * @param list 消息列表,默认msg里只有一条消息,可以通过设置maxConsumeSize参数来批量接收消息
     * @param context 上下文
     * @return 消费结果,消费成功后返回CONSUME_SUCCESS,否则consumer会重新消费该消息,直到返回CONSUME_SUCCESS
     */
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext context) {
        if (CollectionUtils.isEmpty(list)) {
            logger.info("MQ接收消息为空,直接返回成功");
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        }
        MessageExt msg = list.get(0);
        try {
            String topic = msg.getTopic();
            String tags = msg.getTags();
            String body = new String(msg.getBody(), StandardCharsets.UTF_8);

            logger.info("MQ消息:消息数:{}, topic={}, tags={}, body={}", list.size(), topic, tags, body);

            // TODO: 处理业务逻辑

            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //消费成功
        } catch (Exception e) {
            logger.error("获取MQ消息内容异常{}",e.getMessage(), e);
            return ConsumeConcurrentlyStatus.RECONSUME_LATER; //消费失败,重新就回到队列,等待下次消费
        }
    }
}
6.2.3 顺序消息消费者监听器
package com.vz.rocketmq.clients.consumer;

import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.common.message.MessageExt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author visy.wang
 * @description: 消费者监听器(顺序消息)
 * @date 2023/3/20 14:58
 */
@Component
public class MQConsumeMsgOrderlyListener implements MessageListenerOrderly {
    public static final Logger logger = LoggerFactory.getLogger(MQConsumeMsgOrderlyListener.class);

    /**
     * 消费消息
     * @param list 消息列表
     * @param context 上下文
     * @return 消费结果
     * ========================================
     * 与实现”MessageListenerConcurrently“接口的区别:
     * MessageListenerConcurrently:一个队列可能会被多个线程消费
     * MessageListenerOrderly:一个队列只会被一个线程消费
     */
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext context) {
        context.setAutoCommit(false); //关闭自动提交

        if (CollectionUtils.isEmpty(list)) {
            logger.info("MQ接收消息为空,直接返回成功");
            return ConsumeOrderlyStatus.SUCCESS;
        }
        MessageExt msg = list.get(0);

        String topic = msg.getTopic();
        String tags = msg.getTags();
        String body = new String(msg.getBody(), StandardCharsets.UTF_8);

        logger.info("MQ消息:消息数:{}, topic={}, tags={}, body={}", list.size(), topic, tags, body);

        // TODO: 处理业务逻辑

        return ConsumeOrderlyStatus.SUCCESS; //消费成功
    }
}

你可能感兴趣的:(java-rocketmq,rocketmq,java,分布式事务,生产者/消费者)