RocketMQ 实现分布式事务--可靠消息最终一致性

Table of Contents

1.RocketMQ 安装

2.Rocketmq 中添加 Topic

3.可靠性消息原理

4.数据库设计

5.分布式业务搭建

1.order服务

2.account服务

3.源码


1.RocketMQ 安装

安装

 

2.Rocketmq 中添加 Topic

order-topic

 

3.可靠性消息原理

RocketMQ 提供了可靠性消息,也叫事务消息。发送“可靠消息”,只需要以下三步:

  1. 发送半消息(半消息不会发送给消费者)
  2. 执行本地事务
  3. 提交消息


完成事务消息发送后,消费者就可以以正常的方式来消费数据。RocketMQ 的自动重发机制在绝大多数情况下,都可以保证消息被正确消费。假如消息最终消费失败了,还可以由人工处理进行托底。

两种错误情况:

  1. 事务执行失败时回滚消息
  2. 服务器无法得知消息状态时,需要主动回查消息状态

 

4.数据库设计

CREATE DATABASE /*!32312 IF NOT EXISTS*/`rocketmq_account` /*!40100 DEFAULT CHARACTER SET latin1 */;

USE `rocketmq_account`;

/*Table structure for table `account` */

DROP TABLE IF EXISTS `account`;

CREATE TABLE `account` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
  `total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
  `used` decimal(10,0) DEFAULT NULL COMMENT '已用余额',
  `residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度',
  `frozen` decimal(10,0) DEFAULT '0' COMMENT 'TCC事务锁定的金额',
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

/*Data for the table `account` */

insert  into `account`(`id`,`user_id`,`total`,`used`,`residue`,`frozen`) values 
(1,1,1000,200,800,0);

 

CREATE DATABASE /*!32312 IF NOT EXISTS*/`rocketmq_order` /*!40100 DEFAULT CHARACTER SET latin1 */;

USE `rocketmq_order`;

/*Table structure for table `order` */

DROP TABLE IF EXISTS `order`;

CREATE TABLE `order` (
  `id` bigint(11) NOT NULL,
  `user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
  `product_id` bigint(11) DEFAULT NULL COMMENT '产品id',
  `count` int(11) DEFAULT NULL COMMENT '数量',
  `money` decimal(11,0) DEFAULT NULL COMMENT '金额',
  `status` int(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


/*Table structure for table `tx_table` */

DROP TABLE IF EXISTS `tx_table`;

CREATE TABLE `tx_table` (
  `xid` char(32) NOT NULL COMMENT '事务id',
  `status` int(11) DEFAULT NULL COMMENT '0-提交,1-回滚,2-未知',
  `created_at` bigint(20) unsigned NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

 

5.分布式业务搭建

通过order服务下订单,发起事务,account服务扣款

1.order服务

通过 create方法 发送事务消息(半消息),会触发 TxListener 事务监听器 执行本地事务,它执行时会回调doCreate() 方法完成订单的保存,本地事务执行后保存事务信息(事务id、事务状态)到数据库,以便之后进行事务回查。

事务监听器有两个方法:

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

 

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderMapper orderMapper;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Override
    public void create(Order order) {
        // 产生事务ID
        String xid = UUID.randomUUID().toString().replace("-", "");

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

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

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

    /**
     *本地事务,执行订单保存
     *这个方法在事务监听器中调用
     * @param order
     */
    @Override
    @Transactional
    public void doCreate(Order order) {
        log.info("执行本地事务,保存订单");
        Long nextId = IdGenerator.getInstance().nextId();
        order.setId(nextId);
        orderMapper.create(order);

        //todo 1.模拟事务异常
//        if (Math.random() < 0.5) {
//            throw new RuntimeException("模拟异常");
//        }

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


    @Override
    public Order get(Long id) {
        return orderMapper.get(id);
    }
}

 

@Slf4j
@Component
@RocketMQTransactionListener
public class TxListener implements RocketMQLocalTransactionListener {

    @Autowired
    private OrderService orderService;
    @Resource
    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;
        Order order = (Order) o;

        try {
            orderService.doCreate(order);
            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;
        }
    }
}

 

2.account服务

TxConsumer 接收事务消息,调用账户业务方法 decrease,如果处理失败,Rocketmq会进行重试,直到处理成功。

@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 message) {
        TxAccountMessage txAccountMessage = JsonUtil.from(message, new TypeReference<TxAccountMessage>() {});
        log.info("收到消息: "+txAccountMessage);
        accountService.decrease(txAccountMessage.getUserId(),txAccountMessage.getMoney());
    }
}

 

3.源码

https://github.com/akeung/rocketmq-dtx-demo

 

 

 

 

 

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RocketMQ 是一个开源的分布式消息队列系统,它支持可靠消息传输和最终一致性RocketMQ可靠消息传输是通过消息的持久化和复制来实现的。当生产者发送消息时,消息会被持久化到本地磁盘,并且会根据配置的复制因子将消息复制到其他的 Broker 节点上。这样即使某个 Broker 节点出现故障,消息仍然可以从其他节点获取。 RocketMQ 通过使用主题(Topic)和分区(Partition)的概念来实现消息的负载均衡和扩展性。一个主题可以由多个分区组成,每个分区可以在不同的 Broker 节点上存储。这样可以保证同一个主题的消息在多个节点上进行分布式存储,提高了系统的可靠性和可扩展性。 最终一致性是指当消息被消费者消费后,消息队列系统会保证所有消费者看到的消息顺序是一致的。RocketMQ 使用了消息消费者组(Consumer Group)的概念,每个消费者组内的消费者共同消费一个主题的消息,系统会确保每个消费者按照相同的顺序消费消息。 此外,RocketMQ 还提供了事务消息和顺序消息等特性来满足不同业务场景下的需求,进一步提高了消息传输的可靠性和一致性。 总结来说,RocketMQ 通过持久化、复制、负载均衡、分区和消费者组等机制来实现可靠消息传输和最终一致性。这使得 RocketMQ 在分布式系统中被广泛应用于解决可靠消息传输的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值