Spring Cloud Stream RocketMq 的使用

Spring Cloud Stream RocketMq 的使用


RocketMq相关基本概念与安装
docker-compose 安装RocketMq
分布式事务相关说明

RocketMq 可以发送事务消息
RocketMq 事务消息的基本原理是
Spring Cloud Stream RocketMq 的使用_第1张图片
**注意:使用RocketMq 发送事务消息,这里主要解决的是生产者执行事务后确保消息有发布到Mq 并可以被消费。消费者按照正常消费就好,但是要确保消息多次消费的结果是要一致相等的(幂等性,不确保只消费一次),多次消费失败就会进入死信队列 **
图解步骤

  1. Mq producer 发送一个事务消息(包含一些业务消息)
  2. Mq 收到事务消息,并响应成功
  3. Mq producer 收到Mq 响应成功,执行本地事务
  4. Mq producer 发送事务执行状态给Mq,Mq 根据事务状态 设置消息是可以被消费还是回滚,
  5. 如果因为某些原因(网络原因之类),Mq没有得到事务执行结果状态,或者是得到了事务状态RocketMQLocalTransactionState.UNKNOWN;会在一次向Mq发送信息确认状态
  6. Mq producer 检查事务状态
  7. Mq producer 发送事务状态,然后Mq 根绝事务状态设置消息是否可以被消费(或回滚遗弃)
  8. 消费者正常订阅消费消息

demo

alibb-rocketmq-producer 工程

gradle 一些主要的包引用和配置

plugins {
    id 'org.springframework.boot' version '2.3.2.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
}

ext {
    set('springCloudAlibabaVersion', "2.2.3.RELEASE")
    set('springCloudVersion', "Hoxton.SR8")
}
//版本冲突可能导致启动异常 官网推荐整合的各个资源的版本
// https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.alibaba.cloud:spring-cloud-starter-stream-rocketmq'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        mavenBom "com.alibaba.cloud:spring-cloud-alibaba-dependencies:${springCloudAlibabaVersion}"
    }
}

application.properties

#配置rocketmq的nameserver地址
spring.cloud.stream.rocketmq.binder.name-server=ip:port

spring.application.name=aibb-rocketmq-producer
server.port=8090


#自定义消息
#MsgSource中的normalMsg
spring.cloud.stream.bindings.normalMsg.destination=normal-topic
#消息的默认类型 the default type of the msg
spring.cloud.stream.bindings.normalMsg.content-type=application/json
spring.cloud.stream.bindings.normalMsg.group=normal-group

#MsgSource中的 orderlyMsg 顺序发送
spring.cloud.stream.bindings.orderlyMsg.destination=orderly-topic
spring.cloud.stream.bindings.orderlyMsg.content-type=application/json
spring.cloud.stream.rocketmq.bindings.orderlyMsg.producer.group=orderly-group
#消息同步发送
spring.cloud.stream.rocketmq.bindings.orderlyMsg.producer.sync=true


#MsgSource中的 orderlyMsg 顺序发送
spring.cloud.stream.bindings.transactionMsg.destination=transaction-topic
spring.cloud.stream.bindings.transactionMsg.content-type=application/json
spring.cloud.stream.rocketmq.bindings.transactionMsg.producer.group=msgRoducerGroup
#事务消息
spring.cloud.stream.rocketmq.bindings.transactionMsg.producer.transactional=true
/**
 * @Auther: jmx
 * @Date: 2021/01/12
 * @Description: MsgConfig 自定义Mq的相关配置
 * @Version 1.0.0
 */
@Configuration
@EnableBinding(MsgSource.class)
public class MsgConfig {
}

package com.study.alibbrocketmqproducer.msgproducer.listener;

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;

@RocketMQTransactionListener(
 txProducerGroup = "msgRoducerGroup",
 corePoolSize = 2,
 maximumPoolSize = 5
)
public class MsgMQLocalTransactionListenerImpl implements RocketMQLocalTransactionListener {

