Spring Cloud(二十一)Seata 分布式事务

Table of Contents

1.Seata 介绍

2.Seata-Server 安装

1.下载

2.修改file.conf

3.执行sql

4.修改registry.conf

5.启动Nacos、Seata

3.订单、库存、账户分布式搭建

1.场景

2.分布式架构图

3.业务数据库创建

4.对应库分别创建回滚日志表

5.业务微服务创建

4.Seata 事务模式

1.Seata AT模式

2.Seata TCC模式


1.Seata 介绍

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

https://github.com/seata/seata/tags

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

 

术语:

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

 

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

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

 

 

2.Seata-Server 安装

1.下载

链接:https://pan.baidu.com/s/1xBTHgXk3jzJ38qVIMaGDVw 
提取码:snnm

 

2.修改file.conf

 

自定义事务组名称 + 事务日志存储模式为db + 数据库连接信息

3.执行sql

https://github.com/seata/seata/blob/1.0.0/script/server/db/mysql.sql

注意:Seata 1.0.0版本下载的文件里面 /conf目录下面不会有db_store.sql文件

-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = INNODB
  DEFAULT CHARSET = utf8;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME,
    `gmt_modified`      DATETIME,
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = INNODB
  DEFAULT CHARSET = utf8;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(96),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_branch_id` (`branch_id`)
) ENGINE = INNODB
  DEFAULT CHARSET = utf8;

 

4.修改registry.conf

5.启动Nacos、Seata

启动参数设置

启动文件:seata-server.bat

用文本编辑器打开文件,找到文件中这一行:

%JAVACMD% %JAVA_OPTS% -server -Xmx2048m -Xms2048m -Xmn1024m -Xss512k -XX:Sur......

看到 Seata Server 默认使用 2G 内存,测试环境我们可以把内存调低:

%JAVACMD% %JAVA_OPTS% -server -Xmx256m -Xms256m -Xmn128m -Xss512k -XX:Sur......

 

3.订单、库存、账户分布式搭建

1.场景

  • 创建三个服务,一个订单服务,一个库存服务,一个账户服务
  • 当用户下单时,会在订单服务中创建一个订单,
  • 然后通过远程调用库存服务来扣减下单商品的库存,
  • 再通过远程调用账户服务来扣减用户账户里面的余额,
  • 最后在订单服务中修改订单状态为已完成.

该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题

 

2.分布式架构图

 

3.业务数据库创建

/**订单数据库**/
CREATE DATABASE seata_order DEFAULT CHARACTER SET utf8;

/**订单表**/
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 0 COMMENT '数量',
money DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
STATUS INT(1) DEFAULT 0 COMMENT '订单状态: 0:创建中:1:已完结'
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;


--------------------------------------------------------------


/**库存数据库**/
CREATE DATABASE seata_storage DEFAULT CHARACTER SET utf8;

/**库存表**/
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=2 DEFAULT CHARSET=utf8;

INSERT INTO seata_storage.t_storage(id,product_id,total,used,residue) 
VALUES(1,1,100,0,100);


--------------------------------------------------------------


/**账户数据库**/
CREATE DATABASE seata_account DEFAULT CHARACTER SET utf8;

/**账户表**/
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=2 DEFAULT CHARSET=utf8;

INSERT INTO seata_account.t_account(id,user_id,total,used,residue) 
VALUES(1,1,1000,0,1000);

 

4.对应库分别创建回滚日志表

https://github.com/seata/seata/blob/1.0.0/script/client/at/db/mysql.sql

-- 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`
(
    `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT COMMENT 'increment id',
    `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     NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME     NOT NULL COMMENT 'modify datetime',
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

 

5.业务微服务创建

@GlobalTransactional

代码:https://github.com/akeung/springclouddemo

pom

<dependencies>
        <!--整合 seata -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.seata/seata-spring-boot-starter -->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--整合 open feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ak.demo</groupId>
            <artifactId>api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- MySql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!-- Druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.verison}</version>
        </dependency>
        <!-- mybatis-springboot整合 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.spring.boot.verison}</version>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

 

