RocketMQ的消息类型:
- Normal:普通消息
- FIFO:顺序消息
- Delay:定时/延时消息
- Transaction:事务消息
前面三个消息类型也支持异步
接下来我们通过SpringBoot整合RocketMQ的简单Demo来实现不同消息类型的发送
首先我们构建两个SpringBoot项目,一个作为生产者服务,一个作为消费者服务,还有一个公用模块common
消费者,生产者引入依赖,yml配置号RocketMQ服务(自己启动本地RocketMQ服务)
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.3</version>
</dependency>
生产者yml
rocketmq:
name-server: 127.0.0.1:9876
producer:
group: my-producer-group
消费者yml
rocketmq:
name-server: 127.0.0.1:9876
消费者服务键两个监听器用来处理消息
@Component
@RocketMQMessageListener(topic = "my_topic",consumerGroup = "my-consumer-group-1", selectorExpression ="normal_tag",consumeMode = ConsumeMode.ORDERLY)
public class ConsumerListener implements RocketMQListener<String> {
@Override
public void onMessage(String s) {
System.out.println(s);
}
}
@Component
@RocketMQMessageListener(topic = "my_topic",consumerGroup = "my-consumer-group", selectorExpression ="sync_tag||async_tag")
public class SyncListener implements RocketMQListener<MyMessageBody> {
@Override
public void onMessage(MyMessageBody messageBody) {
System.out.println( messageBody.toString());
}
}
@RocketMQMessageListener注解参数解释:
topic: 指定主题
consumerGroup: 指定消费者组
selectorExpression: 订阅标签Tag
selectorType: 选择器类型
TAG: 类型匹配,根据标签过滤,此时 selectorExpression 表示的是消息的标签(Tag)表达式,比如"tagA || tagB" 表示只消费带有 tagA 或 tagB 的消息
SQL92: 表达式过滤,SQL92 语法,根据 SQL92 语法过滤消息,此时 selectorExpression 是一个 SQL 表达式,如 "age > 5 AND name = 'yzz'",RocketMQ 会根据消息的自定义属性进行匹配。
consumeMode: 消费模式, 默认是并发消费(Concurrently):消息监听器以并发方式消费消息
可选择顺序消费(Orderly)保证消息按照分区(MessageQueue)内的顺序被消费
messageModel: 消息模式,默认是集群模式(CLUSTERING):每条消息只会被分配给组内的 一个消费者实例,实现负载均衡
可选择广播模式(BROADCASTING):每条消息会被发送给消费者组内的所有消费者实例
注意:这两个监听器要属于不同的消费者组,消费者组名称不能一样,如果有多个Tag,用“||”拼接
普通消息
通过convertAndSend()方法
/**
* 发送普通消息
* @param message
* @return
*/
@RequestMapping("/send")
public String send(String message) {
rocketMQTemplate.convertAndSend("my_topic:normal_tag", message);
return "success";
}
convertAndSend支持自动类型转换,可以直接传入字符串、POJO 等对象,内部会自动将其转换为 RocketMQ 的 Message。底层调用的是 syncSend,但省去了手动构建 Message 的步骤。
注意:如果要发送消息到my_topic主题的某个Tag标签,用":"拼接
通过syncSend()发送同步消息
/**
* 发送同步消息
*/
@PostMapping("sendSync")
public String sendSync(@RequestBody MyMessageBody body) {
Message<MyMessageBody> message = MessageBuilder
.withPayload(body)
.build();
//发送同步消息,将消息发送到"my_topic":sync_tag,即"my_topic"主题下的sync_tag标签
SendResult sendResult = rocketMQTemplate.syncSend("my_topic"+":"+"sync_tag", message);
if (sendResult != null){
return sendResult.toString();
}
return "success";
}
通过asyncSend()发送异步消息
/**
* 发送异步消息
*/
@PostMapping("sendAsync")
public String sendAsync(@RequestBody MyMessageBody body) {
Message<MyMessageBody> message = MessageBuilder
.withPayload(body)
.build();
rocketMQTemplate.asyncSend("my_topic" + ":" + "async_tag", message, new SendCallback(){
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("成功:" + sendResult.toString());
}
@Override
public void onException(Throwable throwable) {
System.out.println("失败:" + throwable.getMessage());
}
});
return "success";
}
通过sendOneWay()发送单项消息
生产者不需要返回结果,不管消息是否发送成功,对消息可靠性要求不高
@PostMapping("sendOneWay")
public String sendOneWay(@RequestBody MyMessageBody body) {
Message<MyMessageBody> message = MessageBuilder
.withPayload(body)
.build();
rocketMQTemplate.sendOneWay("my_topic:normal", message);
return "success";
}
顺序消息
首先我们看看普通的非顺序发送的消息
/**
* 批量发送消息,非顺序消息
*/
@RequestMapping("sendBatch")
public List<SendResult> sendBatch() {
List<SendResult> sendResults = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Message<String> message = MessageBuilder.withPayload("批量发送消息" + i).build();
SendResult sendResult = rocketMQTemplate.syncSend("my_topic" + ":" + "normal_tag", message);
sendResults.add(sendResult);
}
return sendResults;
}
结果:多运行几次发现消息消费顺序比较随机,因为消息比较少,而且消费者也只有一个,所以大部分情况可能会显示一定的顺序,自己可以通过集群方式验证
因为一个Topic存在多个队列,默认消息会被随机或轮询发送给队列,也就说发送的10个消息会被分给不同的队列,消费者也是默认按队列顺序 + 轮询方式进行拉取消息,该过程没有办法保证消息消费的顺序性。
我们可以通过给消息设置Key,保证同一个key的消息顺序发送到一个队列中,但是同一个key的消息在队列里不保证连续,因为一个队列可以接收多个key的消息,队列先进先出的原则就保证了消息消息的顺序消费。
需要注意的是,消费模式consumeMode = ConsumeMode.ORDERLY时才能实现顺序性,在并发模式Concurrently下顺序无效。
@RequestMapping("sendOrder")
public String sendOrder() {
for (int i = 0; i < 10; i++) {
rocketMQTemplate.syncSendOrderly("my_topic" + ":" + "normal_tag", "批量发送消息" + i,"my_key");
}
return "success";
}
消息被消费结果:
定时/延时消息
/**
* 发送定时/延时消息
*/
@RequestMapping("sendDelay")
public String sendDelay() {
//发送延时消息,5秒后执行
rocketMQTemplate.syncSendDelayTimeSeconds("my_topic" + ":" + "normal_tag", "延时消息", 5);
//发送定时消息,10秒后执行
rocketMQTemplate.syncSendDeliverTimeMills("my_topic" + ":" + "normal_tag", "定时消息", System.currentTimeMillis() + 10000);
return "success";
}
事务消息
发送事务消息与其他消息不同的是,事务消息需要一个事务监听器,用于处理事务消息的提交和回滚,而该事务监听器是属于发送方来定义的,也就是生产者,所以事务监听器与Topic无关,也就是说同一个rocketMQTemplate发送的事务消息使用的是同一个事务监听器,不看topic。
topic代表的是我事务提交后会被发送到消费者由哪个消费者组消费,也就是说生产者的事务消息被成功提交后该消息才会被发送给消费者。
事务监听器:
@RocketMQTransactionListener
public class LocalTransactionListener implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object arg) {
try {
System.out.println("rocketMQTemplate执行本地事务");
System.out.println("执行本地事务成功,事务提交");
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
System.out.println("执行本地事务异常,事务回滚");
return RocketMQLocalTransactionState.ROLLBACK;
}
}
/**
* 回查本地事务状态
* 当 Producer 发送事务消息后,在一定时间内 没有提交或回滚。
* Broker 会在一段时间后主动发起 事务状态回查,调用此方法。
* Consumer 或 Producer 需要在这个方法中判断本地事务是否成功,并返回相应的状态。
* @param message
* @return
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
System.out.println("回查本地事务状态");
//比如:根据订单ID查询订单状态,如果订单状态为成功,则返回COMMIT,否则返回ROLLBACK
return null;
}
}
注意:事务监听器要放在消息生产者服务里,因为事务监听器是由生产者定义的
发送事务消息:
@RequestMapping("sendTransaction")
public String sendTransaction() {
rocketMQTemplate.sendMessageInTransaction("my_topic:normal_tag", MessageBuilder.withPayload("rocketMQTemplate事务消息").build(), null);
return "success";
}
生产者控制台:
消费者控制台:
同一个rocketMQTemplate发送的事务消息使用的是同一个事务监听器,如果创建多个rocketMQTemplate对象,则需要为每个rocketMQTemplate对象创建一个事务监听器 通过@RocketMQTransactionListener(rocketMQTemplateBeanName="")rocketMQTemplateBeanName来指定使用哪个rocketMQTemplate对象,默认是rocketMQTemplate
比如我自定义一个RocketMQTemplate对象
//一定要加上生产组,否则多事务消息的监听器都会监听到
@ExtRocketMQTemplateConfiguration(group = "my_group")
public class MyRocketMQTemplate extends RocketMQTemplate {
}
再为 myRocketMQTemplate配置事务监听器,同时模拟事务执行异常回滚的情况,观察消息是否会被发送给消费者
@RocketMQTransactionListener(rocketMQTemplateBeanName="myRocketMQTemplate")
public class MyLocalTransactionListener implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object arg) {
try {
System.out.println("myRocketMQTemplate执行本地事务");
//模拟事务执行异常
int i = 5/0;
System.out.println("执行本地事务成功,事务提交");
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
System.out.println("执行本地事务异常,事务回滚");
return RocketMQLocalTransactionState.ROLLBACK;
}
}
/**
* 回查本地事务状态
* 当 Producer 发送事务消息后,在一定时间内 没有提交或回滚。
* Broker 会在一段时间后主动发起 事务状态回查,调用此方法。
* Consumer 或 Producer 需要在这个方法中判断本地事务是否成功,并返回相应的状态。
* @param message
* @return
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
System.out.println("回查本地事务状态");
//比如:根据订单ID查询订单状态,如果订单状态为成功,则返回COMMIT,否则返回ROLLBACK
return null;
}
}
事务消息发送:
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private MyRocketMQTemplate myRocketMQTemplate;
@RequestMapping("sendTransaction")
public String sendTransaction() {
rocketMQTemplate.sendMessageInTransaction("my_topic:normal_tag",
MessageBuilder.withPayload("rocketMQTemplate事务消息").build(), null);
myRocketMQTemplate.sendMessageInTransaction("my_topic:normal_tag",
MessageBuilder.withPayload("myRocketMQTemplate事务消息").build(), null);
return "success";
}
结果:
生产者服务控制台:
消费者服务控制台:
结果显示事务消息只有成功提交后才会发送给消费者消费。