    // 发送half消息成功之后,mq返回成功,回调执行本地事务操作,并返回执行事务的结果给MQ,
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        String type = msg.getHeaders().get("status").toString();
        System.out.println("executeLocalTransaction: msg-"+ msg + "-arg:" + arg +"-status:"+type);
        switch (type) {
            case "1":
                System.out.println("事务执行状态未知");
                return RocketMQLocalTransactionState.UNKNOWN;
            case "2":
                System.out.println("事务执行状态成功");
                return RocketMQLocalTransactionState.COMMIT;
            case "3":
                System.out.println("事务执行状态失败");
                return RocketMQLocalTransactionState.ROLLBACK;

        }
        return RocketMQLocalTransactionState.ROLLBACK ;
    }

    //当Mq 没有收到我们返回的事务状态信息 或者 返回的事务状态为RocketMQLocalTransactionState.UNKNOWN,会
    // 再次发送消息过来确定消息的状态
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        System.out.println("checkLocalTransaction:"+msg);
        return RocketMQLocalTransactionState.COMMIT;
    }
}
package com.study.alibbrocketmqproducer.msgproducer.model;
/**
 * @Auther: jmx
 * @Date: 2021/01/12
 * @Description: MsgModel
 * @Version 1.0.0
 */
public class MsgModel {

    private String content;
    private long time;
    public MsgModel() {
    }
    public MsgModel(String content,long time) {
        this.content = content;
        this.time = time ;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    @Override
    public String toString() {
        return "MsgModel{" +
                "content='" + content + '\'' +
                ", time=" + time +
                '}';
    }
}

package com.study.alibbrocketmqproducer.msgproducer.service;

import com.study.alibbrocketmqproducer.msgproducer.model.MsgModel;
import com.study.alibbrocketmqproducer.msgproducer.source.MsgSource;
import org.apache.rocketmq.common.message.MessageConst;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeTypeUtils;

/**
 * @Auther: jmx
 * @Date: 2021/01/12
 * @Description: MsgSendService
 * @Version 1.0.0
 */
@Service
public class MsgSendService {

    @Autowired
    private MsgSource msgSource;

    //普通发送
    public void sendNormal(String content) {
        Message<String> msg = MessageBuilder.withPayload(content).build();
        msgSource.normalMsg().send(msg);
    }
    //顺序发送
    public void sendOrderly(String content,String tag) {
        MsgModel msgModel = new MsgModel(content,System.currentTimeMillis());
        Message<MsgModel> msg = MessageBuilder.withPayload(msgModel)
                .setHeader(MessageConst.PROPERTY_TAGS,tag)
                .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
                .build();
        msgSource.orderlyMsg().send(msg);
    }
    //事务消息 half 消息,这条消息去到MQ 在本地事务没有执行成功之前,对于消费者来说是不可见,无法消费的,这里的事务操作是指确保
    // 本地事务执行成功之后,消息发送到MQ并可以被消费
    // 对于消费者来说,只要消费MQ的MQ信息就好,但是要确保,事务消息的消费结果是幂等性的,即多次消费结果都一样,MQ不确保消息被消费一次
    public void sendTransaction(String content,int status){
        MsgModel msgModel = new MsgModel(content,System.currentTimeMillis());
        Message<MsgModel> msg = MessageBuilder.withPayload(msgModel)
                .setHeader("status",status)
                .setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
                .build();
        msgSource.transactionMsg().send(msg);
    }
}

package com.study.alibbrocketmqproducer.msgproducer;

import com.study.alibbrocketmqproducer.msgproducer.service.MsgSendService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Auther: jmx
 * @Date: 2021/01/12
 * @Description: MsgController
 * @Version 1.0.0
 */
@RestController
@RequestMapping("/msg")
public class MsgController {
    @Autowired
    private MsgSendService msgSendService;

    @RequestMapping("/normal/{content}")
    public ResponseEntity<String> normal(@PathVariable("content") String content){
        msgSendService.sendNormal(content);
        return ResponseEntity.ok().body("normal ok");
    }
    @RequestMapping("/orderly")
    public String orderly(@RequestParam(name = "c") String content){
        msgSendService.sendOrderly(content,"orderly");
        return "orderly ok";
    }

    @RequestMapping("/transaction")
    public String transaction(String content,int status){
        msgSendService.sendTransaction(content,status);
        return "transaction ok";
    }
}

alibb-rocketmq-consumer 工程

gradle 一些主要的包引用和配置

plugins {
    id 'org.springframework.boot' version '2.3.2.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
}

ext {
    set('springCloudAlibabaVersion', "2.2.3.RELEASE")
    set('springCloudVersion', "Hoxton.SR8")
}
//版本冲突可能导致启动异常 官网推荐整合的各个资源的版本
// https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'com.alibaba.cloud:spring-cloud-starter-stream-rocketmq'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        mavenBom "com.alibaba.cloud:spring-cloud-alibaba-dependencies:${springCloudAlibabaVersion}"
    }
}