yml

server:
  port: 8911

spring:
  application:
    name: seata-order
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.71.128:3306/seata_order?useSSL=false
    username: root
    password: 123456

feign:
  hystrix:
    enabled: true

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.ak.demo.entities

logging:
  level:
    io:
      seata: info
    com.ak.demo.dao: debug

seata:
  enabled: true #默认
  tx-service-group: ak_tx_group #与seata-server的file.conf的对应
  registry:
    type: nacos
    nacos:
      server-addr: localhost:8848
  client:
    support:
      spring:
        datasource-autoproxy: true
  service:
    disable-global-transaction: false


测试:

http://localhost:8911/order/create?userId=1&productId=1&money=10&count=10

 

4.Seata 事务模式

  • Seata AT模式
  • Seata TCC模式
  • Seata Saga 模式
  • Seata XA模式

1.Seata AT模式

机制

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  • 二阶段:

    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿

写隔离

  • 一阶段本地事务提交前,需要确保先拿到 全局锁 。
  • 拿不到 全局锁 ,不能提交本地事务。
  • 拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

上面的案例为该模式,seata的默认模式

 

2.Seata TCC模式

全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:

  • 一阶段 prepare 行为
  • 二阶段 commit 或 rollback 行为

TCC 与 Seata AT 事务一样都是两阶段事务,它与 AT 事务的主要区别为:

  • TCC 对业务代码侵入严重
    每个阶段的数据操作都要自己进行编码来实现,事务框架无法自动处理。
  • TCC 效率更高
    不必对数据加全局锁,允许多个事务同时操作数据。

举例:

通过 @LocalTCC 每个服务分别自定义自己的 TccAction,@TwoPhaseBusinessAction在prepare方法,并注明commit方法和rollback方法。以下只举例order模块,storage和account模块可参照实现  TccAction 和  TccActionImpl

@LocalTCC
public interface OrderTccAction {

    /*
    第一阶段的方法
    通过注解指定第二阶段的两个方法名
    
    BusinessActionContext 上下文对象,用来在两个阶段之间传递数据
    @BusinessActionContextParameter 注解的参数数据会被存入 BusinessActionContext
     */
    @TwoPhaseBusinessAction(name = "orderTccAction", commitMethod = "commit", rollbackMethod = "rollback")
    boolean prepareCreateOrder(BusinessActionContext businessActionContext,
                      @BusinessActionContextParameter(paramName = "orderId") Long orderId,
                      @BusinessActionContextParameter(paramName = "userId") Long userId,
                      @BusinessActionContextParameter(paramName = "productId") Long productId,
                      @BusinessActionContextParameter(paramName = "count") Integer count,
                      @BusinessActionContextParameter(paramName = "money") BigDecimal money);

    // 第二阶段 - 提交
    boolean commit(BusinessActionContext businessActionContext);

    // 第二阶段 - 回滚
    boolean rollback(BusinessActionContext businessActionContext);

}
@Component
@Slf4j
public class OrderTccActionImpl implements OrderTccAction {
    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    @Override
    public boolean prepareCreateOrder(BusinessActionContext businessActionContext, Long orderId, Long userId, Long productId, Integer count, BigDecimal money) {
        log.info("创建 order 第一阶段,预留资源 - "+businessActionContext.getXid());

        Order order = new Order(orderId, userId, productId, count, money, 0);
        orderMapper.create(order);

        //事务成功,保存一个标识,供第二阶段进行判断
        ResultHolder.setResult(getClass(), businessActionContext.getXid(), "p");
        return true;
    }

    @Transactional
    @Override
    public boolean commit(BusinessActionContext businessActionContext) {
        log.info("创建 order 第二阶段提交,修改订单状态1 - "+businessActionContext.getXid());

        // 防止幂等性,如果commit阶段重复执行则直接返回
        if (ResultHolder.getResult(getClass(), businessActionContext.getXid()) == null) {
            return true;
        }

        //Long orderId = (Long) businessActionContext.getActionContext("orderId");
        long orderId = Long.parseLong(businessActionContext.getActionContext("orderId").toString());
        orderMapper.updateStatus(orderId, 1);

        //提交成功是删除标识
        ResultHolder.removeResult(getClass(), businessActionContext.getXid());
        return true;
    }

