分布式事务 ( 二 ) seata 分布式事务框架

3. 分布式事务框架-seata

seata:Simple Extensible Autonomous Transaction Architecture

中文官方:http://seata.io/zh-cn/

中文wiki:https://github.com/seata/seata

2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And Rollback),和社区一起共建开源分布式事务解决方案。Fescar 的愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题。

Fescar 开源后,蚂蚁金服加入 Fescar 社区参与共建,并在 Fescar 0.4.0 版本中贡献了 TCC 模式。

为了打造更中立、更开放、生态更加丰富的分布式事务开源社区,经过社区核心成员的投票,大家决定对 Fescar 进行品牌升级,并更名为 Seata,意为:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案。

Seata 融合了阿里巴巴和蚂蚁金服在分布式事务技术上的积累,并沉淀了新零售、云计算和新金融等场景下丰富的实践经验,但要实现适用于所有的分布式事务场景的愿景,仍有很长的路要走。因此,我们决定建立一个完全中立的分布式事务组织,希望更多的企业、开发者能够加入我们,一起打造 Seata。

历史:

Ant Financial

XTS:Extended Transaction Service,可扩展事务服务。蚂蚁金服中间件团队自2007年以来开发了分布式事务中间件,广泛应用于Ant Financial,解决了跨数据库和服务的数据一致性问题。

DTX:Distributed Transaction Extended。自2013年以来,XTS已在Ant Financial Cloud上发布,名称为DTX。

阿里巴巴

TXC:Taobao Transaction Constructor。阿里巴巴中间件团队自2014年起启动该项目,以解决因应用程序架构从单片机改为微服务而导致的分布式事务问题。

GTS:Global Transaction Service。 TXC作为Aliyun中间件产品,新名称GTS自2016年起发布。

Fescar:我们从2019年开始基于TXC / GTS开源开源项目Fescar,以便在未来与社区密切合作。

Seata社区

Seata:简单的可扩展自治交易架构。 Ant Financial加入Fescar,使其成为一个更加中立和开放的分布式服务社区,并将Fescar更名为Seata。

3.1. 结构

Seata有3个基本组件:

  • Transaction Coordinator(TC):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
  • Transaction Manager™:事务管理器,控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
  • Resource Manager(RM):资源管理器,控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

Seata管理分布式事务的典型生命周期:

  • TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
  • XID 在微服务调用链路的上下文中传播。
  • RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。
  • TM 向 TC 发起针对 XID 的全局提交或回滚决议。
  • TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

分布式事务 ( 二 ) seata 分布式事务框架_第1张图片

至此,seata的协议机制总体上看与 XA 是一致的。但是是有差别的:

分布式事务 ( 二 ) seata 分布式事务框架_第2张图片

XA 方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身(通过提供支持 XA 的驱动程序来供应用使用)。

而 Fescar 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的,不依赖于数据库本身对协议的支持,当然也不需要数据库支持 XA 协议。这点对于微服务化的架构来说是非常重要的:应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。

这个设计,剥离了分布式事务方案对数据库在 协议支持 上的要求。

3.2.实例

springCloud整合seata:https://github.com/seata/seata-samples/tree/master/springcloud-jpa-seata

继续之前的细分商品保存, 现在同时要保存库存信息及优惠套餐信息

分布式事务 ( 二 ) seata 分布式事务框架_第3张图片

3.3.项目准备

3.3.1.vue查询页面

请求菜单 : 发布商品

请求url : /goods/goodsskuinfo-step-all

路径 : \src\views\modules\goods\goodsskuinfo-step-all.vue

goodsskuinfo-step-all 完整 页面



3.3.2.细分商品模态窗vue页面

路径 : \src\views\modules\common\goods-sku-many-dialog.vue




3.3.3.查询细分商品

请求url : /goods/goodsskuinfo/getDataListNotIds

模块 : five-mall-goods

Controller

路径 : com.yuan.goods.controller.GoodsSkuInfoController

    @RequestMapping("/getDataListNotIds")
    public R getDataListNotIds(@RequestParam Map<String, Object> params){

        System.out.println("params.get(\"ids\") = " + params.get("ids"));
        PageUtils page = goodsSkuInfoService.queryPageNotIds(params);

        return R.ok().put("page", page);
    } 

