微服务实战(五) Seata 分布式事务

微服务实战(五) Seata 分布式事务

官方文档:https://seata.io/zh-cn/

概述

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

Seata 是阿里开源的分布式事务框架,属于二阶段提交模式。

Seata术语

  • TC (Transaction Coordinator) - 事务协调者

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

  • TM (Transaction Manager) - 事务管理器

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

  • RM (Resource Manager) - 资源管理器

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

AT工作模式

图解

单体应用架构

微服务实战(五) Seata 分布式事务_第1张图片

微服务应用架构

微服务实战(五) Seata 分布式事务_第2张图片

Seate处理事务流程

微服务实战(五) Seata 分布式事务_第3张图片

工作机制

两阶段提交协议的演变:

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

一阶段(1~4)

二阶段-回滚(5~7)

二阶段-提交(8)

  1. Business 是业务入口,在程序中会通过注解(@GlobalTransactional)来说明他是一个全局事务,这时他的角色为 TM(事务管理者)
  2. Business 会请求 TC(事务协调器,一个独立运行的服务),说明自己要开启一个全局事务,TC 会生成一个全局事务ID(XID),并返回给 Business
  3. Business 得到 XID 后,开始调用微服务,例如调用 Storage。
  4. Storage 会收到 XID,知道自己的事务属于这个全局事务。Storage 执行自己的业务逻辑,执行业务 SQL,并把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中。向 TC 注册分支, 申请全局锁,拿到锁后提交本地事务,将本地事务提交的结果上报给 TC。 Storage 的角色是 RM(资源管理者),管理分支事务处理的资源。
  5. 如果所有分支都没有报错,TM向TC发起全局事务提交请求,TC再向RM发送提交请求。如果存在分支报错,TM向TC发起全局事务回滚请求,TC在向个RM发送分支事务回滚请求,
  6. RM收到TC 的分支回滚请求,开启一个本地事务,通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。如果回滚日志存在:将后置镜像与当前数据对比,如果数据一致表示可以回滚(没有发生脏写),通过回滚日志的前置镜像生成回滚SQL, 执行数据回滚。如果回滚日志不存在:插入一条状态为全局事物已完成(数据库的值是: 1 )的回滚日志, 避免另一个线程提交成功。提交本地事务,并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
  7. 如果存在分支回滚失败,TC会重试发起分支回滚请求。当分支都回滚成功,TC向分支发送提交请求。
  8. RM收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

启动服务端

  • 这里服务端存储模式采用的为db模式,服务注册和配置中心为Nacos

下载安装包

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

#解压到指定文件夹
tar -zxvf ./seata-server-1.3.0.tar.gz -C /sunny/software/

下载资源

下载地址:https://github.com/seata/seata/tree/1.3.0/script

目录结构

#存放client端sql脚本 (包含 undo_log表) ,参数配置
client
#各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件
config-center
#server端数据库脚本 (包含 lock_table、branch_table 与 global_table) 及各个容器配置
server
  • client

    需要客户端 添加undo_log表

  • config-center

    config-center目录下的config.txt 放到seata安装目录下,nacos-config.sh放到seata bin目录下

  • server

    Mysql创建数据库seata,执行server目录下的sql脚本,创建表lock_table、branch_table 与 global_table。

修改配置

cd /sunny/software/seata/conf/

#这里采用的Nacos的服务注册与用户中心  所以不需要修改file.conf  只需要修改registry.conf  填写Nacos相关配置

vim ./registry.conf 


#修改注册配置
registry{
 # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
 type = "nacos"
 
 nacos {
     application = "seata-server"
     serverAddr = "127.0.0.1:8848"
     group = "SEATA_GROUP"
     namespace = "e90d261b-9c05-4bcb-b99f-b419d952737a"
     cluster = "default"
     username = "nacos"
     password = "nacos"
 	}
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"
  
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = "e90d261b-9c05-4bcb-b99f-b419d952737a"
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    }
}

推送配置到Nacos

  • 推送配置到Nacos,需要前面下载资源中config-center的config.txtnacos-config.sh

修改config.txt

  • service.vgroupMapping.my_test_tx_group:my_test_tx_group为seata客户端事务组名称,需要与客户端配置相同,多个则配置多行
  • service.default.grouplist :seata服务端地址
  • store.mode=file:默认file ,需要修改为db
  • store.db.url=…:数据库地址
  • store.db.user=username:数据库用户名
  • store.db.password=password:数据库密码
  • store.db.driverClassName:mysql8.0 需要改为com.mysql.cj.jdbc.Driver
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
service.vgroupMapping.sunnyws-seata-order-group=default
service.vgroupMapping.sunnyws-seata-storage-group=default
service.default.grouplist=172.16.220.50:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
store.mode=file
store.publicKey=
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://172.16.220.50:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
store.redis.queryLimit=100
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

