下载地址:https://rocketmq.apache.org/zh/download/
选择一个你需要的版本,Source下载的是源码,需要自行编译;Binary下载的是编译后的二进制文件,可直接运行。此处下载编译后二进制版本。注意:下载不区分操作系统。
下载后将得到一个压缩包:
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
RocketMQ的运行基于JDK8或以上版本,安装及配置略。
修改bin目录下的文件:
修改conf目录下的文件:
我这里全都使用默认配置,就不作修改了。
此处是Windowos平台的启动
打开cmd命令行窗口,切换到MQ安装目录下的bin目录:
cd D:\ProgramFiles\rocketmq-all-5.1.0-bin-release\bin
start mqnamesrv.cmd
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%添加一对双引号:
下载地址:https://github.com/apache/rocketmq-dashboard
下载后解压,导入IDEA,按SpringBoot项目正常启动即可
启动前需要修改application.yml里面的配置:
搭建好了RocketMQ的运维控制台之后,直接在浏览器打开,默认会进入到驾驶舱(Dashboard)
整体横向菜单分为九个部分:
主要是设置nameserver和配置vipchannel。
控制台的dashboard,可以分别按broker和主题来查看消息的数量和趋势。
整个RocketMq的集群情况,包括分片,编号,地址,版本,消息生产和消息消费的TPS等,这个在做性能测试的时候可以作为数据指标。
可以新增/更新topic;也看查看topic的信息,如状态,路由,消费者管理和发送消息等。
可以在当前broker中查看/新建消费者group,包括消费者信息和消费进度。
可以在当前broker中查看生产组下的生产者group,包生产者信息和生产者状态。
可以按照topc,messageID,messageKey分别查询具体的消息。
达到最大重试次数后依然不能被正常消费的消息,一般需人工处理。
跟踪消息发送和消费的轨迹,默认情况下,RocketMQ 是不开启轨迹消息的,需要我们手工开启。
其中最常用的是集群,主题,消费者和消息这四部分。
创建项目:rocketmq-clients项目,结构如下:
引入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
配置参数查看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;
}
}
服务接口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());
}
}
同步发送是最常用的方式,是指消息发送方发出一条消息后,会在收到服务端同步响应之后才发下一条消息的通讯方式。
可靠的同步传输被广泛应用于各种场景,如重要的通知消息、短消息通知等。
//构建消息
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);
@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;
}
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);
}
}
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;
}
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);
}
}
配置参数查看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;
}
}
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; //消费失败,重新就回到队列,等待下次消费
}
}
}
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; //消费成功
}
}