Service

路径 : com.yuan.goods.service.GoodsSkuInfoService

   PageUtils queryPageNotIds(Map<String, Object> params);

ServiceImpl

路径 : com.yuan.goods.service.impl.GoodsSkuInfoServiceImpl

    @Override
    public PageUtils queryPageNotIds(Map<String, Object> params) {
        String[] ids = params.get("ids").toString().split(",");
        IPage<GoodsSkuInfoEntity> page = this.page(
                new Query<GoodsSkuInfoEntity>().getPage(params),
                new LambdaQueryWrapper<GoodsSkuInfoEntity>()
                        .like(GoodsSkuInfoEntity::getSkuTitle, params.get("key"))
                        .notIn(GoodsSkuInfoEntity::getSkuId,ids )
        );

        return new PageUtils(page);
    }

3.3.4.查询仓库信息

请求url : /stock/repositoryinfo/listAll

模块 : five-mall-stock

Controller

路径 : com.yuan.stock.controller.RepositoryInfoController

    @RequestMapping("/listAll")
    public R listAll(){
        List<RepositoryInfoEntity> list = repositoryInfoService.list();
        return R.ok().put("list", list);
    }

3.3.5.接值vo 类

模块 : five-mall-goods

路径 : com.yuan.goods.vo.SkuManyVo

package com.yuan.goods.vo;

import lombok.Data;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Data
public class SkuManyVo {

    // sku
    private Integer skuId;
    private Integer goodsId;
    private Integer supplierId;
    private String skuTitle;
    private Double skuPrice;
    private String skuInfo;
    private Date modifiedTime;
    private Double skuNowPrice;
    private String goodsName;
    private String supplierName;

    // attribute
    private List<Integer> attrTypeIdList = new ArrayList<>();
    private List<String> attrTypeNameList = new ArrayList<>();
    private List<String> attrValueList  = new ArrayList<>();
    private List<String> attrValueUnitList  = new ArrayList<>();
    private List<String> attrValueInfoList  = new ArrayList<>();

    // stock
    private Date createDate;
    private List<Integer> repositoryIdList = new ArrayList<>();
    private List<String> repositoryNameList = new ArrayList<>();
    private List<Integer> stockCountList = new ArrayList<>();
    private List<Integer> stockWarnMaxList = new ArrayList<>();
    private List<Integer> stockWarnMinList = new ArrayList<>();


    // package
    private Integer packageId;
    private String packageInfo;
    private Double packageTotal;
    private Double packageDiscount;
    private String packageTitle;
    private String packageCode;
    private Integer packageCount;
    private Date packageInTime;
    private Date packageBeginDate;
    private String packageState;

    // packageDetail
    private List<Integer> skuIdList = new ArrayList<>();
    private List<Integer> packageDetailCountList = new ArrayList<>();
    private List<Double> packageDetailPriceList = new ArrayList<>();
    private List<String> skuTitleList = new ArrayList<>();
    private List<Double> skuNowPriceList = new ArrayList<>();
    private List<String> masterImagePathList = new ArrayList<>();

}

3.3.6.service保存调整

模块 : five-mall-goods

路径 : com.yuan.goods.service.impl.GoodsSkuInfoServiceImpl

@Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveMany(SkuManyVo skuManyVo) {
        GoodsSkuInfoEntity goodsSkuInfo = null;
        boolean save = false;

            // 保存 sku
            goodsSkuInfo = new GoodsSkuInfoEntity();

            goodsSkuInfo.setGoodsId(skuManyVo.getGoodsId());
            goodsSkuInfo.setSupplierId(skuManyVo.getSupplierId());
            goodsSkuInfo.setSkuTitle(skuManyVo.getSkuTitle());
            goodsSkuInfo.setSkuPrice(skuManyVo.getSkuPrice());
            goodsSkuInfo.setSkuInfo(skuManyVo.getSkuInfo());
            goodsSkuInfo.setModifiedTime(new Date());
            goodsSkuInfo.setSkuNowPrice(skuManyVo.getSkuNowPrice());
            goodsSkuInfo.setGoodsName(skuManyVo.getGoodsName());
            goodsSkuInfo.setSupplierName(skuManyVo.getSupplierName());
            goodsSkuInfo.setSaleCount(0);
            System.out.println("goodsSkuInfo = " + goodsSkuInfo);
            save = this.save(goodsSkuInfo);

        if (save) {

                // 保存 attributeValue
                List<GoodsAttrValueEntity> goodsAttrValueEntityList = new ArrayList<>();
                if (skuManyVo.getAttrTypeIdList() != null) {
                    for (int i = 0; i < skuManyVo.getAttrTypeIdList().size(); i++) {
                        if(skuManyVo.getAttrValueList().get(i)!=null && !"".equals(skuManyVo.getAttrValueList().get(i))){
                            GoodsAttrValueEntity goodsAttrValueEntity = new GoodsAttrValueEntity();
                            goodsAttrValueEntity.setAttrTypeId(skuManyVo.getAttrTypeIdList().get(i));
                            goodsAttrValueEntity.setSkuId(goodsSkuInfo.getSkuId());
                            goodsAttrValueEntity.setAttrValue(skuManyVo.getAttrValueList().get(i));
                            goodsAttrValueEntity.setAttrValueUnit(skuManyVo.getAttrValueUnitList().get(i));
                            goodsAttrValueEntity.setAttrValueInfo(skuManyVo.getAttrValueInfoList().get(i));
                            goodsAttrValueEntity.setAttrTypeName(skuManyVo.getAttrTypeNameList().get(i));
                            goodsAttrValueEntity.setSkuTitle(goodsSkuInfo.getSkuTitle());

                            System.out.println("goodsAttrValueEntity = " + goodsAttrValueEntity);
                            goodsAttrValueEntityList.add(goodsAttrValueEntity);
                        }
                    }
                }
                goodsAttrValueService.saveBatch(goodsAttrValueEntityList);
         

        
                // 保存 stock
                List<GoodsStockInfoVo> goodsStockInfoVoList = new ArrayList();
                if (skuManyVo.getRepositoryIdList() != null) {
                    for (int i = 0; i < skuManyVo.getRepositoryIdList().size(); i++) {


                        GoodsStockInfoVo goodsStockInfoVo = new GoodsStockInfoVo();

                        goodsStockInfoVo.setRepositoryId(skuManyVo.getRepositoryIdList().get(i));
                        goodsStockInfoVo.setSkuId(goodsSkuInfo.getSkuId());
                        goodsStockInfoVo.setStockCount(skuManyVo.getStockCountList().get(i));
                        goodsStockInfoVo.setStockWarnMax(skuManyVo.getStockWarnMaxList().get(i));
                        goodsStockInfoVo.setStockWarnMin(skuManyVo.getStockWarnMinList().get(i));
                        goodsStockInfoVo.setStockWarnState("1");
                        goodsStockInfoVo.setStockInPrice(goodsSkuInfo.getSkuNowPrice());
                        goodsStockInfoVo.setStockSn("");
                        goodsStockInfoVo.setCreateDate(skuManyVo.getCreateDate());
                        goodsStockInfoVo.setStockInTime(new Date());
                        goodsStockInfoVo.setRepositoryName(skuManyVo.getRepositoryNameList().get(i));
                        goodsStockInfoVo.setSkuTitle(goodsSkuInfo.getSkuTitle());

                        System.out.println("goodsStockInfoVo = " + goodsStockInfoVo);
                        goodsStockInfoVoList.add(goodsStockInfoVo);
                    }
                    String stockData = JSONObject.toJSONString(goodsStockInfoVoList);
                    stockFeignService.saveBatchForSku(stockData);
                }
         


           
                // 保存 package
                GoodsPackageVo goodsPackageVo = new GoodsPackageVo();

                goodsPackageVo.setSkuId(goodsSkuInfo.getSkuId());
                goodsPackageVo.setPackageInfo(skuManyVo.getPackageInfo());
                goodsPackageVo.setPackageTotal(skuManyVo.getPackageTotal());
                goodsPackageVo.setPackageDiscount(skuManyVo.getPackageDiscount());
                goodsPackageVo.setPackageTitle(skuManyVo.getPackageTitle());
                goodsPackageVo.setPackageCode(skuManyVo.getPackageCode());
                goodsPackageVo.setPackageCount(skuManyVo.getPackageCount());
                goodsPackageVo.setPackageInTime(new Date());
                goodsPackageVo.setPackageBeginDate(skuManyVo.getPackageBeginDate());
                goodsPackageVo.setPackageState("1");
                goodsPackageVo.setSkuTitle(goodsSkuInfo.getSkuTitle());
                goodsPackageVo.setSkuIdList(skuManyVo.getSkuIdList());
                goodsPackageVo.setPackageDetailCountList(skuManyVo.getPackageDetailCountList());
                goodsPackageVo.setPackageDetailPriceList(skuManyVo.getPackageDetailPriceList());
                goodsPackageVo.setMasterImagePathList(skuManyVo.getMasterImagePathList());
                goodsPackageVo.setSkuNowPriceList(skuManyVo.getSkuNowPriceList());
                goodsPackageVo.setSkuTitleList(skuManyVo.getSkuTitleList());

                System.out.println("goodsPackageVo = " + goodsPackageVo);

                R packageR = couponFeignService.saveManyForSku(goodsPackageVo);
                System.out.println("packageR = " + packageR);
         

        }

        return save;
    }

3.3.7.通过 openFeign 在Stock模块保存库存信息

路径 : com.yuan.goods.feign.StockFeignService

    /**
     * 批量保存库存
     */
    @RequestMapping("/stock/goodsstockinfo/saveBatchForSku")
    public R saveBatchForSku(@RequestBody String datas);

模块 : five-mall-stock

controller

路径 : com.yuan.stock.controller.GoodsStockInfoController

       @RequestMapping("/saveBatchForSku")
    public R saveBatchForSku(@RequestBody String datas){
        System.out.println("datas = " + datas);
        List<GoodsStockInfoEntity> goodsStockInfoEntities = JSONObject.parseObject(datas, new TypeReference<List<GoodsStockInfoEntity>>() {
        });
        System.out.println("goodsStockInfoEntities = " + goodsStockInfoEntities);
        boolean b = goodsStockInfoService.saveBatchForSku(goodsStockInfoEntities);
        if (b) {
            return R.ok();
        }else {
            return R.error();
        }
    }

service

路径 : com.yuan.stock.service.GoodsStockInfoService

 boolean saveBatchForSku(List<GoodsStockInfoEntity> goodsStockInfoEntities);

serviceImpl

**路径 : **com.yuan.stock.service.impl.GoodsStockInfoServiceImpl

    @Override
    public boolean saveBatchForSku(List<GoodsStockInfoEntity> goodsStockInfoEntities) {
        return this.saveBatch(goodsStockInfoEntities);
    }

3.3.8.通过 openFeign 在Coupon模块保存优惠套餐信息

路径 : com.yuan.goods.feign.CouponFeignService

package com.yuan.goods.feign;

import com.yuan.common.utils.R;
import com.yuan.goods.vo.GoodsPackageVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient("five-mall-coupon")
public interface CouponFeignService {

    @RequestMapping("/coupon/goodspackage/saveManyForSku")
    public R saveManyForSku(@RequestBody GoodsPackageVo goodsPackageVo);
}

模块 : five-mall-coupon

controller

路径 : com.yuan.coupon.controller.GoodsPackageController

    @RequestMapping("/saveManyForSku")
    public R saveManyForSku(@RequestBody GoodsPackageVo goodsPackageVo){
        boolean r = goodsPackageService.saveManyForSku(goodsPackageVo);
        if (r) {
            return R.ok();
        }else{
            return R.error();
        }

    }

vo传值类

路径 : com.yuan.coupon.vo.GoodsPackageVo

package com.yuan.coupon.vo;

import lombok.Data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 销售促销套餐 fmp_goods_package
 * 
 * @author yuan
 * @email [email protected]
 * @date 2022-04-06 16:26:24
 */
@Data

public class GoodsPackageVo implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 套餐主键
	 */

	private Integer packageId;
	/**
	 * 细分商品 : goods_sku_info 外键
	 */
	private Integer skuId;
	/**
	 * 套餐描述
	 */
	private String packageInfo;
	/**
	 * 套餐总价
	 */
	private Double packageTotal;
	/**
	 * 套餐优惠
	 */
	private Double packageDiscount;
	/**
	 * 套餐标题
	 */
	private String packageTitle;
	/**
	 * 套餐代号
	 */
	private String packageCode;
	/**
	 * 套餐总数量
	 */
	private Integer packageCount;
	/**
	 * 录入时间
	 */
	private Date packageInTime;
	/**
	 * 启用时间
	 */
	private Date packageBeginDate;
	/**
	 * 套餐状态 : 0 下线 1 使用
	 */
	private String packageState;
	/**
	 * 商品标题
	 */
	private String skuTitle;

	// packageDetail
	private List<Integer> skuIdList = new ArrayList<>();
	private List<Integer> packageDetailCountList = new ArrayList<>();
	private List<Double> packageDetailPriceList = new ArrayList<>();
	private List<String> skuTitleList = new ArrayList<>();
	private List<Double> skuNowPriceList = new ArrayList<>();
	private List<String> masterImagePathList = new ArrayList<>();

}

service

路径 : com.yuan.coupon.service.GoodsPackageService

       boolean saveManyForSku(GoodsPackageVo goodsPackageVo);

serviceImpl

**路径 *com.yuan.coupon.service.impl.GoodsPackageServiceImpl

@Override
    public boolean saveManyForSku(GoodsPackageVo goodsPackageVo) {
        GoodsPackageEntity goodsPackageEntity = new GoodsPackageEntity();
        BeanUtils.copyProperties(goodsPackageVo, goodsPackageEntity);
        System.out.println("goodsPackageEntity = " + goodsPackageEntity);
        boolean save = this.save(goodsPackageEntity);
        if (save) {
            List<GoodsPackageDetailEntity> goodsPackageDetailEntitieList = new ArrayList<>();
            for (int i = 0; i < goodsPackageVo.getSkuIdList().size(); i++) {
                GoodsPackageDetailEntity goodsPackageDetailEntity = new GoodsPackageDetailEntity();

                goodsPackageDetailEntity.setSkuId(goodsPackageVo.getSkuIdList().get(i));
                goodsPackageDetailEntity.setPackageId(goodsPackageEntity.getPackageId());
                goodsPackageDetailEntity.setPackageDetailCount(goodsPackageVo.getPackageDetailCountList().get(i));
                goodsPackageDetailEntity.setPackageDetailPrice(goodsPackageVo.getPackageDetailPriceList().get(i));
                goodsPackageDetailEntity.setSkuTitle(goodsPackageVo.getSkuTitleList().get(i));
                goodsPackageDetailEntity.setPackageCode(goodsPackageEntity.getPackageCode());
                goodsPackageDetailEntity.setMasterImagePath(goodsPackageVo.getMasterImagePathList().get(i));
                goodsPackageDetailEntity.setSkuNowPrice(goodsPackageVo.getSkuNowPriceList().get(i));
                System.out.println("goodsPackageDetailEntity = " + goodsPackageDetailEntity);
                goodsPackageDetailEntitieList.add(goodsPackageDetailEntity);
            }

            goodsPackageDetailService.saveBatch(goodsPackageDetailEntitieList);
        }
        return save;
    }

3.4.启动seata-server

下载地址:https://github.com/seata/seata/releases

3.4.1.启动 seate

路径 : \seata-server-1.3.0\seata\bin

seata-server.bat –m file

-m 事务日志存储方式,支持file,db,redis等 默认file

Server端存储模式(store.mode)现有file、db、redis三种(后续将引入raft,mongodb),file模式无需改动,直接启动即可,下面专门讲下db和redis启动步骤。
注: file模式为单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高;

db模式为高可用模式,全局事务会话信息通过db共享,相应性能差些;

redis模式Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置合适当前场景的redis持久化配置.

