Seata
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简略易用的分布式事务服务。在 Seata 开源之前,Seata 对应的外部版本在阿里经济体外部始终扮演着分布式一致性中间件的角色,帮忙经济体安稳的度过历年的双11,对各BU业务进行了无力的撑持。通过多年积淀与积攒,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加欠缺的技术生态和普惠技术成绩,Seata 正式发表对外开源,凋谢以来,广受欢迎,不到一年曾经成为最受欢迎的分布式事务解决方案。
官网中文网:https://seata.io/zh-cn
github我的项目地址:https://github.com/seata/seata
4.1 Seata术语
TC (Transaction Coordinator) - 事务协调者
保护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范畴:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
治理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata 致力于提供高性能和简略易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
4.1 Seata AT模式
Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。其中AT模式最受欢迎,应用也非常简单,但它外在的原理不简略。
AT模式的相干材料请参考官网文档阐明:https://seata.io/zh-cn/docs/o...
下图是AT模式的执行流程:
4.1.1 AT模式及工作流程
见官网文档:https://seata.io/zh-cn/docs/o...
4.1.2 Seata-Server装置
咱们在抉择用Seata版本的时候,能够先参考下官网给出的版本匹配(Seata版本也能够按本人的要求抉择):
https://github.com/alibaba/sp...
Spring Cloud Alibaba Version | Sentinel Version | Nacos Version | RocketMQ Version | Dubbo Version | Seata Version |
---|---|---|---|---|---|
2.2.5.RELEASE | 1.8.0 | 1.4.1 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE | 1.8.0 | 1.3.3 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE | 1.7.1 | 1.2.1 | 4.4.0 | 2.7.6 | 1.2.0 |
2.2.0.RELEASE | 1.7.1 | 1.1.4 | 4.4.0 | 2.7.4.1 | 1.0.0 |
2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE | 1.7.0 | 1.1.4 | 4.4.0 | 2.7.3 | 0.9.0 |
2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE | 1.6.3 | 1.1.1 | 4.4.0 | 2.7.3 | 0.7.1 |
咱们以后SpringCloud Alibaba
的版本是2.2.5.RELEASE
,对应Seata版本是1.3.0,所以咱们首先装置Seata-Server1.3.0
咱们间接基于docker启动失去:
docker run --name seata-server -p 8091:8091 -d -e SEATA_IP=192.168.200.129 -e SEATA_PORT=8091 --restart=on-failure seataio/seata-server:1.3.0
4.1.3 集成springcloud-alibaba
咱们接下来开始在我的项目中集成应用Seata的AT模式实现分布式事务管制,对于如何集成,官网也给出了很多例子,能够通过
https://github.com/seata/seat...
所以各种集成模式须要大家都自行的去翻看对应的samples
。
集成能够依照如下步骤实现:
1:引入依赖包spring-cloud-starter-alibaba-seata2:配置Seata3:创立代理数据源4:@GlobalTransactional全局事务管制
案例需要:
如上图,如果用户打车胜利,须要批改司机状态、下单、记录领取日志,而每个操作都是调用了不同的服务,比方此时hailtaxi-driver
服务执行胜利了,然而hailtaxi-order
有可能执行失败了,这时候如何实现跨服务事务回滚呢?这就要用到分布式事务。
鉴于咱们个别事务都是在service
层进行的治理,所以,革新一下hailtaxi-order
中的OrderInfoController#add
办法,将业务实现放到对应的Service
中
/*** * 下单 *//*@PostMapping public OrderInfo add(){ //批改司机信息 司机ID=1 Driver driver = driverFeign.status("3",2); //创立订单 OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver); orderInfoService.add(orderInfo); return orderInfo; }*/@PostMappingpublic OrderInfo add() { return orderInfoService.addOrder();}
在Service
实现中:
@Servicepublic class OrderInfoServiceImpl implements OrderInfoService { @Autowired private DriverFeign driverFeign; /** * 1、批改司机信息 司机ID=1 * 2、创立订单 * @return */ @Override public OrderInfo addOrder() { //批改司机信息 司机ID=1 Driver driver = driverFeign.status("1",2); //创立订单 OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver); int count = orderInfoMapper.add(orderInfo); System.out.println("====count="+count); return orderInfo; }}
案例实现:
0) 创立undo_log
表
在每个数据库中都须要创立该表:
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;
1)依赖引入
咱们首先在hailtaxi-driver
和hailtaxi-order
中引入依赖:
<!--seata--><dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <version>2.2.5.RELEASE</version></dependency>
2)配置Seata
依赖引入后,咱们须要在我的项目中配置SeataClient
端信息,对于SeataClient端配置信息,官网也给出了很多版本的模板,能够参考官网我的项目:
https://github.com/seata/seat...,如下图:
咱们能够抉择spring,把application.yml
文件间接拷贝到工程中,文件如下:
残缺文件内容见:https://github.com/seata/seat...
批改后咱们在hailtaxi-driver
和hailtaxi-order
我的项目中配置如下:
seata: enabled: true application-id: ${spring.application.name} tx-service-group: my_seata_group enable-auto-data-source-proxy: true use-jdk-proxy: false excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude client: rm: async-commit-buffer-limit: 1000 report-retry-count: 5 table-meta-check-enable: false report-success-enable: false saga-branch-register-enable: false lock: retry-interval: 10 retry-times: 30 retry-policy-branch-rollback-on-conflict: true tm: degrade-check: false degrade-check-period: 2000 degrade-check-allow-times: 10 commit-retry-count: 5 rollback-retry-count: 5 undo: data-validation: true log-serialization: jackson log-table: undo_log only-care-update-columns: true log: exceptionRate: 100 service: vgroup-mapping: my_seata_group: default grouplist: default: 192.168.200.129:8091 enable-degrade: false disable-global-transaction: false transport: shutdown: wait: 3 thread-factory: boss-thread-prefix: NettyBoss worker-thread-prefix: NettyServerNIOWorker server-executor-thread-prefix: NettyServerBizHandler share-boss-worker: false client-selector-thread-prefix: NettyClientSelector client-selector-thread-size: 1 client-worker-thread-prefix: NettyClientWorkerThread worker-thread-size: default boss-thread-size: 1 type: TCP server: NIO heartbeat: true serialization: seata compressor: none enable-client-batch-send-request: true
对于配置文件内容参数比拟多,咱们须要把握外围局部:
seata_transaction: default:事务分组,后面的seata_transaction能够自定义,通过事务分组很不便找到集群节点信息。tx-service-group: seata_transaction:指定利用的事务分组,和下面定义的分组前局部保持一致。default: 192.168.200.129:8091:服务地址,seata-server服务地址。
留神:
当初配置信息都是托管到nacos中的,所以能够间接将配置存储到nacos中
hailtaxi-order
hailtaxi-driver
3)代理数据源
通过代理数据源能够保障事务日志数据和业务数据能同步,对于代理数据源晚期须要手动创立,然而随着Seata版本升级,不同版本实现计划不一样了,上面是官网的介绍:
1.1.0: seata-all勾销属性配置,改由注解@EnableAutoDataSourceProxy开启,并可抉择jdk proxy或者cglib proxy1.0.0: client.support.spring.datasource.autoproxy=true0.9.0: support.spring.datasource.autoproxy=true
咱们以后的版本是1.3.0,所以咱们创立代理数据源只须要在启动类上增加@EnableAutoDataSourceProxy
注解即可,
在hailtaxi-order
及hailtaxi-driver
的启动类上别离增加该注解:
@SpringBootApplication@EnableDiscoveryClient@EnableFeignClients(basePackages = {"com.itheima.driver.feign"})@EnableAutoDataSourceProxypublic class OrderApplication {}
4)全局事务管制
打车胜利创立订单是由客户发动,在hailtaxi-order
中执行,并且feign调用hailtaxi-driver
,所以hailtaxi-order
是全局事务入口,咱们在OrderInfoServiceImpl.addOrder()
办法上增加@GlobalTransactional
,那么此时该办法就是全局事务的入口,
@Override@GlobalTransactionalpublic OrderInfo addOrder() { //批改司机信息 司机ID=1 Driver driver = driverFeign.status("1",2); //创立订单 OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver); int count = orderInfoMapper.add(orderInfo); System.out.println("====count="+count); return orderInfo;}
5)分布式事务测试
1、测试失常状况,启动测试
将id=1
的司机状态手动改为1,而后进行测试
2、异样测试,在hailtaxi-order
的service办法中增加一个异样,
@Override@GlobalTransactionalpublic OrderInfo addOrder() { //批改司机信息 司机ID=1 Driver driver = driverFeign.status("1",2); //创立订单 OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver); int count = orderInfoMapper.add(orderInfo); System.out.println("====count="+count); //模仿异样 int i = 1 / 0; return orderInfo;}
测试前,将id=1
的司机状态手动改为1,将订单表清空,再次测试,看状态是否被更新,订单有没有增加,以此验证分布式事务是否管制胜利!
4.2 Seata TCC模式
一个分布式的全局事务,整体是 两阶段提交 的模型。全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即须要每个分支事务都具备本人的:
- 一阶段 prepare 行为
- 二阶段 commit 或 rollback 行为
依据两阶段行为模式的不同,咱们将分支事务划分为 Automatic (Branch) Transaction Mode 和 Manual (Branch) Transaction Mode.
AT 模式(参考链接 TBD)基于 反对本地 ACID 事务 的 关系型数据库:
- 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
- 二阶段 commit 行为:马上胜利完结,主动 异步批量清理回滚日志。
- 二阶段 rollback 行为:通过回滚日志,主动 生成弥补操作,实现数据回滚。
相应的,TCC 模式,不依赖于底层数据资源的事务反对:
- 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
- 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
- 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
所谓 TCC 模式,是指反对把 自定义 的分支事务纳入到全局事务的治理中。
TCC实现原理:
有一个 TCC 拦截器,它会封装 Confirm 和 Cancel 办法作为资源(用于前面 TC 来 commit 或 rollback 操作)封装完,它会本地缓存到 RM (缓存的是办法的形容信息),能够简略认为是放到一个 Map 外面当 TC 想调用的时候,就能够从 Map 里找到这个办法,用反射调用就能够了另外,RM 不光是注册分支事务(分支事务是注册到 TC 里的 GlobalSession 中的)它还会把方才封装的资源里的重要属性(事务ID、归属的事务组等)以资源的模式注册到 TC 中的 RpcContext这样,TC 就晓得以后全局事务都有哪些分支事务了(这都是分支事务初始化阶段做的事件)举个例子:RpcContext外面有资源 123,然而 GlobalSession 里只有分支事务 12于是 TC 就晓得分支事务 3 的资源曾经注册进来了,然而分支事务 3 还没注册进来这时若 TM 通知 TC 提交或回滚,那 GlobalSession 就会通过 RpcContext 找到 1 和 2 的分支事务的地位(比方该调用哪个办法)当 RM 收到提交或回滚后,就会通过本人的本地缓存找到对应办法,最初通过反射或其余机制去调用真正的 Confirm 或 Cancel
5 Seata注册核心
参看:https://github.com/seata/seat... 能够看到seata
反对多种注册核心!
5.1 服务端注册核心配置
服务端注册核心(位于seata-server的registry.conf配置文件中的registry.type参数),为了实现seata-server集群高可用不会应用file类型,个别会采纳第三方注册核心,例如zookeeper、redis、eureka、nacos等。
咱们这里应用nacos
,seata-server的registry.conf配置如下:
因为咱们是基于docker
启动的seata
,故能够间接进入到容器外部批改配置文件/resources/registry.conf
registry { # file ...nacos ...eureka...redis...zk...consul...etcd3...sofa type = "nacos" nacos { application = "seata-server" serverAddr = "192.168.200.129:8848" group = "SEATA_GROUP" namespace = "1ebba5f6-49da-40cc-950b-f75c8f7d07b3" cluster = "default" username = "nacos" password = "nacos" }}
此时咱们再重新启动容器,拜访:http://192.168.200.129:8848/n... 看seata
是否已注册到nacos中
5.2 客户端注册核心配置
我的项目中,咱们须要应用注册核心,增加如下配置即可(在nacos配置核心的hailtaxi-order.yaml
和hailtaxi-driver-dev.yaml
都批改)
参看:https://github.com/seata/seat...
registry: type: nacos nacos: application: seata-server server-addr: 192.168.200.129:8848 group : "SEATA_GROUP" namespace: 1ebba5f6-49da-40cc-950b-f75c8f7d07b3 username: "nacos" password: "nacos"
此时就能够正文掉配置中的default.grouplist="192.168.200.129:8091"
残缺配置如下:
seata:enabled: trueapplication-id: ${spring.application.name}tx-service-group: my_seata_groupenable-auto-data-source-proxy: trueuse-jdk-proxy: falseexcludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExcludeclient:rm:async-commit-buffer-limit: 1000report-retry-count: 5table-meta-check-enable: falsereport-success-enable: falsesaga-branch-register-enable: falselock: retry-interval: 10 retry-times: 30 retry-policy-branch-rollback-on-conflict: truetm:degrade-check: falsedegrade-check-period: 2000degrade-check-allow-times: 10commit-retry-count: 5rollback-retry-count: 5undo:data-validation: truelog-serialization: jacksonlog-table: undo_logonly-care-update-columns: truelog:exceptionRate: 100service:vgroup-mapping:my_seata_group: default#grouplist:#default: 192.168.200.129:8091enable-degrade: falsedisable-global-transaction: falsetransport:shutdown:wait: 3thread-factory:boss-thread-prefix: NettyBossworker-thread-prefix: NettyServerNIOWorkerserver-executor-thread-prefix: NettyServerBizHandlershare-boss-worker: falseclient-selector-thread-prefix: NettyClientSelectorclient-selector-thread-size: 1client-worker-thread-prefix: NettyClientWorkerThreadworker-thread-size: defaultboss-thread-size: 1type: TCPserver: NIOheartbeat: trueserialization: seatacompressor: noneenable-client-batch-send-request: trueregistry:type: nacosnacos:application: seata-serverserver-addr: 192.168.200.129:8848group : "SEATA_GROUP"namespace: 1ebba5f6-49da-40cc-950b-f75c8f7d07b3username: "nacos"password: "nacos"
测试:
启动服务再次测试,查看分布式事务是否依然能管制住!!!
6 Seata高可用
seata-server
目前应用的是一个单节点,是否抗住高并发是一个值得思考的问题。生产环境我的项目简直都须要确保能扛高并发、具备高可用的能力,因而生产环境我的项目个别都会做集群。
下面配置也只是将注册核心换成了nacos
,而且是单机版的,如果要想实现高可用,就得实现集群,集群就须要做一些动作来保障集群节点间的数据同步(会话共享)等操作
咱们须要筹备2个seata-server
节点,并且seata-server
的事务日志存储模式,共反对3种形式,
1):file【集群不可用】
2):redis
3):db
咱们这里抉择redis存储会话信息实现共享。
1、启动第二个seata-server
节点
docker run --name seata-server-n2 -p 8092:8092 -d -e SEATA_IP=192.168.200.129 -e SEATA_PORT=8092 --restart=on-failure seataio/seata-server:1.3.0
2、进入容器批改配置文件 registry.conf
,增加注册核心的配置
registry { # file ...nacos ...eureka...redis...zk...consul...etcd3...sofa type = "nacos" nacos { application = "seata-server" serverAddr = "192.168.200.129:8848" group = "SEATA_GROUP" namespace = "1ebba5f6-49da-40cc-950b-f75c8f7d07b3" cluster = "default" username = "nacos" password = "nacos" }}
3、批改seata-server
事务日志的存储模式,resources/file.conf
改变如下:
咱们采纳基于redis来存储集群每个节点的事务日志,通过docker容许一个redis
docker run --name redis6.2 --restart=on-failure -p 6379:6379 -d redis:6.2
而后批改seata-server的file.conf,批改如下:
## transaction log store, only used in seata-serverstore { ## store mode: file...db...redis mode = "redis" ## file store property file { ## store location dir dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions maxBranchSessionSize = 16384 # globe session size , if exceeded throws exceptions maxGlobalSessionSize = 512 # file buffer size , if exceeded allocate new buffer fileWriteBufferCacheSize = 16384 # when recover batch read size sessionReloadReadSize = 100 # async, sync flushDiskMode = async } ## database store property db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc. datasource = "druid" ## mysql/oracle/postgresql/h2/oceanbase etc. dbType = "mysql" driverClassName = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata" user = "mysql" password = "mysql" minConn = 5 maxConn = 30 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } ## redis store property redis { host = "192.128.200.129" port = "6379" password = "" database = "0" minConn = 1 maxConn = 10 queryLimit = 100 }}
如果基于DB来存储
seata-server
的事务日志数据,则须要创立数据库seata
,表信息如下:https://github.com/seata/seat...
批改完后重启
留神:另一个seata-server
节点也同样须要批改其存储事务日志的模式
4、再次启动服务测试,查看分布式事务是否仍然能管制胜利!
本文由传智教育博学谷 - 狂野架构师教研团队公布,转载请注明出处!
如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源