Bootstrap

Spring boot整合RocketMq(最详细步骤)

SpringBoot集成RocketMQ的starter依赖是由Spring社区提供的,目前正在快速迭代的过程当中,要特别注意版本,不同版本之间的差距非常大。

引入关键依赖:

<!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-spring-boot-starter -->
		<dependency>
			<groupId>org.apache.rocketmq</groupId>
			<artifactId>rocketmq-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

PS:rocketmq4.9.4和4.9.3最接近,所以引入2.2.2版本的依赖

 

589fca7d3d745358967bcfd0ac63485e.png

 

1、生产者

项目:springboot-rocketmq-producer

完整依赖

<?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 https://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.7.10</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.cctv</groupId>
	<artifactId>springboot-rocketmq-producer</artifactId>

	<name>springboot-rocketmq-producer</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-spring-boot-starter -->
		<dependency>
			<groupId>org.apache.rocketmq</groupId>
			<artifactId>rocketmq-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

 

application.yml

rocketmq:
  name-server: 192.168.106.128:9876
  producer:
    group: springBootProducerGroup

 

1、基本消息(并发消息)

我们使用消息生产者分别通过三种方式发送消息,同步发送、异步发送以及单向发送,然后使用消费者来消费这些消息。

  1. 同步发送是指消息生产者已经把消息发送到了borker,borker同步已经确认发送成功
  2. 异步发送是指消息生产者已经把消息发送到了borker,borker回调确认
  3. 单向发送没有回调,生产者无法确认消息是否发送成功
package com.cctv.rocket.controller;

import com.cctv.rocket.domain.User;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/mq")
public class MQTestController {
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    private final String topic = "TopicTest";
     //可以发送对象信息或者字符串信息("Hello, World!")
    //对象消息
    Message<User> message = MessageBuilder.withPayload(
            new User("zhangsan", 12)).setHeader(RocketMQHeaders.KEYS, "key1").build();

    //基本类型:同步发送消息
    @RequestMapping("/sendMessage1")
    public String sendMessage1() {
        /**
         * 发送同步消息(阻塞当前线程,等待broker返回响应结果,才能往下走,这样不太容易丢失消息)
         * (msgBody也可以是对象,sendResult为返回的发送结果)
         */
        SendResult sendResult1 = rocketMQTemplate.syncSend(topic, "Hello, World!");
        System.out.println("同步发送字符串消息 = " + sendResult1);

        //同步发送发送对象消息
        SendResult sendResult2 = rocketMQTemplate.syncSend(topic, message);
        System.out.println("同步发送对象消息 = " + sendResult2);
        return "同步对象消息发送完成";
    }

    //基本类型:异步发送消息
    @RequestMapping("/sendMessage2")
    public String sendMessage2() {
        /**
         * 发送异步消息(通过线程池执行发送到broker的消息任务,执行完后回调:在SendCallback中可处理相关成功失败时的逻辑)
         * (适合对响应时间敏感的业务场景)----发送完消息不用等待,会有一个回调告诉我有没有发送成功
         */
        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);
            }
        });
        return "异步消息发送完成";
    }

    //基本类型:单向发送:只负责发,不管有没有成功
    @RequestMapping("/sendMessage3")
    public String sendMessage3() {
        //单向发送消息
        rocketMQTemplate.sendOneWay(topic, message);
        return "单向消息发送完成";
    }
}

 

2、顺序消息

SpringBoot如何实现顺序消息? 需要程序保证发送和消费的是同一个 Queue,

rocketmq默认发送的消息是进入多个消息队列,然后消费端多线程并发消费,所以默认情况,不是順序消费消息的;

RocketMQTemplate给我们提供了SendOrderly方法,来实现发送顺序消息,

一般我们用syncSendOrderly方法发送同步顺序消息。

 

PS:topic主题(相当于路由器),消息都是往topic发送的,再由topic路由到消息队列进行存储,一般默认对应四个存储信息队列mq,生产者通过topic发送消息。并发消息会通过轮询的方式往不同的消息队列中存储消息,消费者也进行并发消费。

顺序消息要保证通过topic所有的消息都按照顺序存储在同一个mq里,消费者也要消费同一个mq里面的。(消费者按照发送顺序进行顺序消费)

一般我们用syncSendOrderly方法发送同步顺序消息。消费者消费模式设置为ConsumeMode.ORDERLY保证顺序消费。

 

