分布式事务(八)Spring Cloud微服务系统基于Rocketmq可靠消息最终一致性实现分布式事务...

项目源码: https://gitee.com/benwang6/rocketmq-dtx

安装搭建 Rocketmq 服务器

搭建单机 Rocketmq 服务器笔记:
RocketMQ (一) 安装

搭建双主双从同步复制 Rocketmq 服务器笔记:
RocketMQ (二) 双主双从同步复制集群方案

基于 Rocketmq 可靠消息的分布式事务方案原理

Rocketmq事务消息笔记:
RocketMQ 发送事务消息原理分析和代码实现

准备订单项目案例

新建 rocketmq-dtx 工程

新建 Empty Project:

a

工程命名为 rocketmq-dtx,存放到任意文件夹下:

a

导入订单项目,无事务版本

下载项目代码

  1. 访问 git 仓库 https://gitee.com/benwang6/seata-samples
  2. 访问项目标签
    a
  3. 下载无事务版
    a

解压到 rocketmq-dtx 目录

压缩文件中的 7 个项目目录解压缩到 rocketmq-dtx 目录:

a

导入项目

在 idea 中按两下 shift 键,搜索 add maven projects,打开 maven 工具:

a

然后选择 rocketmq-dex 工程目录下的 7 个项目的 pom.xml 导入:

a

order 添加事务状态表

Rocketmq收到事务消息后,会等待生产者提交或回滚该消息。如果无法得到生产者的提交或回滚指令,则会主动向生产者询问消息状态,称为回查。

在 order 项目中,为了让Rocketmq可以回查到事务的状态,需要记录事务的状态,所以我们添加一个事务的状态表来记录事务状态。

修改 db-init 项目中的 order.sql 文件,创建 tx_table 表:

drop database if exists `seata_order`;

CREATE DATABASE `seata_order` charset utf8;

use `seata_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 '金额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

ALTER TABLE `order` ADD COLUMN `status` int(1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结' AFTER `money` ;

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

CREATE TABLE IF NOT EXISTS segment
(
    id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '自增主键',
    VERSION       BIGINT      DEFAULT 0  NOT NULL COMMENT '版本号',
    business_type VARCHAR(63) DEFAULT '' NOT NULL COMMENT '业务类型,唯一',
    max_id        BIGINT      DEFAULT 0  NOT NULL COMMENT '当前最大id',
    step          INT         DEFAULT 0  NULL COMMENT '步长',
    increment     INT         DEFAULT 1  NOT NULL COMMENT '每次id增量',
    remainder     INT         DEFAULT 0  NOT NULL COMMENT '余数',
    created_at    BIGINT UNSIGNED        NOT NULL COMMENT '创建时间',
    updated_at    BIGINT UNSIGNED        NOT NULL COMMENT '更新时间',
    CONSTRAINT uniq_business_type UNIQUE (business_type)
) CHARSET = utf8mb4
  ENGINE INNODB COMMENT '号段表';


INSERT INTO segment
(VERSION, business_type, max_id, step, increment, remainder, created_at, updated_at)
VALUES (1, 'order_business', 1000, 1000, 1, 0, NOW(), NOW());

CREATE TABLE tx_table(
	`xid` char(32) PRIMARY KEY COMMENT '事务id',
	`status` int COMMENT '0-提交,1-回滚,2-未知',
	`created_at` BIGINT UNSIGNED NOT NULL COMMENT '创建时间'
);

运行 db-init 项目,会创建这个表:

a

order 发送事务消息,并执行本地事务

Rocketmq 中添加 Topic

使用 order-topic 来收发消息,在 Rocketmq 服务器上创建这个 Topic:

a

order-parent 中添加 rocketmq 起步依赖

修改 pom.xml 添加以下内容:

properties 中设置 rocketmq 起步依赖的版本

<rocketmq-spring-boot-starter.version>2.1.0</rocketmq-spring-boot-starter.version>

添加 rocketmq 起步依赖:

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>${rocketmq-spring-boot-starter.version}</version>
</dependency>

order 项目中添加 rocketmq 连接信息配置:

修改 order 项目的 application.yml,添加 NameServer 地址,指定生产者组名:

rocketmq:
  name-server: 192.168.64.151:9876;192.168.64.152:9876
  producer:
    group: order-group

添加 TxMapper 访问事务状态表

事务状态保存到 tx_table 表,在 TxMapper 接口和 TxMapper.xml 中添加事务状态数据的读写方法。

本地事务执行后要保存事务信息(事务id、事务状态)到数据库,以便之后进行事务回查,首先创建封装事务信息的类 TxInfo

package cn.tedu.order.tx;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TxInfo {
    private String xid;
    private Long created;
    private Integer status;
}

TxMapper 接口:

package cn.tedu.order.mapper;

import cn.tedu.order.tx.TxInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface TxMapper extends BaseMapper<TxInfo> {
    Boolean exists(String xid);
}

TxMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.tedu.order.mapper.TxMapper" >
    <resultMap id="BaseResultMap" type="cn.tedu.order.tx.TxInfo" >
        <id column="xid" property="xid" jdbcType="CHAR" />
        <result column="created_at" property="created" jdbcType="BIGINT" />
        <result column="status" property="status" jdbcType="INTEGER"/>
    </resultMap>

    <insert id="insert">
        INSERT INTO `tx_table`(`xid`,`created_at`,`status`) VALUES(#{xid},#{created},#{status});
    </insert>
    <select id="exists" resultType="boolean">
        SELECT COUNT(1) FROM tx_table WHERE xid=#{xid};
    </select>
    <select id="selectById" resultMap="BaseResultMap">
        SELECT `xid`,`created_at`,`status` FROM tx_table WHERE xid=#{xid};
    </select>
</mapper>

Json处理工具

发送事务消息时,我们把事务对象序列化成 Json 字符串再发送。这里先添加一个工具 JsonUtil 用来处理 Json:

package cn.tedu.order.util;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class JsonUtil {
    private static ObjectMapper ma
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值