SpringBoot整合RocketMQ实现多事务消息发送

RocketMQ事务消息用来解决本地事务和消息提交的状态同步。意思就是:本地事务成功,消息提交成功;本地事务失败,消息提交失败。否则会出现数据不一致的情况。本地事务提交并且创建成功,再保证消费者能成功消费消息,分布式事务就可以实现,如果一次业务操作需要关联很多微服务,各服务只要保证自己事务和消息的一致性就可以实现整个分布式事务的一致性,只不过模式为最终一致。

RocketMQ针对事务消息采用的机制是,创建半消息(预处理消息 RMQ_SYS_TRANS_HALF_TOPIC),不会被消费,保存在特殊队列中,监听器返回结果后更改状态,如果长时间没有返回结果,则定时查询。类似我们对接第三方支付接口一样,调用后等待结果,如果获取结果异常,通过查询接口获取最终结果。

异常情况

消息消费异常,需要人工干预
本地事务超时、异常,通过定时查询获取结果,用来提交或回滚消息

生产者配置

生产者核心Service类图
在这里插入图片描述

1. pom.xml引入依赖包

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-spring-boot-starter</artifactId>
        <version>2.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
</dependencies>

2. yml配置

server:
  port: 8081

spring:
  application:
    name: consumer
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        file-extension: yaml
      discovery:
        server-addr: localhost:8848

rocketmq:
  name-server: 127.0.0.1:9876
  producer:
    group: Producer-Group

3. 定义事务消息必须实现的接口

public interface TransactionalMQService {
    /**
     * 本地使用事务执行
     * @param msg
     * @return
     */
    boolean process(String msg);

    /**
     * 查询本地事务结果
     * @param msg
     * @return
     */
    boolean checkSuccess(String msg);
}

4. 抽象类AbstractRocketMQListener

实现TransactionListener接口,并实现对应方法;实现ApplicationContextAware接口用来获取执行对象的代理对象
此处必须使用TransactionalMQService接口获取,否则有可能获取不到代理对象,导致事务失效。

@Component
public abstract class AbstractRocketMQListener implements TransactionListener, ApplicationContextAware {

    @Value("${rocketmq.producer.group}")
    private String producerGroupName;

    private static final String TOPIC = "TOPIC_A";

    private static ApplicationContext APPLICATION_CONTEXT;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        APPLICATION_CONTEXT = applicationContext;
    }

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 执行本地事务 并获取执行结果
     *
     * @param message
     * @param o
     * @return
     */
    @Override
    public LocalTransactionState executeLocalTransaction(Message message, Object o) {
        String msg = new String(message.getBody());
        System.out.println("------------消息执行本地事务--------------");
        try {
            // 此处必须通过接口强制转换,否则很可能获取不到代理类
            boolean isCommit = ((TransactionalMQService) APPLICATION_CONTEXT.getBean(this.getClass())).process(msg);
            if (isCommit) {
                System.out.println("------------消息执行提交--------------");
                return LocalTransactionState.COMMIT_MESSAGE;
            }
            System.out.println("------------消息执行回滚--------------");
            return LocalTransactionState.ROLLBACK_MESSAGE;
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("------------消息执行异常--------------");
        return LocalTransactionState.UNKNOW;
    }

    /**
     * 查询本地事务执行结果
     *
     * @param messageExt
     * @return
     */
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
        System.out.println("------------消息查询结果--------------");
        // 此处必须通过接口强制转换,否则很可能获取不到代理类
        boolean success = ((TransactionalMQService) APPLICATION_CONTEXT.getBean(this.getClass())).checkSuccess(new String(messageExt.getBody()));
        if (success) {
            System.out.println("------------查询消息提交--------------");
            return LocalTransactionState.COMMIT_MESSAGE;
        }
        System.out.println("------------查询消息回滚--------------");
        return LocalTransactionState.ROLLBACK_MESSAGE;
    }

    /**
     * 发送事务消息
     *
     * @param tag 标签
     * @param msg 消息内容
     * @return
     */
    boolean sendTransactionalMsg(String tag, String msg) {
        String destination = StringUtils.isEmpty(tag) ? TOPIC : TOPIC + ":" + tag;
        // 此处如果使用MQ其他版本,可能导致强转异常
        ((TransactionMQProducer) rocketMQTemplate.getProducer()).setTransactionListener(this);
        org.springframework.messaging.Message message = MessageBuilder.withPayload(msg).build();
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(destination, message, null);
        System.out.println("Send transaction msg result: " + sendResult);
        return sendResult.getSendStatus() == SendStatus.SEND_OK;
    }

    /**
     * 发送同步消息
     *
     * @param tag 标签
     * @param msg 消息内容
     * @return
     */
    boolean sendSyncMsg(String tag, String msg) {
        String destination = StringUtils.isEmpty(tag) ? TOPIC : TOPIC + ":" + tag;
        org.springframework.messaging.Message<String> message = MessageBuilder.withPayload(msg).build();
        SendResult sendResult = rocketMQTemplate.syncSend(destination, message);
        System.out.println("Send syn msg result: " + sendResult);
        return sendResult.getSendStatus() == SendStatus.SEND_OK;
    }
}

5. 定义接口

public interface UserService {
    UserDTO getByUsername(String username);
	
	// 创建消息并处理本地逻辑
    boolean sendCreateUserMsg(String msg);
}

6. 添加实现类

@Service
public class UserServiceImpl extends AbstractRocketMQListener implements UserService, TransactionalMQService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDTO getByUsername(String username) {
        return userMapper.selectByUsername(username);
    }

    @Override
    public boolean sendCreateUserMsg(String msg) {
        return super.sendTransactionalMsg("create_user", msg);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public boolean process(String msg) {
        UserDTO user = JSONObject.parseObject(msg, UserDTO.class);
        String[] names = user.getUsername().split("_");
        for (String username : names) {
            user.setUsername(username);
            user.setCreatedTime(new Date());
            if (userMapper.add(user) != 1) {
                throw new RuntimeException("Add user failed");
            }
        }
        return true;
    }

    @Override
    public boolean checkSuccess(String msg) {
        // 查询数据是否已成功
        UserDTO user = JSONObject.parseObject(msg, UserDTO.class);
        String[] names = user.getUsername().split("_");
        for (String username : names) {
            if (userMapper.selectByUsername(username) == null) {
                return false;
            }
        }
        return true;
    }
}

7. User实体类和Mapper

比较简单,不粘贴了,可根据Controller反推表字段。

8. 定义Controller

@RestController
public class IndexController {
    @Autowired
    private UserService userService;

    @GetMapping("/index")
    private String index(@RequestParam("name") String name) {
        UserDTO user = new UserDTO();
        user.setUsername(name);
        user.setType(0);
        boolean success = userService.sendCreateUserMsg(JSONObject.toJSONString(user));
        return String.valueOf(success);
    }
}

9. 执行结果

访问url: http://127.0.0.1:8081/index?name=1991_1991 事务成功回滚

------------消息执行本地事务--------------
org.springframework.dao.DuplicateKeyException: 
### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1991' for key 'idx_username'
### The error may exist in com/cloud/demo/consumer/mapper/UserMapper.xml
### The error may involve com.cloud.demo.consumer.mapper.UserMapper.add-Inline
### The error occurred while setting parameters
### SQL: insert into u_user         (`username`, `type`, `created_time`)         values(?, ?, ?)
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1991' for key 'idx_username'
... 异常消息省略
------------消息执行异常--------------
Send transaction msg result: SendResult [sendStatus=SEND_OK, msgId=0A09A7D0E4B018B4AAC28A0E02170008, offsetMsgId=null, messageQueue=MessageQueue [topic=TOPIC_A, brokerName=DESKTOP-FEANKDA, queueId=0], queueOffset=200]

访问url: http://127.0.0.1:8081/index?name=1991_1990 事务成功提交

------------消息执行本地事务--------------
------------消息执行提交--------------
Send transaction msg result: SendResult [sendStatus=SEND_OK, msgId=0A09A7D0E4B018B4AAC28A0FCA78000A, offsetMsgId=null, messageQueue=MessageQueue [topic=TOPIC_A, brokerName=DESKTOP-FEANKDA, queueId=3], queueOffset=202]

消费者配置

添加时间监听器

如果抛出异常,消息会被回放至队列中,可再次消费,一般重复3~5次即可,可配置修改
不抛出异常,方法执行完毕,消息被消费完毕。

@Component
@RocketMQMessageListener(topic = "TOPIC_A", consumerGroup = "Group_create_user", selectorExpression = "create_user")
public class RocketMQConsumerService implements RocketMQListener<String> {

    public void onMessage(String s) {
        System.out.println("Receive msg: " + s);
//        throw new RuntimeException("0000");
    }
}

// 如果有多个消费者,selectorExpression可用于区分tag,由不同的consumerGroup区分 如下:

@Component
@RocketMQMessageListener(topic = "TOPIC_A", consumerGroup = "Group_increase_account", selectorExpression = "increase_account")
public class RocketMQConsumerService implements RocketMQListener<String> {

    public void onMessage(String s) {
        System.out.println("Receive increase account msg: " + s);
//        throw new RuntimeException("0000");
    }
}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
SpringBoot整合RocketMQ发送事务消息的步骤如下: 首先,定义一个生产者类,使用@Component注解将其标记为一个组件,并使用@Autowired注解注入RocketMQTemplate实例。在该类中,可以编写一个sendMsg方法来发送消息。该方法接受两个参数,分别是topic和msg,使用MessageBuilder构建一个消息对象,并通过rocketMQTemplate的sendMessageInTransaction方法发送消息。需要注意的是,该方法的第一个参数要与@RocketMQTransactionListener注解中的txProducerGroup属性保持一致。\[1\] 其次,定义一个消费者类,使用@Component和@RocketMQMessageListener注解将其标记为一个组件,并指定topic和consumerGroup。该类需要实现RocketMQListener接口,并实现其中的onMessage方法,用于处理接收到的消息。\[2\] 最后,在引导类中使用@SpringBootApplication注解标记该类为Spring Boot应用程序的入口,并在main方法中调用SpringApplication的run方法启动应用程序。\[3\] 通过以上步骤,就可以在SpringBoot整合RocketMQ发送事务消息了。 #### 引用[.reference_title] - *1* *2* *3* [springboot 整合 rocketmq 发送事务消息](https://blog.csdn.net/weixin_42494845/article/details/109362030)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值