微服务-分布式事务seata
什么是分布式事务
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,
业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
说白了就是一次调用需要修改不同服务器上的数据库,导致一个服务器崩了数据没有按照原有的预期的方式执行,导致数据错误的问题。
Seata简介
是什么
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网地址
能干嘛
组成
由一个全局唯一事务id以及三个组件组成分别是:
- 全局唯一的事务id
- Transaction Coordinator (TC):事务提交回滚的头头,事务最终是由它管理的
- Transaction Manager ™:确定事务边界,负责管理协调这个事务的范围
- Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支(本地)事务的提交和回滚
处理过程
- TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
- XID 在微服务调用链路的上下文中传播;
- RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
- TM 向 TC 发起针对 XID 的全局提交或回滚决议;
- 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"
}
- 指向自己的数据库
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}
- 配置registry.conf将这个组件注册到nacos中
type = "nacos"nacos {serverAddr = "localhost:8848"namespace = ""cluster = "default"}
数据库配置
- 在自己所使用的数据库中创建一个seata的数据库
- 将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
- 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}
}
- 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”,
- 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
- 执行“业务 SQL”更新业务数据,在业务数据更新之后,
- 其保存成“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相关推荐
- 一行代码就能解决微服务分布式事务问题,你知道GTS怎么做到的吗?
2019独角兽企业重金招聘Python工程师标准>>> GTS直播火热报名中,直播直通车 一.GTS (Global Transaction Service)是啥? GTS(全局事务 ...
- seata 如何开启tcc事物_微服务分布式事务4种解决方案实战
分布式事务 分布式事务是指事务的参与者,支持事务的服务器,资源服务器分别位于分布式系统的不同节点之上,通常一个分布式 事物中会涉及到对多个数据源或业务系统的操作. 典型的分布式事务场景:跨银行转操作就 ...
- seata xid是什么_微服务分布式事务解决方案-springboot整合分布式seata1.3.0
概述 Seat是蚂蚁金服和阿里巴巴联合推出的一个开源的分布式事务框架,在阿里云商用的叫做GTS. 项目地址:https://github.com/longxiaonan/springcloud-dem ...
- 微服务分布式事务解决方案Seata
文章目录 一.Seata是什么? 二.使用步骤 1.引入库 2.读入数据 总结 一.什么是Seata? Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用 的分布式事务服务.Sea ...
- 同事操作两个数据源保持事务一致_微服务分布式事务4种解决方案实战
分布式事务 分布式事务是指事务的参与者,支持事务的服务器,资源服务器分别位于分布式系统的不同节点之上,通常一个分布式 事物中会涉及到对多个数据源或业务系统的操作. 典型的分布式事务场景:跨银行转操作就 ...
- .Net Core with 微服务 - 分布式事务 - 2PC、3PC
最近比较忙,好久没更新了.这次我们来聊一聊分布式事务. 在微服务体系下,我们的应用被分割成多个服务,每个服务都配置一个数据库.如果我们的服务划分的不够完美,那么为了完成业务会出现非常多的跨库事务.即使 ...
- 微服务--分布式事务的实现方法及替代方案
这两天正在研究微服务架构中分布式事务的处理方案, 做一个小小的总结, 作为备忘. 如有错误, 欢迎指正! 概念澄清 事务补偿机制: 在事务链中的任何一个正向操作, 都必须存在一个完全符合回滚规则的可逆 ...
- 微服务分布式事务实战(一) 项目需求描述和实现步骤
本文通过一个具体实例如何实施springCloud 分布式事务,不对分布式事务理论做探索.由于内容较多,分多个小节来说明 案例需求: 创建2个基于springCloud的微服务,分别访问不同的数据库: ...
- 微服务~分布式事务里的最终一致性
本地事务ACID大家应该都知道了,统一提交,失败回滚,严格保证了同一事务内数据的一致性!而分布式事务不能实现这种ACID,它只能实现CAP原则里的某两个,CAP也是分布式事务的一个广泛被应用的原型,C ...
最新文章
- C# 发送Http请求 - WebClient类
- wxWidgets:VScroll示例
- mysql查询优化以及面试小结
- OpenCV添加中文(五)
- netty源码阅读之UnpooledByteBufAllocator
- Linux中安装oracle
- linux 下小技巧之-统计文件夹下面子文件夹下面的个数
- 介绍数据库中的wal技术_门禁系统中RFID与ETC两种新兴技术介绍
- 学c++要不要先学C语言?
- 秋招之8.31农行研发中心笔试
- 电流探头常见的三大类型
- 虚拟机导致无法上网_虚拟机无法上网问题解决方法
- 从苏宁电器到卡巴斯基(第二部)第06篇:我在卡巴的日子 VI
- IDA 中怎么查看函数的调用关系
- 2013,爱上暗色调
- 作品集十(平面设计)
- CSS系列之浏览器私有前缀
- php 新手二维码生成
- 一篇文章带你了解Nodejs
- 为何大量网站不能抓取?爬虫突破封禁的6种常见方法