68
社区成员




需要注意的是SpringBoot的starter集成包时,要注意版本。因为SpringBoot集成的RocketMQ的starter依赖由Spring社区提供,迭代比较快,版本之间的差异还是比较大的。可能版本不同,就导致使用的时候出现错误。
maven依赖, 直接把我的maven工程的配置放到这里了。
我直接创建了一个空的maven工程,然后引入spring boot需要的一赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>RocketMQAPI</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.1</version>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.9</version>
</dependency>
</dependencies>
</project>
实例
@Component
public class SpringProducer {
@Resource
private RocketMQTemplate rocketMQTemplate;
// 消息发送方法
public void sendMessage(String topic, String msg){
this.rocketMQTemplate.convertAndSend(topic, msg);
}
}
实例灰常简单
@Component
@RocketMQMessageListener(consumerGroup = "MyConsumerGroup", topic = "TestTopic")
public class SpringConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("Recived message: " + message);
}
}
consumerGroup是RocketMQ的组,spring的yml配置组,Topic生产者使用的Topic
rocketmq:
producer:
group: springBootGroup
name-server: 192.168.40.128:9876;192.168.40.129:9876;192.168.40.130:9876
server:
port: 1100
实例
@RestController
@RequestMapping("/MQTest")
public class MQTestController {
@Resource
private SpringProducer springProducer;
@GetMapping("/sendMessage")
public String sendMessage(@RequestParam("message") String message){
springProducer.sendMessage("TestTopic", message);
return "消息发送完成";
}
}
实例
@SpringBootApplication(scanBasePackages = {"com.anzhi.rocketmq.*"})
public class RocketMQScApplication {
public static void main(String[] args) {
SpringApplication.run(RocketMQScApplication.class, args);
}
}
启动后访问controller层地址:我的本地地址:http://localhost:1100/MQTest/sendMessage?message=123121
此时可以消费者消费消息:结果如下:
Recived message: 123121
Recived message: 123121
Recived message: 123121
Recived message: 123121
Recived message: 123121
Recived message: 123121
Recived message: 123121
Recived message: 123121
Recived message: 123121
// 事物消息发送
public void sendMessageInTransaction(String topic, String msg) throws InterruptedException{
String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"};
for(int i=0; i<10; i++){
Message<String> message = MessageBuilder.withPayload(msg).build();
String destination = topic + ":" + tags[i % tags.length];
SendResult sendResult = rocketMQTemplate.sendMessageInTransaction(destination, message, destination);
System.out.printf("%s%n", sendResult);
Thread.sleep(10);
}
}
相较于普通消息的发送,事物消息还需额外实现一个事物消息监听器
@RocketMQTransactionListener(rocketMQTemplateBeanName = "rocketMQTemplate")
public class MyTransactionImpl implements RocketMQLocalTransactionListener {
private ConcurrentHashMap<Object, String> localTrans = new ConcurrentHashMap<>();
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
Object id = message.getHeaders().get("id");
String destination = o.toString();
localTrans.put(id, destination);
org.apache.rocketmq.common.message.Message msg = RocketMQUtil.convertToRocketMessage(new StringMessageConverter(), "UTF-8", destination, message);
String tags = msg.getTags();
if(StringUtils.contains(tags, "TagA")){
return RocketMQLocalTransactionState.COMMIT;
}else if(StringUtils.contains(tags, "TagB")){
return RocketMQLocalTransactionState.ROLLBACK;
}else{
return RocketMQLocalTransactionState.UNKNOWN;
}
}
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
//SpringBoot的消息对象中,并没有transactionId这个属性。跟原生API不一样。
//String destination = localTrans.get(msg.getTransactionId());
return RocketMQLocalTransactionState.COMMIT;
}
rocketMQTemplateBeanName指定的是spring boot集成rocketmq的rocketMQTemplate的bean的名字,通过注解完成事物消息处理。
这里需要注意的是,一个事物监听器对应一个事物流程,如果多个呢,rocketMQTemplate就无法指定了,显然这个功能不能满足需求了。但是spring boot提供了扩展,即开发者可以继承rocketMQTemplate,对属性进行定制,就可以满足我们的需求。如下:
(ToDo: 找一个实例测试,暂且搁置)
@ExtRocketMQTemplateConfiguration()
public class ExtRocketMQTemplate extends RocketMQTemplate {
}
事物消息的测试结果:同样访问自己的本地链接:http://localhost:1100/MQTest/sendTransMessage?message=1242543
endResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9D0D0100, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=2], queueOffset=103]
SendResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9D800103, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=3], queueOffset=104]
SendResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9DAE0106, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=0], queueOffset=105]
SendResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9DE80109, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=1], queueOffset=106]
SendResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9E4C010C, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=2], queueOffset=107]
SendResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9E82010F, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=3], queueOffset=108]
SendResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9EAD0112, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=0], queueOffset=109]
SendResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9ED50115, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=1], queueOffset=110]
Recived message: 1242543
SendResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9F040118, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=2], queueOffset=111]
SendResult [sendStatus=SEND_OK, msgId=24098A0031A928409DD7720C18F134F221B818B4AAC28D3E9F37011E, offsetMsgId=null, messageQueue=MessageQueue [topic=TestTopic, brokerName=RaftNode00, queueId=3], queueOffset=112]
Recived message: 1242543
Recived message: 1242543
Recived message: 1242543
Recived message: 1242543
Recived message: 1242543
Recived message: 1242543
Recived message: 1242543
还有其他的消息类型,rocketmq的junit案列中也有,可以看看。有时间我自己操作了再写进来。
spring boot虽然已经极大降低了rocketmq的使用成本,但是在某些业务场景还需要对他进行配置。SpringCloudStream则解决了这一问题。
它是Spring社区提供的一个统一的消息驱动框架,目的就是想用一个统一的编程模型来对接所有的MQ消息中间产品。
同样是三板斧
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SpringCloudMQ</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
<version>4.7.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
<version>2.2.3.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
</dependencies>
</project>
@EnableBinding({Source.class, Sink.class})
@SpringBootApplication
public class ScRocketMQApplication {
public static void main(String[] args) {
SpringApplication.run(ScRocketMQApplication.class, args);
}
}
注意这个@EnableBinding({Source.class, Sink.class})注解,这是SpringCloudStream引入的 Binder配置。
server:
port: 11000
spring:
cloud:
stream:
bindings:
input:
destination: TestTopic
group: scGroup
output:
destination: TestTopic
rocketmq:
binder:
name-server: 192.168.232.128:9876;192.168.232.129:9876;192.168.232.130:9876
@Component
public class ScConsumer {
@StreamListener(Sink.INPUT)
public void onMessage(String message){
System.out.println("received message:"+message+" from binding:"+
Sink.INPUT);
}
}
@Component
public class ScProducer {
@Resource
private Source source;
public void sendMessage(String msg){
Map<String, Object> headers = new HashMap<>();
headers.put(MessageConst.PROPERTY_TAGS, "testTag");
MessageHeaders messageHeaders = new MessageHeaders(headers);
Message<String> message = MessageBuilder.createMessage(msg,
messageHeaders);
this.source.output().send(message);
}
}
测试结果:
2021-08-28 23:51:49.570 INFO 25468 --- [io-11100-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-08-28 23:51:49.571 INFO 25468 --- [io-11100-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2021-08-28 23:51:49.585 INFO 25468 --- [io-11100-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 14 ms
received message:1242543 from binding:input
上面只是spring整合RocketMQ的简单实例,具体的复杂应用并没有说明。后续的学习中,ToDo:将其补全。
SpringCloudStream虽然是一套通用的中间件编程框架,上面的使用也证明了这一点。业务代码都不需要改动,只需要修改pom依赖和配置文件即可做到中间件的切换。但是不同的MQ都拥有自己的业务模型,差距还是比较大。所以使用时要考虑业务模型的转换,以及不同MQ的定制化属性。比如rocketmq的个性化属性,都是以spring.cloud.stream.rocketmq开头。
其次是SpringCloudStream中的RocketMQ的版本问题,前面也提到过,而且相关RocketMQ的完整文档比较缺失,因为是RocketMQ是交由厂商自己维护,所以……
因此,对于RocketMQ来说,SpringCloudStream目前来说还并不是一个非常好的集成方案。和目前集成的kafka、Rabbit还是相差很多。所以使用时还是要慎重