分布式事务处理-Seate
分布式事务处理
认识本地事务
事务的特性
什么是事物
事务就是针对数据库的一组操作,它可以由一条或多条SQL语句组 成,同一个事务的操作具备同步的特点,事务中的语句要么都执 行,要么都不执行。
什么是本地事务
本地事务就是这一组sql语句在一个数据库连接中执行,即我们常说 的事务就是一个本地事务,遵循ACID的。
本地事务实现:
编程式事务:begin,commit手动提交事务
声明式事务:加上@Transactional注解
认识分布式事务
前言
随着互联网的快速发展,软件系统由原来的单体应用转变为分布式 应用,下图描述了单体应用向微服务的演变。
注意:
分布式系统会把一个应用系统拆分为可独立部署的多个服务, 因此需要服务与服务之间远程协作才能完成事务操作,这种分 布式系统环境下由不同的服务之间通过网络远程协作完成事务 称之为分布式事务,例如用户注册送积分事务、创建订单减库 存事务,银行转账事务等都是分布式事务。
假如没有分布式事务
在一系列微服务系统当中,假如不存在分布式事务,会发生什么 呢?让我们以互联网中常用的交易业务为例子:
解释:
上图中包含了库存和订单两个独立的微服务,每个微服务维护 了自己的数据库。在交易系统的业务逻辑中,一个商品在下单 之前需要先调用库存服务,进行扣除库存,再调用订单服务, 创建订单记录。
正常情况下,两个数据库各自更新成功,两边数据维持着一致性。
但是,在非正常情况下,有可能库存的扣减完成了,随后的订单记 录却因为某些原因插入失败。这个时候,两边数据就失去了应有的 一致性。
|
问题:
这种时候需要要保证数据的一致性,单数据源的一致性靠单机 事物来保证,多数据源的一致性就要靠分布式事物保证。
什么是分布式事务
指一次大的操作由不同的小操作组成的,这些小的操作分布在不同 的服务器上,分布式事务需要保证这些小操作要么全部成功,要么 全部失败。从本质上来说,分布式事务就是为了保证不同数据库的 数据一致性。
分布式事务产生的场景
什么是分布式事务
指一次大的操作由不同的小操作组成的,这些小的操作分布在不同 的服务器上,分布式事务需要保证这些小操作要么全部成功,要么 全部失败。从本质上来说,分布式事务就是为了保证不同数据库的 数据一致性。
典型的场景:
各个微服务是部署在不同的JVM进程中的,此时,就会产生因跨 JVM进程而导致的分布式事务问题。
跨数据库实例
随着用户增多,数据库压力越来越大,此时我们会进行数据库的拆 分,怎么拆分呐?比如:拆分成用户数据库、商品数据库、订单数 据库。
说明:
数据库由原先的单库,被拆分成2个数据库。单体系统需要访问 多个数据库时就会产生分布式事务。
多个服务数据库
随着模块越来越多,项目越来越庞大,我们会将模块拆分成不同的 项目,那么两个服务需要跨网络远程调用,两个服务持有了不同的 数据库连接进行数据库操作,此时就会产生分布式事务。
简单理解:
跨网络远程调用产生分布式事务
总结
(1)跨网络远程调用完成事务协作,就会产生分布式事务。
(2)跨数据库实例完成事务协作,就会产生分布式事务。
认识Seate
Seata是什么
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简 单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
github地 址:https://github.com/seata/seata。
Seata 是阿里开源的,阿里是国内最早一批进行应用分布式(微服 务化)改造的企业,所以很早就遇到微服务架构下的分布式事务问 题。
仓储服务:对给定的商品进行增删操作记录数量。 订
单服务:根据采购者的需求创建订单。
账户服务:从用户账户中扣除余额、积分等。
数据源支持
AT模式
AT模式支持的数据库有:MySQL、Oracle、PostgreSQL、TiDB、 MariaDB、DaMeng、PolarDB-X 2.0、SQLServer。
TCC模式
TCC模式不依赖数据源(1.4.2版本及之前),1.4.2版本之后增加了 TCC防悬挂措施,需要数据源支持。
Saga模式
Saga模式不依赖数据源。
XA模式
XA模式只支持实现了XA协议的数据库。Seata支持MySQL、 Oracle、PostgreSQL和MariaDB。
分布式解决方案XA模式原理
XA模式概述
XA模式是一种分布式事务处理的标准接口,它定义了两阶段提交 (Two-Phase Commit)协议。
XA模式的主要特点包括
原子性(Atomicity)
持久性(Durability)
一致性(Consistency)
隔离性(Isolation)
什么是两阶段提交
两阶段提交又称2PC,2PC是一个非常经典的强一致、中心化的原子 提交协议。这里所说的中心化是指协议中有两类节点:一个是中心 化 协调者节点 (coordinator)和 N个参与者节点 (partcipant)。
两个阶段:第一阶段:投票阶段 和第二阶段:提交/执行阶段。
2PC例子
A组织B、C和D三个人去爬山:如果所有人都同意去爬山,那么活动 将举行;如果有一人不同意去爬山,那么活动将取消。
首先A将成为该活动的协调者,B、C和D将成为该活动的参与者。
具体流程:
阶段1:
①A发邮件给B、C和D,提出下周三去爬山,问是否同意。 那么此时A需要等待B、C和D的邮件。
②B、C和D分别查看自己的日程安排表。B、C发现自己在 当日没有活动安排,则发邮件告诉A它们同意下周三去爬山。由 于某种原因, D白天没有查看邮件。那么此时A、B和C均需要等 待。到晚上的时候,D发现了A的邮件,然后查看日程安排,发 现周三当天已经有别的安排,那么D回复A说活动取消吧。
阶段2:
①此时A收到了所有活动参与者的邮件,并且A发现D下周 三不能去爬山。那么A将发邮件通知B、C和D,下周三爬山活动 取消。
②此时B、C回复A“太可惜了”,D回复A“不好意思”。至此该 事务终止。
2PC阶段处理流程
举例订单服务A,需要调用支付服务B去支付,支付成功则处理购物 订单为待发货状态,否则就需要将购物订单处理为失败状态。
第一阶段:投票阶段
第一阶段分三步:
**事物询问:**协调者 向所有的 参与者 发送事务预处理请求,称之为Prepare,并开始等待各参 与者的响应。
**执行本地事物:**各个 参与者节点执行本地事务操作,但在执行完成后并不会真正提交数据库本 地事务,而是先向协调者报告说:“我这边可以处理了/我这边不能处理”。.
**各个参与者向协调者反馈事物询问的响应:**如果参与者成功执行了事务操作,那么就反馈给协 调者Yes响应,表示事务可以执行,如果没有参与者成功执行事务,那么就反馈给协调者No响应, 表示事务不可以执行。第一阶段执行完后,会有两种可能。1、所有都返回Yes. 2、有一个或 者多个返回No。
第二阶段:提交/执行阶段(成功流程)
成功条件 :所有参与者都返回Yes。
总结:
二阶段提交(2PC):第一阶段发起询问投票。第二阶段返回结果。两个阶段的某一个处理发生异常。都会回滚。
XA模式实现分布式事务
下载启动Seata服务
下载seata服务器
下载地址 :https://github.com/seata/seata/releases
配置分布图
修改配置文件
修改config目录下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: /usr/local/seate/logs/
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:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
application: seate-server
server-addr: 127.0.0.1:8848
namespace:
group: DEFAULT_GROUP
data-id: seataServer.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: DEFAULT_GROUP
namespace:
cluster: SZ
# store:
# support: file 、 db 、 redis
# mode: file
# 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
在Nacos添加配置
特别注意,为了让服务的集群可以共享配置,我们选择了nacos 作为统一配置中心。因此服务端配置文件下seataServer.properties 文件需要在nacos中配好。
新建配置
seataServer.properties的内容
#这里的地址需要配置成seata所在服务器的地址
service.default.grouplist=192.168.11.110:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#此处对于数据存储使用的是数据库存储所以需要配置数据库的连接信息
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
#数据库驱动如果是mysql8使用这个,否则使用com.mysql.jdbc.Driver
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://192.168.11.110:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
store.db.minConn=5
store.db.maxConn=30
#此处有四张表的配置,所以需要在数据库中执行对应的SQL创建表
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#Transaction rule configuration, only for the server
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
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
#Transaction rule configuration, only for the client
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=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
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
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
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
transport.serialization=seata
transport.compressor=none
创建全局事务表和分支事务表
-------------------------------- 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