13.SpringCloudSeata处理分布式业务
分布式事务(引进)
面试题
你简历上写用微服务boot/cloud做过项目,你不可能只要一个数据库吧?请你谈谈多个数据库之间,你怎样处理分布式事务?
举例:在订单付出成功后,交易中心会调用订单中心的服务把订单状况更新,并调用物流中心的服务告诉产品发货,一同还要调用积分中心的服务为用户添加相应的积分。怎样确保分布式事务一致性,成为了确保订单事务安稳运转的中心诉求之一。
阿里巴巴的Seata-AT方式怎样做到对事务的无侵入?
关于分布式事务,你知道的处理计划有那些?请你谈谈?
- 2PC(两阶段提交)
- 3PC(三阶段提交)
- TCC计划[TCC(Try-Confirm-Cancel)被称为补偿事务),相似2PC的柔性分布式处理计划,2PC改良版]
- LocalMessage本地音讯表
- 独立音讯微服务 + RabbitMQ/Kafka组件,结束可靠音讯终究一致性计划
- 最大尽力告诉计划...
分布式事务问题,怎样发生?先看事务
上述面试问题都指向一个重要问题?
一次事务操作需求跨多个数据源或需求跨多个体系(多个进程)进行长途调用,就会发生分布式事务问题,可是联系型数据库供给的才能是依据单机事务的,一旦遇到分布式事务场景,就需求经过更多其他技能手段来处理问题。
微服务呈现之前
单机单库没有分布式事务的问题。
微服务呈现之后
单体运用被拆分红微服务运用,本来的三个模块,被拆分红三个敌对的运用,别离运用三个独立的数据源,事务操作需求调用三个服务来结束,此刻每个服务自己内部的数据一致性由本地事务来确保,可是大局的数据一致性问题无法确保。
定论
迫切希望供给一种分布式事务结构,处理微服务架构下的分布式事务问题。
1.Seata简介
1.1 是什么
Simple Extensible Autonomous Transaction Architecture(简略可扩展自治事务结构) -- Seata
1.1.1 官网解说
Apache Seata(incubating) 是一款开源的分布式事务处理计划,致力于在微服务架构下供给高性能和简略易用的分布式事务服务。
阿里,现已将其捐献给了Apache基金会。
1.1.2 开展进程
阿里巴巴作为国内最早一批进行运用分布式(微服务化)改造的企业,很早就遇到微服务架构下的分布式事务问题。
2019年1月份蚂蚁金服和阿里巴巴一同开源的分布式事务处理计划:
2014 年,阿里中间件团队发布 TXC(Taobao Transaction Constructor),为集团内运用供给分布式事务服务。
2016 年,TXC 在经过产品化改造后,以 GTS(Global Transaction Service) 的身份登陆阿里云,成为其时业界仅有一款云上分布式事务产品。在阿云里的公有云、专有云处理计划中,开端服务于很多外部客户。
2019 年起,依据 TXC 和 GTS 的技能堆集,阿里中间件团队建议了开源项目 Fescar(Fast & EaSy Commit And Rollback, FESCAR),和社区一同建造这个分布式事务处理计划。
2019 年 fescar(全称fast easy commit and rollback) 被重命名为了seata(simple extensiable autonomous transaction architecture)。TXC、GTS、Fescar 以及 seata 一脉相承,为处理微服务架构下的分布式事务问题交出了一份异乎寻常的答卷。
1.2 功用
Seata
是一款开源的分布式事务处理计划,致力于在微服务架构下供给高性能和简略易用的分布式事务服务。
1.3 下载地址
官网
GitHub
1.4 怎样用
Spring中的本地事务@Transactional
,大局事务@GlobalTransactional
Seata的分布式事务处理计划
2.Seata作业流程简介(原理)
2.1 分布式事务的办理,便是大局事务id的传递和改变,要让开发者无感知
2.2 Seata对分布式事务的协谐和操控便是1 + 3
2.2.1 1个XID
XID是大局事务的仅有标识,它能够在服务的调用链路中传递,绑定到服务的事务上下文中。
2.2.2 官网版3个概念
2.2.3 周阳教师对Seata术语的解说
TC(Tansaction Coordinator):事务和谐器
便是Seata组件,担任保护大局事务和分支事务的状况,驱动
大局事务提交或回滚。
TM(Transaction Manager):事务办理器
标示大局@GlobalTransactional
发动进口的微服务模块(接口),它是大局事务的建议者,担任界说大局事务的规模,并依据TC保护的大局事务和分支事务状况,做出开端事务、提交事务、回滚事务的抉择。
RM(Resource Manager):资源办理器
便是联系型数据库自身,能够时多个RM,担任办理分支事务上的资源,向TC注册分支事务,报告分支事务状况,驱动分支事务的提交或回滚。
留意,Seata的不同方式,运用的存储,运转中发生的的大局事务数据的介质不同,有的是文件,有的是联系型数据库的表,并且运用联系型数据库作为存储介质时,所支撑的联系型数据库也不同,详细见官网数据源支撑,这也是运用Seata作为分布式处理计划的约束之一。
2.3 分布式事务的履行流程-小总结
三个组件相互协作,TC以Seata 服务器(Server)方式独立布置,TM和RM则是以Seata Client的方式集成在微服务中运转,
A typical lifecycle of Seata managed distributed transaction:
- TM asks TC to begin a new global transaction. TC generates an XID representing the global transaction.
- XID is propagated through microservices' invoke chain.
- RM registers local transaction as a branch of the corresponding global transaction of XID to TC.
- TM asks TC for committing or rollbacking the corresponding global transaction of XID.
- TC drives all branch transactions under the corresponding global transaction of XID to finish branch committing or rollbacking.
1.TM 向 TC 恳求敞开一个大局事务,大局事务创立成功并生成一个大局仅有的 XID;
2.XID 在微服务调用链路的上下文中传达;
3.RM 向 TC 注册分支事务,将其归入 XID 对应大局事务的统辖;
4.TM 向 TC 建议针对 XID 的大局提交或回滚抉择;
5.TC 调度XID 下统辖的悉数分支事务结束提交或回滚恳求。
2.4 Seata的事务方式
AT方式
周阳教师,本次课程,只介绍了AT方式。理由是,日常作业,企业调研和本次课时组织约束。
3.Seata-Server2.0.0装置
3.1 下载
见本文1.3 下载地址
3.2 Seata参数官网参阅
https://seata.apache.org/zh-cn/docs/user/configurations
3.3 Seata新手布置攻略
Seata分TC,TM和RM三个人物,TC(Server)端为独自服务端布置,TM和RM(Client端)由事务体系集成。
官网新手布置攻略
3.4 MySQL8.0数据库建库 + 建表
3.4.1 树立TC运用的库
create database seata;
use seata;
3.4.2 在上一步seata库里建表
建表sql地址
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
CREATE TABLE IF NOT EXISTS `vgroup_table`
(
`vGroup` VARCHAR(255),
`namespace` VARCHAR(255),
`cluster` VARCHAR(255),
UNIQUE KEY `idx_vgroup_namespace_cluster` (`vGroup`,`namespace`,`cluster`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
3.5 更改装备
修正seata-server-2.0.0\conf\application.yml装备文件,记住先备份(避免需求回退到原有的装备)
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${log.home:${user.home}/logs/seata}
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP #后续自己在nacos里边新建,不想新建SEATA_GROUP,就写DEFAULT_GROUP
username: nacos
password: nacos
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP #后续自己在nacos里边新建,不想新建SEATA_GROUP,就写DEFAULT_GROUP
namespace:
cluster: default
username: nacos
password: nacos
store:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
user: root
password: 123456
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**
3.6 发动Nacos2.2.3端口号8848
startup.cmd -m standalone
指令运转成功后拜访http://localhost:8848/nacos
3.7 发动seata-server-2.0.0
进入seata软件目录\bin,发动略
进入seata前端界面,http://localhost:7091
进入Nacos,服务列表,会有seata-server服务。
4.Seata事例实战-数据库和表预备
订单 + 库存 + 账户3个事务数据库MySQL预备。
以下演示都需求先发动Nacos后发动Seata,确保两个都OK。
4.1 分布式事务本事例事务阐明
这儿咱们创立三个服务,一个订单服务,一个库存服务,一个账户服务。**
当用户下单时,会在订单服务中创立一个订单,然后经过长途调用库存服务来扣减下单产品的库存,
再经过长途调用账户服务来扣减用户账户里边的余额,
终究在订单服务中修正订单状况为已结束。该操作跨过三个数据库,有两次长途调用,很明显会有分布式事务问题。
4.2 创立3个事务数据库DATABASE
seata_order:存储订单的数据库;
seata_storage:存储库存的数据库;
seata_account:存储账户信息的数据库;
CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account;
4.3 依照上述3库别离树立对应的undo_log回滚日志表
由于AT方式,是需求将分布式事务中,修正前后的数据,做一个快照,保存到数据库中,假如大局事务失利,分支(本地)事务需求回滚,便利修正本地事务现已提交的数据。所以,需求在单个微服务对应的数据库中,树立回滚日志表。成功的话,只要把记载的这条数据删去即可。大局事务成功或失利,没必要保存此事务中的暂时数据。
4.3.1 undo_log表
https://github.com/apache/incubator-seata/blob/2.x/script/client/at/db/mysql.sql
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
留意,在当时笔记中,是以AT方式的mysql数据源,所以挑选的建表句子都是mysql版别的。不同数据源见GitHub或Seata官网。
4.4 依照上述库别离树立对应事务表和undo_log表
#订单库
CREATE DATABASE seata_order;
USE 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=1 DEFAULT CHARSET=utf8;
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
#账号库
CREATE DATABASE seata_account;
USE 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 t_account(`id`,`user_id`,`total`,`used`,`residue`)VALUES('1','1','1000','0','1000');
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
#库存库
CREATE DATABASE seata_storage;
USE 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=1 DEFAULT CHARSET=utf8;
#刺进一条假数据
INSERT INTO t_storage(`id`,`product_id`,`total`,`used`,`residue`)VALUES('1','1','100','0','100');
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
4.5 终究作用
在MySQL8.0中有seata库,seata_order库,seata_storage库,seata_account库,其中有对应的表,表信息略,见上sql句子。
5.Seata事例实战-微服务编码落地结束
事例需求:下订单 -> 减库存 -> 扣余额 -> 改(订单)状况
5.1 运用插件生成根底类
运用最开端的generator模块,和之前生成t_pay表的根底类相同。
5.1.1 config.properties
#t_pay表包名
package.name=com.atguigu.cloud
# mysql8.0
jdbc.driverClass = com.mysql.cj.jdbc.Driver
jdbc.url= jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
jdbc.user = root
jdbc.password =123456
# seata_order
#jdbc.driverClass = com.mysql.cj.jdbc.Driver
#jdbc.url = jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
#jdbc.user = root
#jdbc.password =123456
# seata_storage
#jdbc