一、SpringBoot整合RocketMQ
1、整合RocketMQ
1-1、引入rocketmq的pom依赖
在使用SpringBoot的starter集成包时,要特别注意版本。因为SpringBoot集成RocketMQ的starter依赖是由Spring社区提供的,目前正在快速迭代的过程当中,不同版本之间的差距非常大,甚至基础的底层对象都会经常有改动。例如如果使用rocketmq-spring-boot-starter:2.0.4版本开发的代码,升级到目前最新的rocketmq-spring-boot-starter:2.1.1后,基本就用不了了。
我们创建一个maven工程,引入关键依赖:
此处引入的rocketmq由社区提供,因为其springboot版本较低,因此需要使用exclusions将rocketmq中的springboot相关包排除掉,然后自己再引入项目的实际springboot版本
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>-
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.1</version>
<!--排除sprinboot相关包-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入项目实际的springboot版本-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
1-2、配置application.yml的rocket配置信息
使用原生api的时候,配置nameServer要么在代码中进行配置,要么就使用环境变量进行配置,而springboot只需要在配置文件中进行如下配置即可
server:
port: 8899
rocketmq:
#配置nameServer
name-server: 192.168.253.131:9876
#配置生产者的默认组
producer:
group: springBootGroup
除了配置nameServer和group之外,还提供了如下配置,比较熟悉的配置如配置消息生产者的超时时间,可以看到默认是3s,如果RocketMQ的响应较慢可以将此配置的参数调大一些。
server:
port: 8899
rocketmq:
#配置nameServer
name-server: 192.168.253.131:9876
#配置生产者的默认组
producer:
group: springBootGroup
send-message-timeout: 8000
2、发送基本消息
通过以上配置就完成了RocketMQ的基本配置,接下来就可以正式发送消息了
2-1、发送普通消息
将RocketMQTemplate
注入,然后调用sendOneWay(topicName,message)
就可以完成一条单向消息的发送
@RestController
public class ProducerController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
private final String topic="springTopic";
/**
* 单向发送
* @param message
*/
@GetMapping("/send")
public void sendMessage(String message){
rocketMQTemplate.sendOneWay(topic,message);
}
2-2、发送同步消息
同步消息主要使用syncSend
来进行消息发送,会同步发挥SendResult发送结果信息
/**
* 同步发送
* @param message
* @return
*/
@GetMapping("/sendSync")
public SendResult sendSyncMessage(String message){
User user=new User();
user.setUserName("张三");
user.setAge(19);
user.setSex("男");
SendResult sendResult = rocketMQTemplate.syncSend(topic, user);
return sendResult;
}
2-3、发送异步消息
异步消息使用asyncSend
进行消息发送,不会立即返回发送结果,会在SendCallback
中进行回调,成功调用onSuccess()方法,失败调用onException()方法
/**
* 异步发送
* @param message
*/
@GetMapping("/sendAsync")
public void sendAsyncMessage(String message){
rocketMQTemplate.asyncSend(topic, message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送成功:"+sendResult);
}
@Override
public void onException(Throwable throwable) {
System.out.println("发送失败:"+throwable);
}
});
}
打开RocketMQ控制台,就可以看到刚刚发送的消息
点击状态就可以看到,刚刚发送的消息
2-4 消费者
消费者需要配置一个@RocketMQMessageListener
注解,此注解可以配置消费者的topic、消费组、过滤的tag、或者sql过滤等相关条件,同时需要实现RocketMQListener接口,这样就可以在onMessage中接收消息
@Component
@RocketMQMessageListener(consumerGroup = "MyConsumerGroup", topic = "springTopic")
public class SpringConsumer implements RocketMQListener {
@Override
public void onMessage(Object o) {
System.out.println(o.toString());
}
}
重启项目之后,就可以看到生产者刚刚发送的消息已收到
然后进入控制台,进入springTopic的CONSUMER管理就可以看到消息已消费
如下差值为0,代表消息已消费
3、过滤消息
过滤消息和原生一样,分为tag和sql过滤
3-1、tag消息过滤
3-1-1、发送消息
使用tag进行消息过滤,需要在topic后面添加“:”,然后后面为tag信息
@RestController
public class FilterProducerController {
private final String topic="filterTopic";
@Autowired private RocketMQTemplate rocketMQTemplate;
@GetMapping("/filtersync")
public SendResult sendTag(String message,String tag){
return rocketMQTemplate.syncSend(topic + ":" + tag, message);
}
}
3-1-2、接收消息
接收消息依然使用@RocketMQMessageListener
注解,使用selectorExpression
参数来进行设置,selectorType指定通过tag还是sql进行过滤,默认tag,因此selectorExpression写需要消费消息的tag就可以
@Component
@RocketMQMessageListener(consumerGroup = "filterGroup",topic = "filterTopic",selectorExpression = "tag0")
public class FilterTagConsumer implements RocketMQListener {
@Override
public void onMessage(Object o) {
System.out.println("tagfilter:"+o.toString());
}
}
如上发送到topic为filterTopic并且tag为tag0就可以收到消息了
3-2、sql消息过滤
3-2-1、发送消息
sql过滤的内容,原生使用msg.putUserProperty(key,value)来实现,而springboot中则使用header来进行设置,header可以通过MessageBuilder进行发送,也可以作为map进行发送
3-2-1-1、使用MessageBuilder发送header信息
@GetMapping("/filterSqlsync")
public SendResult filterSql(String message,Integer age,String tag){
Message<String> messAge = MessageBuilder.withPayload(message).setHeader("age", age).build(); return rocketMQTemplate.syncSend(topic + ":" + tag, messAge);
}
3-2-1-2、使用Map发送header信息
@GetMapping("/filterSqlsync")
public void filterSql(String message,Integer age,String tag){
//使用MessageBuilder设置信息
// Message<String> messAge = MessageBuilder.withPayload(message).setHeader("age", age).build();
// return rocketMQTemplate.syncSend(topic + ":" + tag, messAge);
//使用Map发送Header信息
Map<String, Object> headers = new HashMap<>() ;
headers.put("pack", "abc") ;
headers.put("age", age) ;
rocketMQTemplate.convertAndSend(topic + ":" + tag,message,headers);
}
3-2-2、过滤信息
通过sql不仅可以对header中的信息过滤,还支持tag过滤,如下:
这样只有tag为:tagab/tag123 并且age在18到30直接才可以收到信息
@Component
@RocketMQMessageListener(consumerGroup = "sqlFilterGroup",topic = "filterTopic",
selectorType = SelectorType.SQL92,
selectorExpression = "(TAGS is not null and TAGS in ('tagab', 'tag123')) and (age is not null and age between 18 and 30)")
public class FilterSqlConsumer implements RocketMQListener {
@Override
public void onMessage(Object o) {
System.out.println("sqlfilter:"+o.toString());
}
}
4、顺序消息
4-1、发送消息
依然以订单举例,第一次for为10个订单,第二次for为订单的固定步骤,然后需要使用syncSendOrderly(),进行发送消息,最后一个参数为hashKey,会根据这个hashKey将某个订单消息发送到固定的队列中
RocketMQTemplate给我们提供了SendOrderly方法(有多個重载),来实现发送顺序消息;包括以下:
syncSendOrderly,发送同步顺序消息;
asyncSendOrderly,发送异步顺序消息;
sendOneWayOrderly,发送单向顺序消息;
一般我们用syncSendOrderly方法发送同步顺序消息。
各方法发送顺序消息有如下几种方式:
@RestController
public class OrderProduceController {
private final static String topic="orderTopic";
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/orderSend")
public void orderSend(String message){
for (int i=0;i<10;i++){
int orderId=i;
for (int j = 0; j < 5; j++) {
rocketMQTemplate.syncSendOrderly(topic,message+"-"+i+"-"+j,"order_"+i);
}
}
}
}
4-2、接收消息
接收消息需要在@RocketMQMessageListener注解中添加consumeMode = ConsumeMode.ORDERLY
,告诉消费端使用顺序模式进行消费
@Component
@RocketMQMessageListener(consumerGroup = "orderGroup",topic = "orderTopic",consumeMode = ConsumeMode.ORDERLY)
public class OrderConsumer implements RocketMQListener {
@Override
public void onMessage(Object o) {
System.out.println(o.toString());
}
}
执行结果,可以看到虽然第二位订单顺序是错乱的,但是后面第三位订单的步骤是按照0-4的顺序消费的
5、延迟消息
延迟消息在各个消息发送方法中会有一个delayLevel的参数,传入需要延迟的顺序号即可,这就不做演示了
6、批量发送消息
6-1、发送消息
创建一个Message的集合,然后将消息放到集合中,传入的发送方法中
@RestController
public class BatchProducerController {
private final String topic = "batchTopic";
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/batchsend")
public void batchsend() {
List<Message> msgs = new ArrayList<Message>();
for (int i = 0; i < 10; i++) {
msgs.add(MessageBuilder.withPayload("Hello RocketMQ Batch Msg#" + i).
setHeader(RocketMQHeaders.KEYS, "KEY_" + i).build());
}
SendResult sr = rocketMQTemplate.syncSend(topic, msgs);
System.out.printf("--- Batch messages send result :" + sr);
}
}
通过下面执行结果,可以看到这10条消息是一起批量发送到服务端的
--- Batch messages send result :SendResult [sendStatus=SEND_OK, msgId=C0A800104EC418B4AAC21EAE2E490000,C0A800104EC418B4AAC21EAE2E490001,C0A800104EC418B4AAC21EAE2E490002,C0A800104EC418B4AAC21EAE2E490003,C0A800104EC418B4AAC21EAE2E490004,C0A800104EC418B4AAC21EAE2E490005,C0A800104EC418B4AAC21EAE2E490006,C0A800104EC418B4AAC21EAE2E490007,C0A800104EC418B4AAC21EAE2E490008,C0A800104EC418B4AAC21EAE2E490009, offsetMsgId=C0A8FD8400002A9F00000000000722DA,C0A8FD8400002A9F0000000000072421,C0A8FD8400002A9F0000000000072568,C0A8FD8400002A9F00000000000726AF,C0A8FD8400002A9F00000000000727F6,C0A8FD8400002A9F000000000007293D,C0A8FD8400002A9F0000000000072A84,C0A8FD8400002A9F0000000000072BCB,C0A8FD8400002A9F0000000000072D12,C0A8FD8400002A9F0000000000072E59, messageQueue=MessageQueue [topic=batchTopic, brokerName=broker-a, queueId=0], queueOffset=0]
7、事务消息
7-1、发送消息
7-1-1、消息生产者
发送事务消息,RocketMQTemplate,提供了sendMessageInTransaction()方法,通过该方法就可以发送事务消息
@RestController
public class TransactionProducerController {
private final static String topic = "transactionTopic";
@Autowired
private RocketMQTemplate rocketMQTemplate;
@GetMapping("/transend")
public void tranSend(String message) {
String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"};
for (int i = 0; i < 10; i++) {
Message message1 = MessageBuilder.withPayload(message).setHeader("tag", tags[i % tags.length]).build();
TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(topic, message1, null);
System.out.println(transactionSendResult.toString());
}
}
}
7-1-2、事务消息监听器
事务监听器需要通过以下几个步骤:
1、添加@RocketMQTransactionListener
注解,并通过设置rocketMQTemplateBeanName = "rocketMQTemplate"
来实现监听的处理,默认值为rocketMQTemplate
2、实现RocketMQLocalTransactionListener
接口,并重写executeLocalTransaction()和checkLocalTransaction()
@RocketMQTransactionListener(rocketMQTemplateBeanName = "rocketMQTemplate")
public class TransactionListenerImpl implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
String tags=message.getHeaders().get("tag").toString();
//TagA的消息会立即被消费者消费到
if(StringUtils.contains(tags,"TagA")){
return RocketMQLocalTransactionState.COMMIT;
//TagB的消息会被丢弃
}else if(StringUtils.contains(tags,"TagB")){
return RocketMQLocalTransactionState.ROLLBACK;
//其他消息会等待Broker进行事务状态回查。
}else{
return RocketMQLocalTransactionState.UNKNOWN;
}
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
String tags=message.getHeaders().get("tag").toString();
//TagC的消息过一段时间会被消费者消费到
if(StringUtils.contains(tags,"TagC")){
return RocketMQLocalTransactionState.COMMIT;
//TagD的消息也会在状态回查时被丢弃掉
}else if(StringUtils.contains(tags,"TagD")){
return RocketMQLocalTransactionState.ROLLBACK;
//剩下TagE的消息会在多次状态回查后最终丢弃
}else{
return RocketMQLocalTransactionState.UNKNOWN;
}
}
}
7-2、多事务处理
通过RocketMQTemplate发送事务消息,在消息监听器中在@RocketMQTransactionListener注解中通过设置RocketMQTemplate的bean名称来实现事务监听,这样所有通过rocketMQTemplate发送的事务消息就都可以被监听到。
那如果项目中有多个消息事务要处理该怎么办,虽然可以在消息生产者端通过设置Header参数来确定是哪个事务,但是长期以往这个消息监听器代码就会变得非常臃肿,后期维护升级也会有较多麻烦。
为了方便处理就可以安装一下方式处理:
- 1、自定义RocketMQTemplate继承本来的RocketMQTemplate,需要添加@ExtRocketMQTemplateConfiguration注解
@ExtRocketMQTemplateConfiguration
public class MyRocketMQTemplate extends RocketMQTemplate {
}
-
2、发送消息的时候使用上面自定义MyRocketMQTemplate进行消息发送
-
3、消息监听的时候,改为自定Template名称,bean名称首字母要小写
@RocketMQTransactionListener(rocketMQTemplateBeanName = "myRocketMQTemplate")
public class MyTransactionListenerImpl implements RocketMQLocalTransactionListener {
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
return null;
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
return null;
}
}
通过以上步骤就实现了多个事务监听器的处理
8、总结
- SpringBoot 引入org.apache.rocketmq:rocketmq-spring-boot-starter依赖后,就可以通过内置的RocketMQTemplate来与RocketMQ交互。相关属性都以rockemq.开头。具体所有的配置信息可以参见org.apache.rocketmq.spring.autoconfigure.RocketMQProperties这个类。
- SpringBoot依赖中的Message对象和RocketMQ-client中的Message对象是两个不同的对象,这在使用的时候要非常容易弄错。例如RocketMQ-client中的Message里的TAG属性,在SpringBoot依赖中的Message中就没有。Tag属性被移到了发送目标中,与Topic一起,以Topic:Tag的方式指定。
- 最后强调,一定要注意版本。rocketmq-spring-boot-starter的更新进度一般都会略慢于RocketMQ的版本更新,并且版本不同会引发很多奇怪的问题。apache有一个官方的rocketmq-spring示例,地址:github.com/apache/rock… 以后如果版本更新了,可以参考下这个示例代码。