推送配置

#-h -p 指定nacos的端口地址;
#-g 指定配置的分组,注意,是配置的分组;
#-t 指定命名空间id; 
#-u -w指定nacos的用户名和密码
#需要已经启动Nacos
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t e90d261b-9c05-4bcb-b99f-b419d952737a -u nacos -w nacos


#示例
[root@localhost bin]# sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t e90d261b-9c05-4bcb-b99f-b419d952737a -u nacos -w nacos
set nacosAddr=127.0.0.1:8848
set group=SEATA_GROUP
Set transport.type=TCP successfully 
Set transport.server=NIO successfully 
Set transport.heartbeat=true successfully 
Set transport.enableClientBatchSendRequest=false successfully 
Set transport.threadFactory.bossThreadPrefix=NettyBoss successfully 
Set transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker successfully 
Set transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler successfully 
Set transport.threadFactory.shareBossWorker=false successfully 
Set transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector successfully 
Set transport.threadFactory.clientSelectorThreadSize=1 successfully 
Set transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread successfully 
Set transport.threadFactory.bossThreadSize=1 successfully 
Set transport.threadFactory.workerThreadSize=default successfully 
Set transport.shutdown.wait=3 successfully 
Set service.vgroupMapping.my_test_tx_group=default successfully 
Set service.default.grouplist=127.0.0.1:8091 successfully 
Set service.enableDegrade=false successfully 

启动服务端

#-h: 注册到注册中心的ip
#-p: Server rpc 监听端口
#-m: 全局事务会话信息存储模式,file、db、redis,优先读取启动参数 (Seata-Server 1.3及以上版本支持redis)
#-n: Server node,多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突
#-e: 多环境配置参考 http://seata.io/en-us/docs/ops/multi-configuration-isolation.html
#前台启动
seata-server.sh -h 172.16.220.50 -p 8091 -m db -n 1 
#后台启动
nohup ./seata-server.sh -h 172.16.220.50 -p 8091 -m db -n 1 > ./nohuo.out 2>&1 &

业务系统集成Client

  • Nacos+seata

引入依赖


<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>

<dependency>
    <groupId>io.seatagroupId>
    <artifactId>seata-spring-boot-starterartifactId>
    <version>1.4.1version>
dependency>
<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-seataartifactId>
    <exclusions>
        <exclusion>
            <groupId>io.seatagroupId>
            <artifactId>seata-spring-boot-starterartifactId>
        exclusion>
    exclusions>
dependency>

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-openfeignartifactId>
dependency>

引入数据库进行测试


<dependency>
    <groupId>com.baomidougroupId>
    <artifactId>mybatis-plus-boot-starterartifactId>
    <version>${mybatis-plus.version}version>
dependency>

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druid-spring-boot-starterartifactId>
    <version>${druid.version}version>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>${mysql.version}version>
    <scope>runtimescope>
dependency>

添加配置

  • 服务端service.vgroupMapping.XXX配置一般与application.name相同,也与客户端tx-service-group配置相同
server:
  port: 8002
spring:
  application:
    name: sunnyws-seata-order
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        server-addr: 172.16.220.50:8848
        namespace: e90d261b-9c05-4bcb-b99f-b419d952737a

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://172.16.220.50:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT&allowPublicKeyRetrieval=true
    username: root
    password: root

seata:
  enabled: true
  application-id: ${spring.application.name}  # seata 应用编号,默认为 ${spring.application.name}
  tx-service-group: sunnyws-seata-order-group # seata 事务组编号,用于 TC 集群名
  # seata 服务配置项,对应 ServiceProperties 类
  service:
    grouplist:
      default: 172.16.220.50:8091
    # 虚拟组和分组的映射, key一定要与 my_test_tx_group一致
    vgroupMapping:
      sunnyws-seata-order-group: default
    #不禁用全局事务
    disable-global-transaction: false

添加注解

  • 业务上添加@GlobalTransactional注解

测试

  • sunnyws-seata-order 下订单
  • sunnyws-seata-storage 减库存
  1. 客户端配置都同上面相同
  2. 当通过feign调用storage 减库存后,抛出异常,查看是否回滚。

微服务实战(五) Seata 分布式事务_第4张图片

将本地@Transactional去除后,可以在异常抛出前看到订单库存都已更新到数据库,并且客户端数据库undo_log表和seata服务端数据global_table表都生成了记录,xid也都相同。

微服务实战(五) Seata 分布式事务_第5张图片

当放开断点,抛出异常后,订单和库存也都回到之前的数据,undo_log和服务端的事务记录也被删除掉了。

微服务实战(五) Seata 分布式事务_第6张图片

作者: SunnyWs
链接: https://sunnyws.com/posts/7a1077b7/
来源: SunnyWs’Blog

你可能感兴趣的:(微服务实战,spring,cloud,alibaba)