基于RocketMQ 事务消息

事务消息

RocketMQ 提供了可靠性消息,也叫事务消息。

事务消息的原理

  1. 先发送半消息(半消息不会发送给消费者),保证通道连接
  2. 执行本地事务
    执行成功 :将提交消息
    1)半消息先放入第一个队列,本地事务成功以后提交消 息,从第一个队列拿出来放到自己的列队中(Topic2)。RocketMQ 发送给消费者B时的自动重发机制在绝大多数情况下,都可以保证消息被正确消费。
    2)假如消息最终消费失败了,还可以由人工处理进行托底。
    recketmq与B之间是执行18个事务级别,比如说第一次10s,没成功后间隔30s再询问, 没成功再间隔1min执行。

该模式没有全局回滚,理论上A成功,B必须成功。

在这里插入图片描述
在这里插入图片描述
执行失败:
1)回滚:事务执行失败时回滚消息
2)回查:(a与rocketmq之间)服务器无法得知消息状态时,如网络断开, 一分钟一次,一直循环
需要主动回查消息状态,每隔1min询问本地事务状态,一直询问不停歇。
会查的到的结果有三种:
LocalTransactionState.COMMIT_MESSAGE 提交
LocalTransactionState.ROLLBACK_MESSAGE 回滚
LocalTransactionState.UNKNOW 未知,之前的事务未执行结束或者之前事务状态没存储而取不到,导致不知道是成功还是失败,一般用不上,

测试一:
public class Producer {
    public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
        // 创建生产者对象
        DefaultMQProducer p = new DefaultMQProducer("prod-group1");  //prod-group1事物组组名
        // 设置 name server
        p.setNamesrvAddr("192.168.64.141:9876");
        // 启动,连接
        p.start();
        // 创建消息封装对象 Message(Topic, Tag, 消息)
        // 发送
        while (true) {
            System.out.print("输入消息:");
            String s = new Scanner(System.in).nextLine();
            // Topic 相当于是一级分类,Tag 相当于是二级分类
            Message msg = new Message("Topic1", "TagA", s.getBytes());
            msg.setDelayTimeLevel(3);
            SendResult r = p.send(msg);
            System.out.println(r);
        }
    }
}

