什么是分布式事务

单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,
业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
说白了就是一次调用需要修改不同服务器上的数据库,导致一个服务器崩了数据没有按照原有的预期的方式执行,导致数据错误的问题。

Seata简介

是什么

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

官网地址

能干嘛

组成

由一个全局唯一事务id以及三个组件组成分别是:

  1. 全局唯一的事务id
  2. Transaction Coordinator (TC):事务提交回滚的头头,事务最终是由它管理的
  3. Transaction Manager ™:确定事务边界,负责管理协调这个事务的范围
  4. Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支(本地)事务的提交和回滚

处理过程

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

如何使用

步骤

下载

注意笔者下载的是seata-server-0.9.0.zip
https://github.com/seata/seata/releases

配置

打开seata-server的conf下的file.conf

1.自定义一个事务组名称

service {#vgroup->rgroupvgroup_mapping.my_test_tx_group = "fsp_tx_group"#only support single nodedefault.grouplist = "127.0.0.1:8091"#degrade current not supportenableDegrade = false#disabledisable = false#unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanentmax.commit.retry.timeout = "-1"max.rollback.retry.timeout = "-1"
}
  1. 指向自己的数据库

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://127.0.0.1:3306/seata"user = "root"password = "123456"min-conn = 1max-conn = 3global.table = "global_table"branch.table = "branch_table"lock-table = "lock_table"query-limit = 100}
  1. 配置registry.conf将这个组件注册到nacos中

 type = "nacos"nacos {serverAddr = "localhost:8848"namespace = ""cluster = "default"}

数据库配置

  1. 在自己所使用的数据库中创建一个seata的数据库
  2. 将seata中的sql脚本刷到这个库中

启动nacos

启动Seata

如下图所示,如果服务列表出现名为serverAddr的服务就说明部署成功了

订单/库存/账户业务数据库准备

业务说明

这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。

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

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

建库