    @Transactional
    @Override
    public boolean rollback(BusinessActionContext businessActionContext) {
        log.info("创建 order 第二阶段回滚,删除订单 - "+businessActionContext.getXid());

        //第一阶段没有完成的情况下,不必执行回滚
        //因为第一阶段有本地事务,事务失败时已经进行了回滚。
        //如果这里第一阶段成功,而其他全局事务参与者失败,这里会执行回滚
        //幂等性控制:如果重复执行回滚则直接返回
        if (ResultHolder.getResult(getClass(), businessActionContext.getXid()) == null) {
            return true;
        }

        //Long orderId = (Long) businessActionContext.getActionContext("orderId");
        long orderId = Long.parseLong(businessActionContext.getActionContext("orderId").toString());
        orderMapper.deleteById(orderId);

        //回滚结束时,删除标识
        ResultHolder.removeResult(getClass(), businessActionContext.getXid());
        return true;
    }
}
@Service
public class OrderServiceImpl implements OrderService {
    // @Autowired
    // private OrderMapper orderMapper;
    @Autowired
    EasyIdGeneratorClient easyIdGeneratorClient;
    @Autowired
    private AccountClient accountClient;
    @Autowired
    private StorageClient storageClient;

    @Autowired
    private OrderTccAction orderTccAction;

    @GlobalTransactional
    @Override
    public void create(Order order) {
        // 从全局唯一id发号器获得id
        Long orderId = easyIdGeneratorClient.nextId("order_business");
        order.setId(orderId);

        // orderMapper.create(order);

        // 这里修改成调用 TCC 第一节端方法
        orderTccAction.prepareCreateOrder(
                null,
                order.getId(),
                order.getUserId(),
                order.getProductId(),
                order.getCount(),
                order.getMoney());

        // 修改库存
        storageClient.decrease(order.getProductId(), order.getCount());

        // 修改账户余额
        accountClient.decrease(order.getUserId(), order.getMoney());

    }
}

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Cloud Alibaba Seata 是一款优秀的分布式事务解决方案,它可以帮助开发人员在分布式环境下处理复杂的事务操作。在分布式系统中,由于多个服务之间存在依赖关系,因此需要对服务之间的事务进行协调和管理,以确保数据的一致性和完整性。以下是对Spring Cloud Alibaba Seata处理分布式事务的需求分析: 1. 事务管理:在分布式系统中,需要对多个服务之间的事务进行管理和协调,以确保数据的一致性和完整性。Spring Cloud Alibaba Seata提供了全局事务管理功能,可以跨多个服务进行事务管理。 2. 分布式事务的隔离性:在分布式系统中,需要确保不同服务之间的事务操作是独立的,互相之间没有影响。Spring Cloud Alibaba Seata提供了分布式事务的隔离性功能,可以确保不同服务之间的事务操作是独立的。 3. 并发控制:在分布式系统中,由于多个服务之间存在依赖关系,因此可能会出现并发冲突的情况。Spring Cloud Alibaba Seata提供了并发控制功能,可以确保多个服务之间的并发操作不会冲突。 4. 事务回滚:在分布式系统中,如果某个服务的事务操作失败,需要对整个事务进行回滚。Spring Cloud Alibaba Seata提供了事务回滚功能,可以确保在分布式环境下的事务回滚操作是可靠的。 5. 可靠性:在分布式系统中,需要确保事务操作是可靠的,不会出现数据丢失或者数据不一致的情况。Spring Cloud Alibaba Seata提供了高可靠性的事务管理功能,可以确保分布式事务操作的可靠性和安全性。 综上所述,Spring Cloud Alibaba Seata是一款非常强大的分布式事务解决方案,可以帮助开发人员在分布式环境下处理复杂的事务操作,确保数据的一致性和完整性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值