application.properties

spring.application.name=aibb-rocketmq-consumer
server.port=8091
spring.cloud.stream.rocketmq.binder.name-server=ip:port
#自定义消息
#MsgSink中的normalMsg
spring.cloud.stream.bindings.normalMsg.destination=normal-topic
#消息的默认类型 the default type of the msg
spring.cloud.stream.bindings.normalMsg.content-type=application/json
spring.cloud.stream.bindings.normalMsg.group=normal-group

#MsgSink 中的 orderlyMsg 顺序发送
spring.cloud.stream.bindings.orderlyMsg.destination=orderly-topic
spring.cloud.stream.bindings.orderlyMsg.content-type=application/json
#spring.cloud.stream.rocketmq.bindings.orderlyMsg.consumer.group=orderly-group
spring.cloud.stream.bindings.orderlyMsg.group=orderly-group
#消息同步发送
spring.cloud.stream.rocketmq.bindings.orderlyMsg.consumer.orderly=true


#MsgSink中的 transactionMsg
spring.cloud.stream.bindings.transactionMsg.destination=transaction-topic
spring.cloud.stream.bindings.transactionMsg.content-type=application/json
spring.cloud.stream.bindings.transactionMsg.group=msgRoducerGroup
spring.cloud.stream.bindings.transactionMsg.consumer.concurrency=5
package com.study.alibbrocketmqconsumer.msgconsumer.config;

import com.study.alibbrocketmqconsumer.msgconsumer.sink.MsgSink;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableBinding(MsgSink.class)
public class MsgConfig {
}

package com.study.alibbrocketmqconsumer.msgconsumer.model;
/**
 * @Auther: jmx
 * @Date: 2021/01/12
 * @Description: MsgModel
 * @Version 1.0.0
 */
public class MsgModel {

    private String content;
    private long time;
    public MsgModel() {
    }
    public MsgModel(String content,long time) {
        this.content = content;
        this.time = time ;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    @Override
    public String toString() {
        return "MsgModel{" +
                "content='" + content + '\'' +
                ", time=" + time +
                '}';
    }
}

package com.study.alibbrocketmqconsumer.msgconsumer.sink;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
/**
 * @Auther: jmx
 * @Date: 2021/01/13
 * @Description: MsgSink
 * @Version 1.0.0
 */
public interface MsgSink {

    @Input("normalMsg")
    SubscribableChannel normalMsg();

    //顺序组消息
    @Input("orderlyMsg")
    SubscribableChannel orderlyMsg();

    //事务消息
    @Input("transactionMsg")
    SubscribableChannel transactionMsg();
}

package com.study.alibbrocketmqconsumer.msgconsumer;

import com.study.alibbrocketmqconsumer.msgconsumer.model.MsgModel;
import com.study.alibbrocketmqconsumer.msgconsumer.sink.MsgSink;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.annotation.StreamRetryTemplate;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Service;

@Service
public class ReceivceService {
    @Autowired
    private MsgSink msgSink;

    @StreamListener("normalMsg")
    public void normalMsg(String msg){
        System.out.println("normalMsg:"+msg);
    }
    @StreamListener("orderlyMsg")
    public void orderlyMsg(@Payload MsgModel msg){
        System.out.println("orderlyMsg:"+msg);
    }
    @StreamListener("transactionMsg")
    public void transactionMsg(MsgModel msg){
        System.out.println("transactionMsg:"+msg);
    }
}

如果要测试死信队列(集群消费的无序消息才会出现,顺序消息在测试中一直重试)可以将
部署的broker下broker.conf 中添加
messageDelayLevel=5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s(测试使用)
默认配置为:messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

这个死信队列broker 会自动创建,然后需要修改权限,否则在消息选下下搜消息的时候是搜不出来的
Spring Cloud Stream RocketMq 的使用_第2张图片

你可能感兴趣的:(SpringCloud,rocketMq,消息队列)