参数一:topic 如果想添加tag,可以使用"topic:tag"的写法

参数二:消息内容

参数三:hashKey 使用此参数选择队列。 例如:orderId,productId…

因为broker会管理多个消息队列,这个hashKey参数,主要用来计算选择队列的,一般可以把订单ID,产品ID作为参数值;发送到一个队列,这样方便产生顺序队列;

 

发送端

package com.cctv.rocket.controller;

import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 模拟两个订单发送消息
 */
@RestController
public class RocketMQOrderController {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    /**
     * 发送同步顺序消息
     */
    @RequestMapping("/testSyncOrderSend")
    public void testSyncSend(){
        //参数一:topic   如果想添加tag,可以使用"topic:tag"的写法
        //参数二:消息内容
        //参数三:hashKey 用来计算决定消息发送到哪个消息队列, 一般是订单ID,产品ID等
        rocketMQTemplate.syncSendOrderly("test-topic-orderly","111111创建","111111");
        rocketMQTemplate.syncSendOrderly("test-topic-orderly","111111支付","111111");
        rocketMQTemplate.syncSendOrderly("test-topic-orderly","111111完成","111111");
        rocketMQTemplate.syncSendOrderly("test-topic-orderly","222222创建","222222");
        rocketMQTemplate.syncSendOrderly("test-topic-orderly","222222支付","222222");
        rocketMQTemplate.syncSendOrderly("test-topic-orderly","222222完成","222222");
    }
}

 

顺序消费消息

RocketMQMessageListener有个属性consumeMode,默认是ConsumeMode.CONCURRENTLY ,我们要改成ConsumeMode.ORDERLY;

package com.cctv.rocket.base;

import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 消费顺序消息
 * 配置RocketMQ监听
 *
 * ConsumeMode.ORDERLY:顺序消费
 */
@Component
@RocketMQMessageListener(consumerGroup = "test",topic = "test-topic-orderly",consumeMode = ConsumeMode.ORDERLY)
public class RocketMQCommonConsumerListener implements RocketMQListener<String> {
    @Override
    public void onMessage(String s) {
        System.out.println("consumer 顺序消费,收到消息:"+s);
    }
}

 

3、广播消息

注意:

广播消息与生产者无关,只跟消费者有关

RocketMQ有2种消费模式:

  1. 集群消费模式,默认
  2. 广播消费模式:不支持顺序消费,即ConsumeMode不能是ORDERLY。
  3. 在集群状态(MessageModel.CLUSTERING)下,每一条消息只会被同一个消费者组中的一个实例消费到。
  4. 而广播模式则是把消息发给了所有订阅了对应主题的消费者,而不管消费者是不是同一个消费者组。

 

PS:广播消息:同一组不同消费者都能收到broker中的消息

集群消息:同一组只有一个消费者可以收到broker中的消息

 

两种消费模式(集群和广播):

messageModel = MessageModel.CLUSTERING(集群消费)

messageModel = MessageModel.BROADCASTING(广播消费)

 

4、延迟消息

延迟消息实现的效果就是在调用rocketMQTemplate.syncSend方法后,消息立即发出,但是消费端不会立马消费到。这是RocketMQ特有的一个功能。

那会延迟多久呢?延迟时间的设置就是在Message消息对象上设置一个延迟级别;

开源版本的RocketMQ中,只支持18个固定的延迟级别,1到18分别对应1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h。

    //延迟消息
    @RequestMapping("/sendMessage4")
    public String sendMessage6() {

        // syncSend(String destination, Message<?> message, long timeout, int delayLevel)
        
        // timeout 超时时间----跟延时时间没关系
        // int delayLevel 延时时间 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h  级别 1 及 1s,最大延时 2h
        SendResult sendResult1 = rocketMQTemplate.syncSend(topic, message, 2000, 3);
        System.out.println("延迟消息 = " + sendResult1);

        return "延迟消息发送完成";
    }

 

5、批量消息

注意:

批量消息只跟生产者有关,与消费者无关

批量消息是指将多条消息合并成一个批量消息,一次发送出去。这样的好处是可以减少网络IO,提升吞吐量。

