十三、Alibaba Seata分布式事务

    技术2022-07-11  97

    一、分布式事务

    1. 概念

    一个微服务对应一个数据库,某个业务需涉及不同微服务,即涉及多数据库;一个业务在完成的时候,多个数据库的操作需要符合事务的要求;每个微服务的事务性只能都本地事务保证,不同微服务的事务性需要分布式事务来保证;

    2. Seata

    Alibaba的开源式分布式事务解决方案;Seata官网;

    二、Seata组件

    1. 1+3套件

    Transaction ID(XID):

    全局唯一的事务ID;

    TC - 事务协调者:

    维护全局和分支事务的状态,驱动全局事务提交或回滚;TC(Server端)为单独服务端部署;

    TM - 事务管理器:

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

    RM - 资源管理器:

    管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。TM和RM(Client端)由业务系统集成。

    三、Seata安装 (windows 0.9版本)

    其实就是一个微服务,来保证分布式事务

    1. 下载

    # 地址:如果下载很慢,自己去网上找百度云盘的资源吧 https://github.com/seata/seata/releases/tag/v0.9.0

    2.seata/conf/file.conf

    service模块

    service模块:修改group属性 ; service { #vgroup->rgroup # service模块:修改自定义事务组的名称 vgroup_mapping.my_test_tx_group = "shuzhan_group" #only support single node default.grouplist = "127.0.0.1:8091" #degrade current not support enableDegrade = false #disable disable = false #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" }

    store模块

    存储模式,默认为file,修改为db; store { ## store mode: file、db # 存储模式,默认为file,修改为db mode = "db" ## file store file { dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions max-branch-session-size = 16384 # globe session size , if exceeded throws exceptions max-global-session-size = 512 # file buffer size , if exceeded allocate new buffer file-write-buffer-cache-size = 16384 # when recover batch read size session.reload.read_size = 100 # async, sync flush-disk-mode = async }

    数据库设置

    上面修改后的db存储数据的数据库地址; ## database store 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://60.205.229.31:3307/seata" user = "root" password = "123456" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 }

    3. 建数据库

    在上述配置的数据库中建立seata数据库,并执行对应的sql;sql位置: seata\conf\db_store.sql;主要来保存事务的执行过程中的相关信息;

    4. seata/conf/registry.conf

    seata 的server端也是要注册到注册中心的; registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa # 默认为file,改成注册到nacos type = "nacos" # 注册的nacos的ip及端口 nacos { serverAddr = "120.79.28.20:9001" namespace = "" cluster = "default" }

    5. 启动

    先启动nacos;再启动seata server: windows, bin/seata server

    四、业务类数据库

    分别为三个数据库 seata_order, seata_storage, seata_account;

    order建表sql

    CREATE TABLE `se_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '表id', `user_id` bigint(20) DEFAULT NULL COMMENT '用户id', `product_id` bigint(20) DEFAULT NULL COMMENT '产品id', `count` int(11) DEFAULT NULL COMMENT '数量', `money` decimal(11,0) DEFAULT NULL COMMENT '金额', `status` int(11) DEFAULT NULL COMMENT '订单状态(0为创建中,1为已完成)', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

    storage建表sql

    CREATE TABLE `storage` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '表id', `product_id` bigint(20) DEFAULT NULL COMMENT '产品id', `total` int(10) DEFAULT NULL COMMENT '总库存', `used` int(10) DEFAULT NULL COMMENT '消耗库存', `leftover` int(10) DEFAULT NULL COMMENT '剩余库存', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

    account建表sql

    CREATE TABLE `account` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '表主键', `user_id` bigint(20) DEFAULT NULL COMMENT '用户id', `total` decimal(50,0) DEFAULT NULL COMMENT '总额度', `used` decimal(50,0) DEFAULT NULL COMMENT '已用额度', `leftover` decimal(50,0) DEFAULT NULL COMMENT '剩余额度', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

    三个库的回滚表

    对应的sql在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;

    五、三个微服务业务类代码

    1. 订单微服务

    pom.xml

    <dependencies> <!--seata的相关依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <!--里面自带的依赖为0.7.9--> <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> <!--web模块,这两个一般绑定在一起--> <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> <!--nacos注册中心--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--openfeign服务调用--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--mybatis与springboot的整合--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--阿里巴巴的druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <!--mysql数据库--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--swagger依赖--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!--lombok插件--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>

    application.yaml

    server: port: 9001 spring: application: name: day-dreamer-order # 数据源只要设置好,并不需要额外的配置 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://60.205.229.31:3307/seata_order username: root password: 123456 # 服务注册的Nacos地址:用IP加宿主机端口,不用写http cloud: nacos: discovery: server-addr: 120.79.28.20:9001 alibaba: seata: # 自定义事务组名称, # 需要和seata-server中的seata/conf/file.conf的service模块保持一致 tx-service-group: shuzhan_group feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 mybatis: mapper-locations: classpath:com.day.dreamer.order.*.xml type-aliases-package: com.day.dreamer.order.domain

    file.conf

    将上述seata-server中修改完的file.conf复制,保存在业务微服务中,修改自定义事务组的名称;service部分,store部分,db部分 service { #vgroup->rgroup # 修改自定义事务组的名称:shuzhan_group,这里为啥和seata-server端配置的不太一样 vgroup_mapping.shuzhan_group = "default" #only support single node default.grouplist = "127.0.0.1:8091" #degrade current not support enableDegrade = false #disable disable = false #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" } store { ## store mode: file、db # 存储模式,默认为file,修改为db mode = "db" ## file store file { dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions max-branch-session-size = 16384 # globe session size , if exceeded throws exceptions max-global-session-size = 512 # file buffer size , if exceeded allocate new buffer file-write-buffer-cache-size = 16384 # when recover batch read size session.reload.read_size = 100 # async, sync flush-disk-mode = async } 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://60.205.229.31:3307/seata" user = "root" password = "123456" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } }

    registry.conf

    将上面seata-server改好后的配置registry.conf文件,也复制在该工程中;没看懂这一步要干啥,seata_server中是为了把seata_server注册到注册中心,但微服务在application.yaml中已经注册过了啊; registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa # 默认为file,改成注册到nacos type = "nacos" nacos { serverAddr = "120.79.28.20:9001" namespace = "" cluster = "default" }
    Processed: 0.011, SQL: 9