CREATE DATABASE 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 DEFAULT CHARSET=utf8;SELECT * FROM t_order;
CREATE DATABASE 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=2 DEFAULT CHARSET=utf8;INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
VALUES ('1', '1', '100', '0', '100');SELECT * FROM t_storage;
CREATE DATABASE seata_account;CREATE TABLE t_account (`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',`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');SELECT * FROM t_account;

订单-库存-账户3个库下都需要建各自的回滚日志表

在每个数据库中跑下图文件的sql建表脚本即可

订单模块搭建

创建模块

new 一个module名为seata-order-service2001

修改pom

注意seata版本需要和当前使用版本一致

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>mscloud</artifactId><groupId>com.zsy.springcloud</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><groupId>com.zsy.springcloud</groupId><artifactId>seata-order-service2001</artifactId><version>1.0-SNAPSHOT</version><dependencies><!--nacos--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><exclusions><exclusion><artifactId>seata-all</artifactId><groupId>io.seata</groupId></exclusion></exclusions></dependency><dependency><groupId>io.seata</groupId><artifactId>seata-all</artifactId><version>0.9.0</version></dependency><!--feign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--web-actuator--><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-druid--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.37</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies></project>

添加yml

注意事务组名称需要和上文seata中配置的一致

server:port: 2001spring:application:name: seata-order-servicecloud:alibaba:seata:#自定义事务组名称需要与seata-server中的对应tx-service-group: fsp_tx_groupnacos:discovery:server-addr: localhost:8848datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_orderusername: rootpassword: 123456feign:hystrix:enabled: falselogging:level:io:seata: infomybatis:mapperLocations: classpath:mapper/*.xml

添加两个conf

  1. file.conf

注意这里事务组的名称

transport {# tcp udt unix-domain-sockettype = "TCP"#NIO NATIVEserver = "NIO"#enable heartbeatheartbeat = true#thread factory for nettythread-factory {boss-thread-prefix = "NettyBoss"worker-thread-prefix = "NettyServerNIOWorker"server-executor-thread-prefix = "NettyServerBizHandler"share-boss-worker = falseclient-selector-thread-prefix = "NettyClientSelector"client-selector-thread-size = 1client-worker-thread-prefix = "NettyClientWorkerThread"# netty boss thread size,will not be used for UDTboss-thread-size = 1#auto default pin or 8worker-thread-size = 8}shutdown {# when destroy server, wait secondswait = 3}serialization = "seata"compressor = "none"
}service {vgroup_mapping.fsp_tx_group = "default" #修改自定义事务组名称default.grouplist = "127.0.0.1:8091"enableDegrade = falsedisable = falsemax.commit.retry.timeout = "-1"max.rollback.retry.timeout = "-1"disableGlobalTransaction = false
}client {async.commit.buffer.limit = 10000lock {retry.internal = 10retry.times = 30}report.retry.count = 5tm.commit.retry.count = 1tm.rollback.retry.count = 1
}## transaction log store
store {## store mode: file、dbmode = "db"## file storefile {dir = "sessionStore"# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptionsmax-branch-session-size = 16384# globe session size , if exceeded throws exceptionsmax-global-session-size = 512# file buffer size , if exceeded allocate new bufferfile-write-buffer-cache-size = 16384# when recover batch read sizesession.reload.read_size = 100# async, syncflush-disk-mode = async}## database storedb {## 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://127.0.0.1:3306/seata"user = "root"password = "123456"min-conn = 1max-conn = 3global.table = "global_table"branch.table = "branch_table"lock-table = "lock_table"query-limit = 100}
}
lock {## the lock store mode: local、remotemode = "remote"local {## store locks in user's database}remote {## store locks in the seata's server}
}
recovery {#schedule committing retry period in millisecondscommitting-retry-period = 1000#schedule asyn committing retry period in millisecondsasyn-committing-retry-period = 1000#schedule rollbacking retry period in millisecondsrollbacking-retry-period = 1000#schedule timeout retry period in millisecondstimeout-retry-period = 1000
}transaction {undo.data.validation = trueundo.log.serialization = "jackson"undo.log.save.days = 7#schedule delete expired undo_log in millisecondsundo.log.delete.period = 86400000undo.log.table = "undo_log"
}## metrics settings
metrics {enabled = falseregistry-type = "compact"# multi exporters use comma dividedexporter-list = "prometheus"exporter-prometheus-port = 9898
}support {## springspring {# auto proxy the DataSource beandatasource.autoproxy = false}
}
  1. registry.conf
    注意这里配置为自己所使用的注册中心即可

registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "nacos"nacos {serverAddr = "localhost:8848"namespace = ""cluster = "default"}eureka {serviceUrl = "http://localhost:8761/eureka"application = "default"weight = "1"}redis {serverAddr = "localhost:6379"db = "0"}zk {cluster = "default"serverAddr = "127.0.0.1:2181"session.timeout = 6000connect.timeout = 2000}consul {cluster = "default"serverAddr = "127.0.0.1:8500"}etcd3 {cluster = "default"serverAddr = "http://localhost:2379"}sofa {serverAddr = "127.0.0.1:9603"application = "default"region = "DEFAULT_ZONE"datacenter = "DefaultDataCenter"cluster = "default"group = "SEATA_GROUP"addressWaitTime = "3000"}file {name = "file.conf"}
}config {# file、nacos 、apollo、zk、consul、etcd3type = "file"nacos {serverAddr = "localhost"namespace = ""}consul {serverAddr = "127.0.0.1:8500"}apollo {app.id = "seata-server"apollo.meta = "http://192.168.1.204:8801"}zk {serverAddr = "127.0.0.1:2181"session.timeout = 6000connect.timeout = 2000}etcd3 {serverAddr = "http://localhost:2379"}file {name = "file.conf"}
}

编写通用返回值和domain类

package com.zsy.springcloud.common;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {private Integer code;private String message;private T data;public CommonResult(Integer code, String message) {this(code, message, null);}
}
package com.zsy.springcloud.domain;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.math.BigDecimal;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {private Long id;private Long userId;private Long productId;private Integer count;private BigDecimal money;/*** 订单状态:0:创建中;1:已完结*/private Integer status;
}

dao、service、controller

注意这里库存和账户余额扣款都是通过feign来进行调用的

创建相应的库存服务和扣款服务

库存服务


扣款服务

具体可以参照笔者源码

https://gitee.com/fugongliudehua/mscloud

测试

我们想测试分布式事务,且我们都知道feign超时时间是1s,所以我们可以尝试在账户扣款中增加一个线程休眠,如下图所示

如果去掉下图所示注解,我们可以看到t_order表创建了订单,并且storage表扣了库存,而用户钱包即t_account没有扣款,很明显这种情况等于让用户白嫖了

反之我们将注解接触就会发现,扣款服务feign报超时异常时,所有数据都会自动回滚,保证了分布式事务

seata工作原理

前置步骤

我们不妨在订单service类中添加如下所示断点

一阶段提交

在一阶段,Seata 会拦截“业务 SQL”,

  1. 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
  2. 执行“业务 SQL”更新业务数据,在业务数据更新之后,
  3. 其保存成“after image”,最后生成行锁。
    以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

    所以我们代码debug到库存服务的feign调用时


可以看到undo log表插入了一条数据,这就是所谓的RM 向 TC 汇报资源准备状态


并且我们可以看到rollback info中,记录了扣款前和扣款后的值



当我们执行到如下代码,由于我们设置了线程休眠30s,触发超时异常,触发了二阶段回滚

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,
如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

可以看到undo log之前的数据没了