注意事项:批量发送的数据量不能超过 4 m,超过后需要将消息分割。

 //批量消息发送,
    // 注意事项:批量发送的数据量不能超过 4 m,超过后需要将消息分割。
    @RequestMapping("/sendMessage5")
    public String sendMessage7() {
        List<Message<User>> messages = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setName("zhangsan" + (i + 1));
            user.setAge(i);
            Message<User> message = MessageBuilder.withPayload(
                    user).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE).build();
            messages.add(message);
        }
        SendResult sendResult1 = rocketMQTemplate.syncSend(topic, messages);
        System.out.println("批量消息 = " + sendResult1);

        return "批量消息发送完成";
    }

 

6、消息过滤

根据tag过滤:发送消息可以发送到topic下面指定的标记topic,消费端要指定tag,如果不指定,默认消费topic下所有消息。

    //过滤消息发送,根据tag过滤
    //注意:topic 如果想添加tag,使用"topic:tag"的写法
    @RequestMapping("/sendMessage6")
    public String sendMessage4() {
        String topicTag = topic + ":TagA";
        SendResult sendResult1 = rocketMQTemplate.syncSend(topicTag, message);
        System.out.println("同步带TAG的消息 = " + sendResult1);

        return "过滤消息发送完成";
    }

 

7、事务消息

 

什么是事务消息

事务消息是在分布式系统中保证最终一致性的两阶段提交(TCC模式)的消息实现。他可以保证本地事务执行与消息发送两个操作的原子性,也就是这两个操作一起成功或者一起失败。

其次,事务消息只保证消息发送者的本地事务与发消息这两个操作的原子性,因此,事务消息只涉及到消息发送者,对于复杂的分布式事务,RocketMQ提供的事务消息也是目前业内最佳的方案

 

事务消息的关键是实现了一个事务监听器,这个事务监听器就是事务消息的关键控制器(RocketMQLocalTransactionListener)。

 

事务消息的实现机制

 

ded35088f4c1af1d296d9b7d66ee5bf6.png

 

事务消息机制的关键是在发送消息时,会将消息转为一个half半消息,并存入RocketMQ内部的一个 RMQ_SYS_TRANS_HALF_TOPIC 这个Topic,这样对消费者是不可见的。再经过一系列事务检查通过后,再将消息转存到目标Topic,这样对消费者就可见了。

 

1:场景模拟

场景:假设我们现在有这样的业务:用户充值话费会获得积分,且1元=1积分,

用户服务-------------->充值100元,

积分服务-------------->要对该用户增加100积分

 

分析:像这种跨服务、跨库的操作,我们要保证这两个操作要么一起成功、要么一起失败,采用RocketMQ的方案就是:

RocketMQ事务消息+本地事务+监听消费,来达到最终一致性(两阶段提交协议TCC实现)

 

2:业务代码表

我这里是模拟,所以用户表和积分表就放在一个库(rocketmq库)里

1)用户表

CREATE TABLE `t_user` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户表',
   `name` varchar(16) NOT NULL COMMENT '姓名',
   `id_card` varchar(32) NOT NULL COMMENT '身份证号',
   `balance` int(11) NOT NULL DEFAULT '0' COMMENT '余额',
   `state` tinyint(1) DEFAULT NULL COMMENT '状态(1在线,0离线)',
   `vip_flag` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'VIP用户标识(1是,0否)',
   `create_time` datetime NOT NULL COMMENT '创建时间',
   `last_login_time` datetime DEFAULT NULL COMMENT '最后一次登录时间',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4

(2)积分表