```java
public class Consumer {
    public static void main(String[] args) throws MQClientException {
        // 创建消费者对象
        DefaultMQPushConsumer c =
                new DefaultMQPushConsumer("cons-group1");
        // 设置 name server
        c.setNamesrvAddr("192.168.64.141:9876");

        // 设置从哪里订阅消息
        // 标签: *--所有标签, TagA--只收TagA, TagA || TagB || TagC--受多种标签
        c.subscribe("Topic1", "TagA || TagB || TagC");
        // 设置消息监听器
        // Concurrently -- 会启动多个线程处理消息
        c.setMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt ext : msgs) {
                    String s = new String(ext.getBody());
                    System.out.println("收到:"+s);
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                //return ConsumeConcurrentlyStatus.RECONSUME_LATER;//稍后重新消费
            }
        });

        // 启动
        c.start();
    }
}
测试二:
public class Producer {
    static String[] msgs = {
"15103111039,创建",
"15103111065,创建",
				 											  	"15103111039,付款",
"15103117235,创建",
																	"15103111065,付款",
																	"15103117235,付款",
																	 								"15103111065,完成",
								"15103111039,推送",
																									"15103117235,完成",
																									"15103111039,完成"
    };

    public static void main(String[] args) throws MQClientException, MQBrokerException, RemotingException, InterruptedException {
        // 创建生产者对象
        DefaultMQProducer p = new DefaultMQProducer("prod-group2");
        // 设置 name server
        p.setNamesrvAddr("192.168.64.141:9876");
        // 启动,连接
        p.start();
        //遍历数组发送消息
        for (String s : msgs) {
            // s -- "15103111039,完成"
            Long orderId = Long.valueOf(s.split(",")[0]);
            Message msg = new Message("Topic2", s.getBytes());
            // p.send(消息, 队列选择器, 选择依据)
            SendResult r =
                p.send(msg, new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                        Long orderId = (Long) arg;
                        int index = (int) (orderId % mqs.size());
                        return mqs.get(index);
                    }
                }, orderId);

            System.out.println(r);
        }

    }

}

public class Consumer {
    public static void main(String[] args) throws MQClientException {
        // 创建消费者对象
        DefaultMQPushConsumer c =
                new DefaultMQPushConsumer("cons-group2");
        // 设置 name server
        c.setNamesrvAddr("192.168.64.141:9876");

        // 设置从哪里订阅消息
        // 标签: *--所有标签, TagA--只收TagA, TagA || TagB || TagC--受多种标签
        c.subscribe("Topic2", "*");
        // 设置消息监听器
        // Concurrently -- 会启动多个线程处理消息
        // Orderly -- 单个线程
        c.setMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                for (MessageExt ext : msgs) {
                    String s = new String(ext.getBody());
                    System.out.println("收到: "+s);
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        // 启动
        c.start();
    }
}
测试三:
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.Scanner;

public class Producer {
    public static void main(String[] args) throws MQClientException {
        //创建事务消息生产者
        TransactionMQProducer p = new TransactionMQProducer("prod-group1");
        //设置 name server
        p.setNamesrvAddr("192.168.124.160:9876");

        //设置事务消息监听器
        //1.执行本地事务(service.业务方法())
        //2.处理rocketmq的事务回查
        p.setTransactionListener(new TransactionListener() {
            @Override
            public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
                // 模拟网络不稳定,不能向服务器提交或回滚消息
                // if (Math.random() < 1) {
                 //    System.out.println("网络中断,无法向服务器提交或回滚");
                 //    return LocalTransactionState.UNKNOW;
                // }

                System.out.println("执行本地业务,参数: "+arg);
                if (Math.random()<0.5) {
                    System.out.println("本地事务执行成功");
                    return LocalTransactionState.COMMIT_MESSAGE; // 提交消息,通知服务器消息可以投递
                } else {
                    System.out.println("本地事务执行失败");
                    return LocalTransactionState.ROLLBACK_MESSAGE; // 回滚消息,撤回消息
                }
                // return LocalTransactionState.UNKNOW; // 未知,当前方法中,一般不会出现这种状态
            }

            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt msg) {
              //  System.out.println("rocketmq 服务器正在回查事务状态"); //如果第一次提交UNKNOW,则执行,
              // return LocalTransactionState.UNKNOW; //自己设置,继续每次会查都会返回未知状态
	           return null;
            }
        });

        //启动
        p.start();
        //发送事务消息,触发监听器
        while (true) {
            System.out.print("输入消息: ");
            String s = new Scanner(System.in).nextLine();
            Message msg = new Message("Topic3", s.getBytes());
            TransactionSendResult r =
                    p.sendMessageInTransaction(msg, "业务数据参数");
            System.out.println("----------------事务消息结果:" + r);
        }
    }
}
public class Consumer {
    public static void main(String[] args) throws MQClientException {
        // 创建消费者对象
        DefaultMQPushConsumer c =
                new DefaultMQPushConsumer("cons-group3");
        // 设置 name server
        c.setNamesrvAddr("192.168.64.141:9876");

        // 设置从哪里订阅消息
        // 标签: *--所有标签, TagA--只收TagA, TagA || TagB || TagC--受多种标签
        c.subscribe("Topic3", "*");
        // 设置消息监听器
        // Concurrently -- 会启动多个线程处理消息
        c.setMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt ext : msgs) {
                    String s = new String(ext.getBody());
                    System.out.println("收到:"+s);
                }
                //return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;//稍后重新消费,通常Try,catch使用
            }
        });

        // 启动
        c.start();
    }
}

在这里插入图片描述
模拟提交失败,每分钟询问一次,一个事务一条
只有一条消息,间隔60s,如图
在这里插入图片描述
有多条消息,如图
在这里插入图片描述

业务中。。。

本地事务的成败将保存到数据库中,因对实物回查
事物id,成/败
在这里插入图片描述

在这里插入图片描述

Autowired
private RocketMQTemplate t;

发送事物消息

业务和监听器一起写
 import cn.tedu.order.entity.AccountMessage;
import cn.tedu.order.entity.Order;
import cn.tedu.order.entity.TxInfo;
import cn.tedu.order.feign.AccountClient;
import cn.tedu.order.feign.EasyIdClient;
import cn.tedu.order.feign.StorageClient;
import cn.tedu.order.mapper.OrderMapper;
import cn.tedu.order.mapper.TxMapper;
import cn.tedu.order.util.JsonUtil;
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.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Random;
import java.util.UUID;

@RocketMQTransactionListener
@Service
public class OrderServiceImpl implements OrderService, RocketMQLocalTransactionListener {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private EasyIdClient easyIdClient;
    @Autowired
    private RocketMQTemplate t;
    @Autowired
    private TxMapper txMapper;

    @Override
    public void create(Order order) {
        // "xxx-xxx-xxx-xxx-xxx" 有4个减号,共36位
        // 这里把减号去掉,保留32位
        String xid = UUID.randomUUID().toString().replace("-", "");
        AccountMessage am = new AccountMessage(
                order.getUserId(), order.getMoney(), xid);
        // am ---> json
        String json = JsonUtil.to(am);
        // spring封装的通用message对象
        // payLoad会自动被转换成 byte[]
        Message<String> message = MessageBuilder.withPayload(json).build();


        // 发送事务消息
        // 如果添加Tag,格式: "orderTopic:TagA"
        // t.sendMessageInTransaction("orderTopic",message,业务数据参数);
        t.sendMessageInTransaction("orderTopic",message,order);
    }

	 //本地事务,执行订单保存
    //这个方法在事务监听器中调用
    @Transactional
    // 在监听器中,会调用这个业务方法
    public void doCreate(Order order) {
        //  远程调用发号器,获取订单id
        String s = easyIdClient.nextId("order_business");
        Long orderId = Long.valueOf(s);

        order.setId(orderId);
        orderMapper.create(order);
    }

    @Transactional
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        RocketMQLocalTransactionState state; //用来返回
        int status; //用来存储到数据库
        try {
            Order order = (Order) o;
            doCreate(order);
            state=RocketMQLocalTransactionState.COMMIT;
            status=0;
        } catch (Exception e) {
            e.printStackTrace();
            state=RocketMQLocalTransactionState.ROLLBACK;
            status=1;
        }

        // TxInfo 封装事务状态
        String json = new String((byte[]) message.getPayload());
        // {"xid":"u5y4g5t34", "userId":7, ...}
        String xid = JsonUtil.getString(json, "xid");
        TxInfo txInfo = new TxInfo(xid, status, System.currentTimeMillis());
        txMapper.insert(txInfo);
        return state;
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        String json = new String((byte[]) message.getPayload());
        String xid = JsonUtil.getString(json, "xid");
        TxInfo txInfo = txMapper.selectById(xid);
        if (txInfo == null) {
            return RocketMQLocalTransactionState.UNKNOWN;
        }
        switch (txInfo.getStatus()) {
            case 0: return RocketMQLocalTransactionState.COMMIT;
            case 1: return RocketMQLocalTransactionState.ROLLBACK;
            default: return RocketMQLocalTransactionState.UNKNOWN;
        }
    }
}
或者分开写业务和监听器

在业务方法 create() 中不直接保存订单,而是发送事务消息。

消息发出后,会触发 TxListener 执行本地事务,它执行时会回调这里的 doCreate() 方法完成订单的保存。

package cn.tedu.order.tx;

import cn.tedu.order.entity.Order;
import cn.tedu.order.feign.EasyIdGeneratorClient;
import cn.tedu.order.mapper.OrderMapper;
import cn.tedu.order.mapper.TxMapper;
import cn.tedu.order.service.OrderService;
import cn.tedu.order.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.UUID;

@Slf4j
@Primary
@Service
public class TxOrderService implements OrderService {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private TxMapper txMapper;
    @Autowired
    EasyIdGeneratorClient easyIdGeneratorClient;

    /*
    创建订单的业务方法
    这里修改为:只向 Rocketmq 发送事务消息。
     */
    @Override
    public void create(Order order) {
        // 产生事务ID
        String xid = UUID.randomUUID().toString().replace("-", "");

        //对事务相关数据进行封装,并转成 json 字符串
        TxAccountMessage sMsg = new TxAccountMessage(order.getUserId(), order.getMoney(), xid);
        String json = JsonUtil.to(sMsg);

        //json字符串封装到 Spring Message 对象
        Message<String> msg = MessageBuilder.withPayload(json).build();

        //发送事务消息
        rocketMQTemplate.sendMessageInTransaction("order-topic:account", msg, order);
        log.info("事务消息已发送");
    }

    //本地事务,执行订单保存
    //这个方法在事务监听器中调用
    @Transactional
    public void doCreate(Order order, String xid) {
        log.info("执行本地事务,保存订单");

        // 从全局唯一id发号器获得id
        Long orderId = easyIdGeneratorClient.nextId("order_business");
        order.setId(orderId);

        orderMapper.create(order);

        log.info("订单已保存! 事务日志已保存");
    }
}

TxListener 事务监听器
发送事务消息后会触发事务监听器执行。

事务监听器有两个方法:

executeLocalTransaction(): 执行本地事务
checkLocalTransaction(): 负责响应Rocketmq服务器的事务回查操作

package cn.tedu.order.tx;

import cn.tedu.order.entity.Order;
import cn.tedu.order.mapper.TxMapper;
import cn.tedu.order.util.JsonUtil;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RocketMQTransactionListener
public class TxListener implements RocketMQLocalTransactionListener {
    @Autowired
    private TxOrderService orderService;
    @Autowired
    private TxMapper txMapper;

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        log.info("事务监听 - 开始执行本地事务");

        // 监听器中得到的 message payload 是 byte[]
        String json = new String((byte[]) message.getPayload());
        String xid = JsonUtil.getString(json, "xid");

        log.info("事务监听 - "+json);
        log.info("事务监听 - xid: "+xid);

        RocketMQLocalTransactionState state;
        int status = 0;
        Order order = (Order) o;

        try {
            orderService.doCreate(order, xid);

            log.info("本地事务执行成功,提交消息");
            state = RocketMQLocalTransactionState.COMMIT;
            status = 0;
        } catch (Exception e) {
            e.printStackTrace();
            log.info("本地事务执行失败,回滚消息");
            state = RocketMQLocalTransactionState.ROLLBACK;
            status = 1;
        }

        TxInfo txInfo = new TxInfo(xid, System.currentTimeMillis(), status);
        txMapper.insert(txInfo);

        return state;
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        log.info("事务监听 - 回查事务状态");

        // 监听器中得到的 message payload 是 byte[]
        String json = new String((byte[]) message.getPayload());
        String xid = JsonUtil.getString(json, "xid");

        TxInfo txInfo = txMapper.selectById(xid);
        if (txInfo == null) {
            log.info("事务监听 - 回查事务状态 - 事务不存在:"+xid);
            return RocketMQLocalTransactionState.UNKNOWN;
        }

        log.info("事务监听 - 回查事务状态 - "+ txInfo.getStatus());

        switch (txInfo.getStatus()) {
            case 0: return RocketMQLocalTransactionState.COMMIT;
            case 1: return RocketMQLocalTransactionState.ROLLBACK;
            default: return RocketMQLocalTransactionState.UNKNOWN;
        }
    }
}

接收事务消息

rocketmq:
  name-server: 192.168.64.151:9876;192.168.64.152:9876

接收的消息转换成 pojo 对象

消息监听
package cn.tedu.account.tx;

import cn.tedu.account.service.AccountService;
import cn.tedu.account.util.JsonUtil;
import com.fasterxml.jackson.core.type.TypeReference;
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(consumerGroup = "account-consumer-group", topic = "order-topic", selectorExpression = "account")
public class TxConsumer implements RocketMQListener<String> {
    @Autowired
    private AccountService accountService;

    @Override
    public void onMessage(String msg) {
        TxAccountMessage txAccountMessage = JsonUtil.from(msg, new TypeReference<TxAccountMessage>() {});
        log.info("收到消息: "+txAccountMessage);

        accountService.decrease(txAccountMessage.getUserId(), txAccountMessage.getMoney());
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值