前言
经过前面几部分的教程,大家应该已经对RocketMQ有了一个全面的认识,建议仔细阅读前几章的内容,可以更好的理解这次的内容,接下来,我们通过代码来演示一下SpringBoot如何集成并使用RocketMQ发送消息
一、SpringBoot集成RocketMQ
集成很简单,只需要导入RocketMQ的starter包即可
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
// 工具类方便打印信息
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
</dependency>
在配置文件中配置相关参数
生产者
# 应用服务 WEB 访问端口
server:
port: 8088
rocketmq:
# 多个nameserver用分号隔开
name-server: 192.168.0.8:9876
#生产者
producer:
# 生产者组名
group: producer_group
# 异步发送消息的重试次数
retry-times-when-send-async-failed: 5
消费者:
# 应用服务 WEB 访问端口
server:
port: 8081
rocketmq:
# 多个nameserver用分号隔开
name-server: 192.168.0.8:9876
# 消费者
consumer:
# 组名
group: consumer_group
# 消息模式 集群模式
message-model: CLUSTERING
spring:
application:
name: consumer
二、发送普通消息
1.同步发送消息
同步消息是指消息发送方发出一条消息后,会在收到服务端返回响应之后才发下一条消息的通讯方式。
2.异步发送消息
异步消息是指发送方发出一条消息后,不等服务端返回响应,接着发送下一条消息的通讯方式。RocketMQ 异步发送,需要实现异步发送回调接口(SendCallback)。消息发送方在发送了一条消息后,不需要等待服务端响应即可发送第二条消息,发送方通过回调接口接收服务端响应,并处理响应结果。
3.单步发送消息
发送⽅只负责发送消息,不等待服务端返回响应且没有回调函数触发,即只发送请求不等待应答。
具体代码如下:
生产者:
import cn.hutool.core.date.DateUtil;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Date;
@RestController
@RequestMapping("/rocketmq")
public class ProducerController {
@Resource
private RocketMQTemplate rocketMQTemplate;
/**
* 发送普通消息
* @return
*/
@RequestMapping("/testNormalMessage")
public String testNormalMessage() throws Exception{
//发送10条消息
for (int i = 1; i <= 10; i++) {
String msg = "这是第"+i+"个订单";
//普通同步消息
rocketMQTemplate.syncSend("TopicOrder",MessageBuilder.withPayload("同步消息:"+msg));
//普通异步消息
rocketMQTemplate.asyncSend("TopicOrder", MessageBuilder.withPayload("异步消息:"+msg), new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送成功:"+sendResult.toString());
}
@Override
public void onException(Throwable throwable) {
System.out.println("发生异常:"+throwable.toString());
}
});
//单步发送
rocketMQTemplate.sendOneWay("TopicOrder",MessageBuilder.withPayload("单步发送消息:"+msg));
Thread.sleep(1000);
}
return "";
}
}
消费者:
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
@RocketMQMessageListener(topic = "TopicOrder",consumerGroup = "${rocketmq.consumer.group}",selectorExpression = "*")
@Component
public class ConsumerNormal implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
try {
System.out.println("收到信息:"+new String(messageExt.getBody()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
结果:
可以看到消费者收到了生产者发送的消息
三、发送顺序消息
生产者:
/**
* 发送顺序通知
* @return
*/
@RequestMapping("/testSyncOrderMessage")
public String testSyncOrderMessage() throws Exception{
//发送10个订单,每个订单发送3条记录数据
for (int i = 1; i <= 10; i++) {
for (int j = 1; j <= 3; j++) {
String msg = "这是第"+i+"个订单";
switch (j){
case 1:
msg += ",用于生成订单";
break;
case 2:
msg += ",用来生成支付记录";
break;
case 3:
msg += ",用来生成物流记录";
break;
}
//自定义队列选择器
// rocketMQTemplate.setMessageQueueSelector(new MessageQueueSelector() {
// @Override
// public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
// return list.get(o.hashCode() % 2);
// }
// });
SendResult topicOrderOrderly = rocketMQTemplate.syncSendOrderly("TopicOrder_Orderly", msg, i + "");
System.out.println("发送信息:"+topicOrderOrderly.toString());
Thread.sleep(1000);
}
}
return "";
}
消费者:
@RocketMQMessageListener(topic = "TopicOrder_Orderly",consumerGroup = "orderly_group",consumeMode = ConsumeMode.ORDERLY)
@Component
public class ConsumerOrderly implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
try {
System.out.println("收到信息:队列ID:"+messageExt.getQueueId()+" 内容:"+new String(messageExt.getBody()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
我们可以启动两个消费者,观察多个消费者消费的数据情况,是否相同队列的数据由同一个消费者进行处理
结果:
可以从结果看出,相同订单号的订单由一个消费者按顺序消费,默认4个队列,两个消费者各承担了两个队列的消息
四、发送延迟消息
延迟等级前面的教程中介绍过,下面贴出来帮大家回忆一下
代码如下:
生产者:
/**
* 发送延迟消息
* @return
*/
@RequestMapping("/testDelayMessage")
public String testDelayMessage() throws Exception{
//发送延迟消息
SendResult sendResult = rocketMQTemplate.syncSend(
"TopicDelay", //Topic
MessageBuilder.withPayload("这是一条延迟10S的消息").build(), //发送的消息主体
3000, //timeout时间
3 //延迟等级,对应16个默认延迟时间
);
System.out.println(sendResult.toString());
System.out.println(DateUtil.formatTime(new Date()));
return "";
}
消费者:
@RocketMQMessageListener(topic = "TopicDelay",consumerGroup = "delay_group",selectorExpression = "*")
@Component
public class ConsumerDelay implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
try {
System.out.println("收到信息"+messageExt.toString());
System.out.println(DateUtil.formatTime(new Date()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
结果:
生产者:
消费者:
五、发送事务消息(重要)
事务消息的整体逻辑跟前面章节介绍的方式相同,现在发送3条模拟消息分别代表
1.模拟成功消息
2.模拟二次确认失败消息
3.模拟二次确认结果未知,主动查询消息之后返回成功
预想的结果应该是第一和第三种可以成功消费
下面是测试代码:
1.新增生产者监听类,用于二次确认事务处理,和提供主动查询逻辑
import cn.hutool.core.date.DateUtil;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.messaging.Message;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@RocketMQTransactionListener
public class OrderTransMsgListener implements RocketMQLocalTransactionListener {
// 事务消息共有三种状态,提交状态、回滚状态、中间状态:
// RocketMQLocalTransactionState.COMMIT: 提交事务,它允许消费者消费此消息。
// RocketMQLocalTransactionState.ROLLBACK: 回滚事务,它代表该消息将被删除,不允许被消费。
// RocketMQLocalTransactionState.UNKNOWN: 中间状态,它代表需要检查消息队列来确定状态。
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
if("commit".equals(o)){
System.out.println("二次确认成功:"+new String((byte[])message.getPayload(), StandardCharsets.UTF_8));
return RocketMQLocalTransactionState.COMMIT;
}
if("rollback".equals(o)){
System.out.println("二次确认失败:"+new String((byte[])message.getPayload(), StandardCharsets.UTF_8));
return RocketMQLocalTransactionState.ROLLBACK;
}
if("unknown".equals(o)){
System.out.println("二次确认未知:"+new String((byte[])message.getPayload(), StandardCharsets.UTF_8));
return RocketMQLocalTransactionState.UNKNOWN;
}
return null;
}
/**
* 检查本地事务的状态
* 回查间隔时间:系统默认每隔60秒发起一次定时任务,对未提交的半事务消息进行回查,共持续12小时。
* 第一次消息回查最快
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
System.out.println("收到事务查询回调的时间:" + DateUtil.formatDateTime(new Date()));
System.out.println("信息内容:"+new String((byte[])message.getPayload(), StandardCharsets.UTF_8));
return RocketMQLocalTransactionState.COMMIT;
}
}
2.生产者
/**
* 发送事务通知
* @return
*/
@RequestMapping("/testTransMessage")
public String testTransMessage() throws Exception{
//模拟正常提交消息
SendResult sendResult1 = rocketMQTemplate.sendMessageInTransaction("TopicTrans",MessageBuilder.withPayload("这是正常的消息").build(),"commit");
System.out.println(sendResult1.toString());
//模拟需要回滚的消息
SendResult sendResult2 = rocketMQTemplate.sendMessageInTransaction("TopicTrans",MessageBuilder.withPayload("这是错误的消息").build(),"rollback");
System.out.println(sendResult2.toString());
//模拟生产者发送成功,二次确认失败,broker主动查询成功的情况
SendResult sendResult3 = rocketMQTemplate.sendMessageInTransaction("TopicTrans",MessageBuilder.withPayload("这是二次确认失败,主动查询成功的消息").build(),"unknown");
System.out.println(sendResult3.toString());
return "";
}
消费者:
@RocketMQMessageListener(topic = "TopicTrans",consumerGroup = "trans_group")
@Component
public class ConsumerTrans implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
try {
System.out.println("收到信息:"+new String(messageExt.getBody())+"时间:"+ DateUtil.formatDateTime(new Date()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
然后我们执行发送信息,看一下结果:
生产者:
消费者:
和我们预料的结果相同。
关注我,不迷路