CREATE TABLE `t_credit` (
   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '积分表',
   `user_id` int(11) NOT NULL COMMENT '用户id',
   `username` varchar(16) NOT NULL COMMENT '用户姓名',
   `integration` int(11) NOT NULL DEFAULT '0' COMMENT '积分',
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4

 

(3)事务日志表

CREATE TABLE `t_mq_transaction_log` (
   `transaction_id` varchar(64) NOT NULL COMMENT '事务id',
   `log` varchar(64) NOT NULL COMMENT '日志',
   PRIMARY KEY (`transaction_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

 

插入数据

INSERT INTO `rocket`.`t_user`(`id`, `name`, `id_card`, `balance`, `state`, `vip_flag`, `create_time`, `last_login_time`) VALUES (1, 'zhangsan', '433024186526325232', 100, NULL, 0, '2023-08-07 00:39:29', '2023-08-07 00:39:33');
INSERT INTO `rocket`.`t_credit`(`id`, `user_id`, `username`, `integration`) VALUES (2, 1, 'zhangsan', 100);

3:代码实现

 

59921f9e27b72f6e67719978fe0f0f98.png

pom.xml

<?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 https://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.7.10</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.cctv</groupId>
	<artifactId>springboot-rocketmq-transaction-producer</artifactId>

	<name>springboot-rocketmq-producer</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-spring-boot-starter -->
		<dependency>
			<groupId>org.apache.rocketmq</groupId>
			<artifactId>rocketmq-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- mysql数据库驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.30</version>
		</dependency>

		<!-- mybatis-plus的场景启动器 -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.2</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

 

application.yml

server:
  port: 8089
rocketmq:
  name-server: 192.168.106.128:9876
  producer:
    group: Tx_Charge_Group

spring:
  #项目名
  application:
    name: rocketmq-transaction-producer
  datasource:
    username: root
    password: root123
    url: jdbc:mysql://localhost:3306/rocketmq?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver

#输出SQL日志
logging:
  level:
    root: info
    com.cctv.rocket.mapper: debug

 

 

MQ事务生产者

 

MQController

package com.cctv.rocket.controller;

import com.cctv.rocket.dto.Result;
import com.cctv.rocket.dto.UserCharge;
import com.cctv.rocket.service.MQTXProducerService;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/mq")
public class MQController {
    @Autowired
    MQTXProducerService mqtxProducerService;

    @PostMapping("/charge")
    public Result<TransactionSendResult> charge(UserCharge userCharge) {
        
        //发送事务消息
        TransactionSendResult sendResult = mqtxProducerService.sendTranMsg(userCharge);
        return Result.OK(sendResult);
    }
}

 

 

MQTXProducerService

package com.cctv.rocket.service;

import com.alibaba.fastjson.JSON;
import com.cctv.rocket.dto.UserCharge;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

import java.util.UUID;

@Slf4j
@Component
public class MQTXProducerService {

    private static final String Topic = "RLT_TEST_TOPIC";
    private static final String Tag = "charge";

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发送事务消息
     *
     * @param userCharge 用户充值信息
     */
    public TransactionSendResult sendTranMsg(UserCharge userCharge) {
        // 生成事务id,就是事务日志表的id
        String transactionId = UUID.randomUUID().toString().replace("-", "");
        log.info("transactionId={}", transactionId);

        // 发送事务消息(参1:topic+tag,参2:消息体(可以传参),参3:发送参数)
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(Topic + ":" + Tag,
                MessageBuilder.withPayload(userCharge).setHeader(RocketMQHeaders.TRANSACTION_ID, transactionId).build(),
                userCharge);
        log.info("【事务消息】sendResult={}", JSON.toJSONString(sendResult));
        return sendResult;
    }
}

 

本地事务监听器:MQTXLocalService

package com.cctv.rocket.service;

import com.cctv.rocket.domain.TransactionLog;
import com.cctv.rocket.dto.UserCharge;
import com.cctv.rocket.mapper.TransactionLogMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @description: 事务消息监听器
 * 关于@RocketMQTransactionListener(txProducerGroup = "springBootGroup2")
 * 这个注解。2.0.4版本中,是需要指定txProducerGroup指向一个消息发送者组。

 * 到了2.1.1版本,只能指定rocketMQTemplateBeanMame,
 * 也就是说如果你有多个发送者组需要有不同的事务消息逻辑,
 * 那就需要定义多个RocketMQTemplate。
 **/
@Slf4j
@RocketMQTransactionListener(rocketMQTemplateBeanName = "rocketMQTemplate")
public class MQTXLocalService implements RocketMQLocalTransactionListener {

    @Autowired
    private UserService userService;
    @Autowired
    private TransactionLogMapper transactionLogMapper;

    /**
     * 用于执行本地事务的方法
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object obj) {
        // 获取消息体里参数
        MessageHeaders messageHeaders = message.getHeaders();
        String transactionId = (String) messageHeaders.get(RocketMQHeaders.TRANSACTION_ID);
        log.info("[执行本地事务]消息体参数:transactionId={}", transactionId);

        // 执行本地事务:增加用户余额+保存mq日志
        try {
            UserCharge userCharge = (UserCharge) obj;
            userService.addBalance(userCharge, transactionId);
            //int i = 1/0;
            return RocketMQLocalTransactionState.COMMIT; // 正常:向MQ Server发送commit消息
        } catch (Exception e) {
            log.error("[执行本地事务]发生异常,事务消息状态UNKNOWN", e);

            //对UNKNOWN状态的消息进行状态回查的时间默认是1分钟
            return RocketMQLocalTransactionState.UNKNOWN; // 异常:向MQ Server发送UNKNOWN消息
        }
    }

    /**
     * 用于回查本地事务执行结果的方法
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        MessageHeaders headers = message.getHeaders();
        String transactionId = headers.get(RocketMQHeaders.TRANSACTION_ID, String.class);
        log.info("【回查本地事务】transactionId={}", transactionId);

        // 根据事务id查询事务日志表
        Map<String,Object> map = new HashMap<>();
        map.put("transaction_id",transactionId);
        List<TransactionLog> transactionLogs = transactionLogMapper.selectByMap(map);
        if (null == transactionLogs && transactionLogs.size() == 0) { // 没查到表明本地事务执行失败,通知回滚
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        return RocketMQLocalTransactionState.COMMIT; // 查到表明本地事务执行成功,提交
    }
}

 

 

MQTXConsumerService

package com.cctv.rocket.consumer;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.cctv.rocket.domain.Credit;
import com.cctv.rocket.dto.UserCharge;
import com.cctv.rocket.mapper.CreditMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RocketMQMessageListener(topic = "RLT_TEST_TOPIC", selectorExpression = "charge", consumerGroup = "Con_Group_Four") // topic、tag保持一致
public class MQTXConsumerService implements RocketMQListener<UserCharge> {

    @Autowired
    private CreditMapper creditMapper;

    @Override
    public void onMessage(UserCharge userCharge) {
        // 一般真实环境这里消费前,得做幂等性判断,防止重复消费
        // 方法一:如果你的业务中有某个字段是唯一的,有标识性,如订单号,那就可以用此字段来判断
        // 方法二:新建一张消费记录表t_mq_consumer_log,字段consumer_key是唯一性,能插入则表明该消息还未消费,往下走,否则停止消费
        // 我个人建议用方法二,根据你的项目业务来定义key,这里我就不做幂等判断了,因为此案例只是模拟,重在分布式事务

        // 给用户增加积分
        UpdateWrapper<Credit> updateWrapper = new UpdateWrapper<>();
        updateWrapper.set("user_id",userCharge.getUserId());
        updateWrapper.set("integration",userCharge.getChargeAmount());
        int i = creditMapper.update(new Credit(),updateWrapper);//.addNumber(userCharge.getUserId(), userCharge.getChargeAmount());
        if (1 == i) {
            log.info("【MQ消费】用户增加积分成功,userCharge={}", JSONObject.toJSONString(userCharge));
        } else {
            log.error("【MQ消费】用户充值增加积分消费失败,userCharge={}", JSONObject.toJSONString(userCharge));
        }
    }
}

4:事务消息测试

测试:http://localhost:8089/mq/charge?userId=1&chargeAmount=1000

 

e9727e857eb10dc6b6eab47ce99e0e0f.png

 

 

 

2、消费者

项目:springboot-rocketmq-consumer

消费者消费消息有两种模式:

  1. 一种是消费者主动去Broker上拉取消息的拉模式
  2. 另一种是消费者等待Broker把消息推送过来的推模式(推介使用)

 

推模式

 

pom.xml

<dependency>
			<groupId>org.apache.rocketmq</groupId>
			<artifactId>rocketmq-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

application.yml

server:
  port: 8081
rocketmq:
  name-server: 192.168.106.128:9876

 

package com.cctv.rocket.base;

import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * @RocketMQMessageListener注解的常用属性
 * 两种消费模式(集群和广播):messageModel = MessageModel.CLUSTERING
 * 指定主题:topic = "topicName",
 * 指定标签:selectorExpression = "tag01||tag02",  selectorExpression = "*"
 * 指定消费者:consumerGroup = "my-group1",
 * 指定是否顺序消费:consumeMode= ConsumeMode.CONCURRENTLY,
 * 指定是消费组:consumerGroup
 **/
@Component
@RocketMQMessageListener(messageModel = MessageModel.CLUSTERING,topic = "TopicTest",/*selectorExpression = "TagA",*/consumeMode= ConsumeMode.CONCURRENTLY,consumerGroup = "MyConsumerGroup")
public class SpringConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {
        System.out.println("Received message : "+ message);
    }
}

 

对象发送出去之后自动转换成json字符串

 

 

 

;