可以看到 Server started 启动成功

 i.s.core.rpc.netty.NettyServerBootstrap  : Server started, listen port: 8091

3.4.2.seata 配置中心

路径 : \seata-server-1.3.0\seata\conf\ file.conf

修改数据库连接

file.conf 30行

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.cj.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata_server"
    user = "root"
    password = "root"
    minConn = 5
    maxConn = 30
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

3.4.3.恢复配置数据库

恢复 seata_server 数据库

/*
各个表对应功能:
全局事务---global_table
分支事务---branch_table
全局锁-----lock_table

*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for branch_table
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `lock_key` varchar(128) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of branch_table
-- ----------------------------

-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of global_table
-- ----------------------------

-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96) DEFAULT NULL,
  `transaction_id` mediumtext,
  `branch_id` mediumtext,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of lock_table
-- ----------------------------

3.5.每个模块加入seata

3.5.1.操作日志表

每一个分库 增加 操作日志表 undo_log 用于回滚

CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

3.5.2.seata依赖

在common 模块里 增加对seata 支持

        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-seataartifactId>
        dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                <version>${spring-cloud-alibaba.version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>
     <spring-cloud-alibaba.version>2.2.2.RELEASEspring-cloud-alibaba.version>

3.5.3.增加 配置

将当前服务 注册到 seata上的配置

spring.cloud.alibaba.seata.tx-service-group=default
seata.application-id=${spring.application.name}
seata.service.vgroupMapping.default=default

3.5.4.注册到注册中心

路径 : \seata-server-1.3.0\seata\conf\registry.conf

 #注册
 nacos {
    application = "serverAddr"
    serverAddr = "127.0.0.1:8848"
    group = "DEFAULT_GROUP"
    namespace = "public"
    cluster = "default"
    username = ""
    password = ""
  }
  
  
  #...
  #配置
    nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = "public"
    group = "default"
    username = ""
    password = ""
  }

设置 注册平台 nacos , 并复制registry.conf 到每一个模块 resources 文件夹下

这样每一个服务 就能找到 seata 服务

3.5.5.更换数据源

在每个模块 config 包下 seata 的datasource

import com.zaxxer.hikari.HikariDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;

/**
 * @Description TODO
 * @Version 1.0
 **/
@Configuration
public class MySeataConfig {

    @Autowired
    DataSourceProperties dataSourceProperties;


    @Bean
    public DataSource dataSource(DataSourceProperties dataSourceProperties) {

        HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        if (StringUtils.hasText(dataSourceProperties.getName())) {
            dataSource.setPoolName(dataSourceProperties.getName());
        }

        return new DataSourceProxy(dataSource);
    }


}

同时修改 启动类注解, 取消数据源自动配置

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

3.6.注解

用注解保证分布式注解

3.6.1.TM 对应方法增加 @GlobalTransactional

@GlobalTransactional

模块 : five-mall-goods

**路径 : **com.yuan.goods.service.impl.GoodsSkuInfoServiceImpl

    @GlobalTransactional
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveMany(SkuManyVo skuManyVo) {
        ...
    }

3.6.2.RM对应方法增加

@Transactional(rollbackFor = Exception.class)

模块 : five-mall-goods

**路径 : **com.yuan.goods.service.impl.GoodsSkuInfoServiceImpl

    @GlobalTransactional
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveMany(SkuManyVo skuManyVo) {
        ...
    }

模块 : five-mall-coupon

**路径 *com.yuan.coupon.service.impl.GoodsPackageServiceImpl

    @Transactional(rollbackFor = Exception.class)
	@Override
    public boolean saveManyForSku(GoodsPackageVo goodsPackageVo) {
       ...
    }

模块 : five-mall-stock

**路径 : **com.yuan.stock.service.impl.GoodsStockInfoServiceImpl

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatchForSku(List<GoodsStockInfoEntity> goodsStockInfoEntities) {
        //System.out.println("stock 出异常:" + 1/0);
        return this.saveBatch(goodsStockInfoEntities);
    }

你可能感兴趣的:(微服务,SQL与数据库,分布式,微服务,seata)