分布式事务第六弹——Seata分布式事务

1,409 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情

每日英语:

The only way to achieve the impossible is to believe it is possible.

翻译:实现不可能的唯一办法就是要相信凡事皆有可能。 ——《爱丽丝梦游仙境2》

Seata分布式事务

微服务架构,服务间调用出现异常是会出现事务问题的,而且是跨服务间的事务问题(分布式事务),前五弹虽然讲解了事务以及分布式事务相关知识点,但分布式事务究竟该如何解决呢?

订单问题分析

1、查询购物车记录
2、库存递减   ->mall-goods-service服务
3、订单明细
4、增加订单   ->当前服务 本地事务(mall-order-service)
5、删除购物车记录  ->mall-cart-service

上面是我们订单的具体操作步骤,整个看起来没有任何问题,但其实存在事务问题,如果上述操作1和2都操作成功了,但是3操作发生异常,2是可根据异常实现本地事务回滚,但1处是没法实现本地事务回滚的,因为它跨应用了,操作流程已经结束。这个问题就是我们说的分布式事务问题。可以采用当前主流分布式事务解决方案Seata来解决。

Seata介绍

1605329655721.png

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,开放以来,广受欢迎,不到一年已经成为最受欢迎的分布式事务解决方案。

1605329982585.png

Seata AT模式

Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。其中AT模式最受欢迎,使用也非常简单,但它内在的原理不简单。

AT模式前提:

1:基于支持本地 ACID 事务的关系型数据库。
2:Java 应用,通过 JDBC 访问数据库。

整体机制: 2PC协议的演变

1PC:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
2PC:
    1)提交异步化,非常快速地完成。
    2)回滚通过一阶段的回滚日志进行反向补偿。

AT模式工作机制

1605333663924.png

以一个示例来说明整个 AT 分支的工作过程。

业务表:product

FieldTypeKey
idbigint(20)PRI
namevarchar(100)
sincevarchar(100)

执行修改操作:(SQL语句如下)

update product set name = 'xz2' where name = 'xz';

1PC:

a.解析SQL语句,得到类型为UPDATE,表为product,条件where name = 'xz'

b.根据解析的SQL语句进行要操作的结果查询:

select * product where name ='xz'

得到的修改前数据结果如下:

idnamesince
1xz2022

c.执行业务SQL

查询修改后的结果:

select id, name, since from product where id = 1;

查询修改后的数据结果如下:

idnamesince
1xz22022

d. 镜像备份

将修改前的结果和修改后的结果添加到数据库表undo_log中。

e. 提交前,向TC注册分支申请product表中id=1的数据的全局锁。

f. 本地事务提交。

8. 本地事务提交结果上报给TC。

2PC-提交:

a. 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。

b. 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

2PC-回滚:

a. 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。

b. 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。

c. 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。

d. 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:

update product set name = 'xz' where id = 1;

e. 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

undo_log表,在每个需要执行分布式事务操作的数据库中添加

-- 注意此处0.7.0+ 增加字段 context
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,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

订单分布式事务

首先我们需要安装seata-server,我们采用docker方式安装:

docker run --name seata-server -p 8091:8091 -d  seataio/seata-server:1.0.0

分布式事务配置过程按照如下操作:

1:需要执行分布式事务的工程中引入依赖包spring-cloud-starter-alibaba-seata
2:配置代理数据源DataSourceProxy
3:将MyBatisPlus数据源切换成代理数据源
4:指定seata-server配置
    a.导入file.conf
    b.导入register.conf
5:配置工程中的分布式事务组
6:开始执行分布式事务的方法上添加注解@GlobalTransactional

1)导入依赖包

mall-order-servicemall-goods-service中引入如下依赖包:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2.1.0.RELEASE</version>
</dependency>

2)配置在理数据源

mall-order-servicemall-goods-service中创建如下配置类:

@Configuration
public class SeataDataSourceConfig {
​
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
    //创建代理数据源
    @Bean
    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
​
    //替换MybatisSqlSessionFactoryBean的DataSource
    @Bean
    public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        // 这里用 MybatisSqlSessionFactoryBean 代替了 SqlSessionFactoryBean,否则 MyBatisPlus 不会生效
        MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        mybatisSqlSessionFactoryBean.setDataSource(dataSourceProxy);
        return mybatisSqlSessionFactoryBean;
    }
}

3)导入配置文件

file.confregistry.conf导入到mall-order-servicemall-goods-serviceresources下,同时配置mall-order-servicemall-goods-service的bootstrap.yml文件,在该文件中添加如下配置:

    alibaba:
      seata:
        tx-service-group: tx_shop

文件参考如下:

file.conf:

service {
  #vgroup->rgroup
  vgroup_mapping.tx_shop = "default"
  #only support single node
  default.grouplist = "192.168.xxx.xxx:8091"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
}

registry.conf:

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "file"
​
  file {
    name = "file.conf"
  }
}
​
config {
  # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig
  type = "file"
​
  file {
    name = "file.conf"
  }
}

4)添加注解@GlobalTransactional

com.xz.mall.order.service.impl.OrderServiceImpl#add方法上添加注解@GlobalTransactional

总结

本篇主要介绍了一下Seata分布式事务是如何解决实际开发过程中遇到的跨库事务问题的,着重介绍了一下AT模式工作机制,希望大家在今后的开发工作中能够去体验一下基于Seata的分布式事务操作。