RocketMQ学习-Springboot整合RocketMQ

·  阅读 1414
RocketMQ学习-Springboot整合RocketMQ

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

SpringBoot整合RocketMQ

需要注意的是SpringBoot的starter集成包时,要注意版本。因为SpringBoot集成的RocketMQ的starter依赖由Spring社区提供,迭代比较快,版本之间的差异还是比较大的。可能版本不同,就导致使用的时候出现错误。

maven依赖, 直接把我的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>
复制代码

创建生产者SpringProducer

实例

@Component
public class SpringProducer {

    @Resource
    private RocketMQTemplate rocketMQTemplate;

    // 消息发送方法
    public void sendMessage(String topic, String msg){
        this.rocketMQTemplate.convertAndSend(topic, msg);
    }
}
复制代码

创建消费者SpringConsumer

实例灰常简单

@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

yml配置

rocketmq:
  producer:
    group: springBootGroup
  name-server: 192.168.40.128:9876;192.168.40.129:9876;192.168.40.130:9876
server:
  port: 1100
复制代码

controller创建

实例

@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 Cloud整合MQ

spring boot虽然已经极大降低了rocketmq的使用成本,但是在某些业务场景还需要对他进行配置。SpringCloudStream则解决了这一问题。

它是Spring社区提供的一个统一的消息驱动框架,目的就是想用一个统一的编程模型来对接所有的MQ消息中间产品。

同样是三板斧

maven依赖

<?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配置。

yml配置

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);
    }
}
复制代码

controller层

测试结果:

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还是相差很多。所以使用时还是要慎重

分类:
后端
标签:
分类:
后端
标签: