RocketMQ事务消息

发送订单的事务消息,预处理

Controller层:

@GetMapping(value = "/transaction")
public String sendTransactionMsg() {
    Order order = new Order("123", "浙江杭州");
    String transactionId = UUID.randomUUID().toString();
    MessageBuilder builder = MessageBuilder.withPayload(order).setHeader(RocketMQHeaders.TRANSACTION_ID, transactionId);
    Message message = builder.build();

    TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction("OrderTransactionGroup", "TopicOrder", message, order.getOrderId());
    return sendResult.getMsgId();
}
public class Order {

    private String orderId;

    private String address;

    public Order(String orderId, String address) {
        this.orderId = orderId;
        this.address = address;
    }

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

Order对象保存订单信息,随机生成一个ID作为消息的事务ID,定义了一个名为OrderTransactionGroup的事务组,用于下一步接收本地事务的监听。

此时消息已经发送到Broker中,但未投递出去,Consumer暂时不能消费这条消息。

订单信息入库的事务操作,提交或回滚

@Component
@RocketMQTransactionListener(txProducerGroup = "OrderTransactionGroup")
public class TransactionMsgListener implements RocketMQLocalTransactionListener {

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        try {
            // 拿到前面生成的事务ID
            String transactionId = (String) message.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
            // 以事务ID为主键,执行本地事务
            Order order = (Order) message.getPayload();
            boolean result = this.saveOrder(order, transactionId);
            return result ? RocketMQLocalTransactionState.COMMIT : RocketMQLocalTransactionState.ROLLBACK;
        } catch (Exception e) {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    private boolean saveOrder(Order order, String transactionId){
        // 事务ID 设置为 唯一键
        // 调用数据库 insert into 订单表
        return true;
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        // 拿到事务ID
        String transactionId = (String) message.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
        // 以事务ID为主键,查询本地事务执行情况
        if (isSuccess(transactionId)) {
            return RocketMQLocalTransactionState.COMMIT;
        }
        return RocketMQLocalTransactionState.ROLLBACK;
    }

    private boolean isSuccess(String transactionId) {
        // 查询数据库 select from 订单表
        return true;
    }
}

实现RocketMQLocalTransactionListener接口,使用@RocketMQTransactionListener注解用于接收本地事务的监听,注解的txProducerGroup属性表示事务组名称,和前面定义的OrderTransactionGroup保持一致。

RocketMQLocalTransactionListener两个实现方法:

  • executeLocalTransaction:执行本地事务,在第一步中消息发送成功后回调执行,一旦事务提交成功,下游应用的Consumer能收到该消息,在这里demo的本地事务就是保持订单信息入库
  • checkLocalTransaction:检查本地事务执行状态,如果executeLocalTransaction方法中返回的状态是未知或者未返回状态,默认会在预处理发送的1分钟后由Broker通知Producer检查本地事务,在Producer中回调本地事务监听器中的checkLocalTransaction方法。检查本地事务时,可以根据事务ID查询本地事务状态,返回具体事务状态给Broker

消费消息

与普通消息消费一样

事务消息原理:

发送预处理消息成功后,开始执行本地事务。

如果本地事务执行成功,发送提交请求提交事务消息,消息会投递给Consumer

如果本地事务执行失败,发送回滚事务消息,消息不会投递给Consumer

如果本地事务状态未知,Broker未收到二次确认的消息。Broker发送请求给Producer进行消息回查,确认提交或回滚,如果消息状态一直未被确认,则需要人工介入处理。

好了 这就是RocketMQ的事务处理机制了,支持事务消息是RocketMQ的一大特点,我们要掌握它的流程,然后在适合的场合运用事务消息,保证事务。