SpringCloudAlibaba系列(三)Seata

369 阅读3分钟

这是我参与更文挑战的第 21 天,活动详情查看: 更文挑战

Seata

一、概述

http://seata.io/zh-cn/docs/overview/what-is-seata.html

二、环境配置

下载安装

  • 下载地址https://github.com/seata/seata/releases/tag/v1.0.0

  • 解压

  • 修改\seata\conf下的file.conf配置文件

    • service模块自定义事务名称vgroup_mapping.my_test_tx_group = "fsp_tx_group"
    • 事务日志的存储模式和数据库信息
    service {
      #transaction service group mapping
      vgroup_mapping.my_test_tx_group = "fsp_tx_group"
      #only support when registry.type=file, please don't set multiple addresses
      default.grouplist = "127.0.0.1:8091"
      #disable seata
      disableGlobalTransaction = false
    }
    store {
      ## store mode: file、db
      mode = "db"
      ## database store property
      db {
        ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
        datasource = "dbcp"
        ## mysql/oracle/h2/oceanbase etc.
        db-type = "mysql"
        driver-class-name = "com.mysql.jdbc.Driver"
        url = "jdbc:mysql://localhost:3306/seata"
        user = "root"
        password = "root"
      }
    } 
    
  • 建库建表,SQL在seata-server-0.9.0\seata\conf目录下

  • 修改registry.conf配置文件

    registry {
      # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
      type = "nacos"
      nacos {
        serverAddr = "localhost:8848"
        namespace = ""
        cluster = "default"
      }
    ...
    }
    
  • 启动Nacos和Seata

三、SEATA 的分布式交易解决方案

用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:

  • 仓储服务:对给定的商品扣除仓储数量。
  • 订单服务:根据采购需求创建订单。
  • 帐户服务:从用户帐户中扣除余额。

数据库准备

CREATE DATABASE seata_order;
USE seata_order;
CREATE TABLE t_order(
    id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    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已完结'
)ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;

CREATE DATABASE seata_storage;
USE seata_storage;
CREATE TABLE t_storage(
    id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    product_id BIGINT(11) DEFAULT NULL COMMENT '产品id',
    total INT(11) DEFAULT NULL COMMENT '总库存',
    used INT(11) DEFAULT NULL COMMENT '已用库存',
    residue INT(11) DEFAULT NULL COMMENT '剩余库存'
)ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1,1,100,0,100);

CREATE DATABASE seata_account;
USE seata_account;
CREATE TABLE t_account(
    id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
    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 '剩余可用额度'
)ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8;
INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1,1,1000,0,1000);

创建回滚日志表

SQL文件在\seata-server-0.9.0\seata\conf\db_undo_log.sql

四个数据库下都创建该表

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;

微服务准备

seata-order-service2001/seata-storage-service2002/seata-account-service2003

依赖
<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <exclusions>
            <exclusion>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-all</artifactId>
        <version>0.9.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.16</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
</dependencies>
配置文件
server:
  port: 2001
spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        # 自定义事务组名称,与配置中的对应
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://47.95.226.96:3306/seata?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: admin
feign:
  hystrix:
    enabled: false
logging:
  level:
    io:
      seata: info
mybatis:
  mapper-locations: classpath:mapper/*.xml

注意:把之前配置的file.confregistry.conf复制到resources

业务代码

image-20201229144242974

测试

order模块发送GET请求http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100,通过Feign调用Account模块和storage模块做其相应的减账户余额和减库存操作。

存在的问题

现在在Account模块中的decrease方法添加一个超时的操作

public void decrease(Long userId, BigDecimal money) {
    LOGGER.info("---------AccountServiceImpl扣减---------");
    //制造超时异常
    try {
        TimeUnit.SECONDS.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    accountDao.decrease(userId,money);
}

继续上一步的请求,发现订单创建了并且库存也减了,但是账户余额没有变化,违背了数据的一致性,使用seata分布式事务解决。

解决方法

在业务的入口处加全局事务控制的注解@GlobalTransactional

@GlobalTransactional
public void createOrder(Order order) {
    LOGGER.info("------新建订单-------");
    orderDao.createOrder(order);
    LOGGER.info("------Storage减库存-------");
    storageService.decrease(order.getProductId(), order.getCount());
    LOGGER.info("------Account扣减-------");
    accountService.decrease(order.getUserId(), order.getMoney());
    LOGGER.info("------修改订单状态-------");
    orderDao.updateOrder(order.getUserId(), 0);
    LOGGER.info("------end-------");
}

执行过程

默认的模式为AT模式。

分为两个阶段:

  • 一阶段:解析SQL,根据SQL得到两个快照,一个前快照-保存执行SQL前的数据,一个后快照-保存执行SQL后的数据。
  • 二阶段:
    • 执行成功,删除快照
    • 执行失败,会把后前快照重写会数据库,再删除快照。