反之二阶段如是顺利提交的话,
因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

源码地址

https://gitee.com/fugongliudehua/mscloud

微服务-分布式事务seata相关推荐

  1. 一行代码就能解决微服务分布式事务问题,你知道GTS怎么做到的吗?

    2019独角兽企业重金招聘Python工程师标准>>> GTS直播火热报名中,直播直通车 一.GTS (Global Transaction Service)是啥? GTS(全局事务 ...

  2. seata 如何开启tcc事物_微服务分布式事务4种解决方案实战

    分布式事务 分布式事务是指事务的参与者,支持事务的服务器,资源服务器分别位于分布式系统的不同节点之上,通常一个分布式 事物中会涉及到对多个数据源或业务系统的操作. 典型的分布式事务场景:跨银行转操作就 ...

  3. seata xid是什么_微服务分布式事务解决方案-springboot整合分布式seata1.3.0

    概述 Seat是蚂蚁金服和阿里巴巴联合推出的一个开源的分布式事务框架,在阿里云商用的叫做GTS. 项目地址:https://github.com/longxiaonan/springcloud-dem ...

  4. 微服务分布式事务解决方案Seata

    文章目录 一.Seata是什么? 二.使用步骤 1.引入库 2.读入数据 总结 一.什么是Seata? Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用 的分布式事务服务.Sea ...

  5. 同事操作两个数据源保持事务一致_微服务分布式事务4种解决方案实战

    分布式事务 分布式事务是指事务的参与者,支持事务的服务器,资源服务器分别位于分布式系统的不同节点之上,通常一个分布式 事物中会涉及到对多个数据源或业务系统的操作. 典型的分布式事务场景:跨银行转操作就 ...

  6. .Net Core with 微服务 - 分布式事务 - 2PC、3PC

    最近比较忙,好久没更新了.这次我们来聊一聊分布式事务. 在微服务体系下,我们的应用被分割成多个服务,每个服务都配置一个数据库.如果我们的服务划分的不够完美,那么为了完成业务会出现非常多的跨库事务.即使 ...

  7. 微服务--分布式事务的实现方法及替代方案

    这两天正在研究微服务架构中分布式事务的处理方案, 做一个小小的总结, 作为备忘. 如有错误, 欢迎指正! 概念澄清 事务补偿机制: 在事务链中的任何一个正向操作, 都必须存在一个完全符合回滚规则的可逆 ...

  8. 微服务分布式事务实战(一) 项目需求描述和实现步骤

    本文通过一个具体实例如何实施springCloud 分布式事务,不对分布式事务理论做探索.由于内容较多,分多个小节来说明 案例需求: 创建2个基于springCloud的微服务,分别访问不同的数据库: ...

  9. 微服务~分布式事务里的最终一致性

    本地事务ACID大家应该都知道了,统一提交,失败回滚,严格保证了同一事务内数据的一致性!而分布式事务不能实现这种ACID,它只能实现CAP原则里的某两个,CAP也是分布式事务的一个广泛被应用的原型,C ...

最新文章

  1. C# 发送Http请求 - WebClient类
  2. wxWidgets:VScroll示例
  3. mysql查询优化以及面试小结
  4. OpenCV添加中文(五)
  5. netty源码阅读之UnpooledByteBufAllocator
  6. Linux中安装oracle
  7. linux 下小技巧之-统计文件夹下面子文件夹下面的个数
  8. 介绍数据库中的wal技术_门禁系统中RFID与ETC两种新兴技术介绍
  9. 学c++要不要先学C语言?
  10. 秋招之8.31农行研发中心笔试
  11. 电流探头常见的三大类型
  12. 虚拟机导致无法上网_虚拟机无法上网问题解决方法
  13. 从苏宁电器到卡巴斯基(第二部)第06篇:我在卡巴的日子 VI
  14. IDA 中怎么查看函数的调用关系
  15. 2013,爱上暗色调
  16. 作品集十(平面设计)
  17. CSS系列之浏览器私有前缀
  18. php 新手二维码生成
  19. 一篇文章带你了解Nodejs
  20. 为何大量网站不能抓取?爬虫突破封禁的6种常见方法

热门文章

  1. Surface Book2 购买、使用、体验
  2. GitBook建立本地Book及导入别人Book
  3. php获取搜索框的函数,PHP自定义函数获取搜索引擎来源关键字的方法
  4. H5 CSS 引入方式 外联样式表
  5. 基于物联网流量指纹的安全威胁轻量级检测方法
  6. 方程组的两种理解方式(线性代数及其应用【2】)
  7. 五招教你做好企业年终绩效考核,太实用了!
  8. Linux系统设置命令大全
  9. Scratch不仅适合小朋友,程序员和大学老师都应该广泛使用!!!
  10. 职场技巧之PPT制作