jdk版本采用8与11
一.事务理论回顾
1.什么是数据库事务?
- 数据库事务:数据库事务是指一个逻辑工作单元中执行的一系列操作,要么完全地执行,要么完全地不执行
- 事务中的步骤全部成功执行,提交事务
- 其中一个是被,那么将会发生回滚操作,并且撤销之前所有的操作
- 事务内的语句:要去全部成功,要么全部失败
2.事务的ACID特性回顾
- 原子性:事务是一个不可分割的执行单元,事务中包括的诸多操作要么都做,要么都不做
- 一致性:事务是指从一个一致性状态变到另一个一致性状态,事务的中间状态不能被观察到的
- 隔离性:一个事务的执行不能被其他事务干扰[事务并发],故有事务的隔离级别
- 持久性:事务一旦提交,它对数据库中数据的改变就应该是永久性的
3.事务的分类
- 本地事务:大多数情况下,一个应用连接一个数据库就能完成的事务我们称之为本地事务
- 分布式事务:下面的内容会讲解到
4.事务的管理
- 现在很多应用都整合了Spring,它提供了PlatformTransactionManager接口来进行事务管理相关操作
- .DataSourceTransactionManager:基于javax.sql.DataSource数据源的事务管理,其内部也是通过操作
java.sql.Connection
来开启、提交和回滚事务【JDBC事务】 - JtaTransactionManager:可以支持分布式事务
5.什么是分布式事务
- 现在绝大部分公司在技术架构上都采用了服务化和数据库拆分,在这种情况下,完成某一个业务功能可能需要横跨多个服务,操作多个数据库,这就涉及到到了分布式事务
场景一:
场景二:
场景三:
- 由这样的多个分支事务构成的一个全局事务我们一般叫分布式事务
- 对于分布式事务,原有的事务管理方式并不适用
二.CPA定理
1.什么是CAP定理
CAP 定理又被称作布鲁尔定理,在一个分布式系统(指互相连接并共享数据的节点的集合)中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个
2.关于一致性
- 对于给定的客户端,读操作保证返回最近的写操作结果(理论上写操作要等到数据同步完成才算写成功)
2.关于可用性
- 非故障节点将在合理的时间内返回合理的响应(没有错误或超时)
- 合理响应并不一定是正确响应
3.关于分区容错性
- 当分布式系统的网络发生分区时,系统将继续运行
4.CAP理论上如何选择
- 虽然
CAP 理论定义中是三个要素中能取两个
,但分布式环境下我们发现P(分区容忍)要素是必须要满足的,因为网络本身无法做到 100% 可靠,我们的系统要允许网络分区的出现 - 一旦出现网络分区之后,C和A是无法同时满足的,所以理论上是在AP 和 CP中做选择
5.CAP实践中的选择
- 虽然理论上是在AP 和 CP中做选择,但并不意味着我们要放弃另一个
- 在实践中选择了AP不代表放弃了C,不一致只是出现在发生网络分区时,网络分区恢复后最终需要保证一致
- 在实践中选择了CP也不代表放弃了A,比如:写入操作只需过半确认,未确认的节点依然能提供读能力
- CP是忽略延时的,而实际应用中延时是无法避免的
- 落地实践时我们需要将系统内的数据按照不同的应用场景和要求进行分类,每类数据选择不同的策略【比如nacos】
6.选择CP
- 系统保证强一致性
- 一般需要引入一致性共识算法
- 比如Paxos ,raft
- .一般系统中会选举一个leader,由它进行写操作,写操作在过半节点同步成功后响应成功
7.选择AP
- 优先保证可用性
- 网络分区恢复后,最终一致即可
三.BASE理论
1.什么是BASE理论
- BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency),是基于CAP定理演化而来
基本可用
:分布式系统在出现故障时,允许损失部分可用性,即保证核心可用
软状态
:允许系统存在中间状态[数据不一致],而该中间状态不会影响系统整体正确性
最终一致性
系统中的所有数据经过一定时间后,最终能够达到一致的状态- BASE 理论本质上是对 CAP 中 AP 方案的一个补充
2.刚柔事务
- 刚性事务:满足ACID理论
- 柔性事务:满足BASE理论
- 柔性事务并不是完全放弃ACID,而是通过放宽一致性要求,借助本地事务来实现最终分布式事务一致性的同时也保证系统的吞吐
四.2PC与3PC
1.X/Open 分布式事务处理模型(DTP)包括大量控制如何处理分布式事务的相关组件
应用程序 (AP)
:定义事务边界以及那些组成事务的操作事务管理器 (TM)
:标识指定事务,监视它们的进度并对事务的完成和失败负有责任资源管理器 (RM)
:提供对诸如数据库之类的共享资源的访问
参考阅读文档:https://www.ibm.com/docs/zh/db2/10.5?topic=managers-designing-xa-compliant-transaction
2.X/Open XA协议:XA规范是开放群组关于分布式事务处理 (DTP)的规范。规范描述了全局的事务管理器与局部的资源管理器之间的接口
参考阅读文档:https://zh.wikipedia.org/wiki/X/Open_XA
3.常见的分布式事务解决方案
- 较经典的是XA分布式事务协议,XA协议包含二阶段提交(2PC)和三阶段提交(3PC)两种实现
- 基于TCC、本地消息表、MQ事务消息等相关的实现
4.2PC两阶段提交:将事务的提交过程分为两个阶段来进行处理:准备阶段和提交阶段
准备阶段:
1.协调者发送事务请求到参与者
2.参与者完成分支事务操作,记录事务日志,但不提交
3.分支事务操作执行结果反馈给协调者
注意:第一阶段的结果将决定第二阶段的行为
- 第一阶段各分支事务成功执行并在超时时间内反馈结果,则第二阶段进行全局事务的提交
- 第一阶段某分支事务执行失败或者超时未反馈结果,则第二阶段进行全局事务的回滚
5.2PC存在的问题
- 性能问题:所有参与者分支事务在二阶段结束前需要一直持有相关事务资源(比如:锁),容易有性能瓶颈
- 可靠性问题:存在全局事务发起者(协调者)单点问题,一旦它出现问题会导致分支事务一直处于等待状态,也影响性能
- 万恶之源网络问题:如果发生网络问题,第二阶段部分参与者没有收到commit/rollback指令,造成不一致
6.3PC三阶段提交:三阶段提交是基于二阶段提交改进而来的,并且引入双向超时机制,分成:CanCommit,PreCommit,doCommit三个阶段
第一阶段
1.协调者发送canCommit,询问是否可以进行事务操作,各参与者可进行资源预检
2.资源预检结束后反馈结果
注意:第一阶段的结果将决定第二阶段的行为
- 第一阶段各分支事务canCommit成功且在超时时间内返回,则第二阶段执行PreCommit
第二阶段
1.各分支事务执行事务操作,记录事务日志,但并不提交
2.分支事务操作执行结果反馈给协调者
注意:第二阶段PreCommit的结果将决定第三阶段的行为
- 第一阶段各分支事务canCommit失败、或者超时时间内未返回、或者参与者超时未收到下一步指令,则第二阶段执行中断操作并结束
- 第二阶段各分支事务执行成功并在超时时间内返回,或者参与者超时未收到下一步指令,则第三阶段执行doCommit操作
第三阶段
1.各分支事务提交并回馈结果,释放资源
- 第二阶段某分支事务执行失败、或超时未返回,则第三阶段执行中断操作【回滚】
7.3PC问题分析
- 性能问题:引入的双向超时机制能一定程度缩短资源的持有时间
- 可靠性问题:发起者单点故障后,双向超时机制会让参与者有进行下一步动作的能力,不会一直等待
- 万恶之源网络问题:当在参与者收到preCommit请求执行成功后等待do commite指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成不一致
8.本地消息表解决方案
- 最初是由ebay提出,核心思路是将分布式事务拆分成本地事务进行处理
- 方案需要引入额外的事务消息表
8.本地消息表方案优劣分析
- 优势:方案轻量,容易实现
- 弊端:与具体的业务场景绑定,耦合性强,不可共用;事务消息数据与业务数据同库,占用业务系统资源
9.MQ事务消息解决方案
- MQ事务消息解决方案其实是对本地消息表的封装,将原本存于DB的事务消息存到MQ,其他方面与本地消息表基本一致
- 典型代表:RocketMQ
官方参考文档:https://rocketmq.apache.org/zh/docs/featureBehavior/04transactionmessage/
10.基于普通消息方案:一致性保障困难
该方案中消息下游分支和订单系统变更的主分支很容易出现不一致的现象
- 消息发送成功,订单没有执行成功,需要回滚整个事务
- 订单执行成功,消息没有发送成功,需要额外补偿才能发现不一致
- 消息发送超时未知,此时无法判断需要回滚订单还是提交订单变更
10.基于Apache RocketMQ分布式事务消息:支持最终一致性
基于Apache RocketMQ实现的分布式事务消息功能,在普通消息基础上,支持二阶段的提交能力。将二阶段提交和本地事务绑定,实现全局提交结果的一致性
11.TCC型分布式事务解决方案
参考文档:https://www.atomikos.com/Blog/TCCForTransactionManagementAcrossMicroservices
github:https://github.com/changmingxie/tcc-transaction
github:https://github.com/prontera/spring-cloud-rest-tcc
五.Seata简介
1.什么是Seata:Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
- 官方中文网:https://seata.io/zh-cn
- github项目地址:https://github.com/seata/seata
- springCloudAlibaba下使用:https://github.com/alibaba/spring-cloud-alibaba/tree/2.2.9.RELEASE
2.Seata中的术语
- TM (Transaction Manager) - 事务管理器,定义全局事务的范围:开始全局事务、提交或回滚全局事务
- TC (Transaction Coordinator) - 事务协调者,维护全局和分支事务的状态,驱动全局事务提交或回滚
- RM (Resource Manager) - 资源管理器,管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
3.Seata的工作模式
- Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,打造了一站式的分布式解决方案
- 参考文档:https://seata.io/zh-cn/docs/dev/mode/at-mode.h
- Seata 的AT模式
- 参考阅读:https://seata.io/zh-cn/docs/dev/mode/at-mode.html
- Seata AT模式的条件
- 基于支持本地 ACID 事务的关系型数据库
- Java 应用,通过 JDBC 访问数据库
- Seata AT模式的工作机制
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资
源 - 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资
六.Seata部署安装
1.Seata Server部署安装
- .Seata Server也就是TC角色,作为一个独立的组件需要单独安装部署,TM和RM(Client端)由业务系统集成
- Server支持多种方式部署:直接部署,使用 Docker, 使用 Docker-Compose, 使用 Kubernetes, 使用 Helm
- 可参考官方文档:https://seata.io/zh-cn/docs/ops/deploy-server.html
2.版本说明(红色框为博主采用的版本)
3.下载地址:https://github.com/seata/seata/releases/tag/v1.5.2
4.下载后上传服务器进行解压 tar -zxvf seata-server-1.5.2.tar.gz
5.直接启动【了解】
cd /seata/bin
sh seata-server.sh
6.启动 seata-server 并以服务的形式注册到注册中心【生产推荐】
- seata 支持多种注册中心,我们选择 nacos
参考:https://seata.io/zh-cn/docs/user/registry/nacos.html
# 1.找到seata-server的启动配置文件 application.yml
cd /seata/seata/conf
# 2.编辑 application.yml 添加注册模块信息 ,修改 seata.registry
seata:
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server #注册的服务名
serverAddr: 192.168.44.128:8848 #注册的服务ip+端口
group: Andy-SEATA-GROUP #注册的服务组名
namespace: ba05dfd3-7f76-42dc-a9bf-52cae38df5d5 #注册服务的命名空间
cluster: CS1 #集群名称,默认default
username: nacos #nacos用户名
password: nacos #nacos密码
7.然后启动 seata-server
cd /seata/bin
sh seata-server.sh
启动参数可参考文档:https://seata.io/zh-cn/docs/ops/deploy-server.html
查看日志如果出现下图错误,则为jvm内存不够,jdk版本问题
jvm内存不够解决方式
方式一:重新找一台内存大的主机
方式二:修改配置参数。window版本为bin/seata-server.bat 文件。linux版本为bin/seata-server.sh文件
jdk版本问题,将window版本为bin/seata-server.bat 文件。linux版本为bin/seata-server.sh文件中的
第868,870,871,872行注释掉
启动后可去 nacos 控制台寻找 seata-server 服务
查看seata控制台
地址:自己的ip:7091
用户名:seata
密码:seata
七.Seata AT模式案例
需求清单
基于Seata的AT模式,完成Andy-order,Andy-driver服务的事务控制(这两个项目在博主的SpringCloudAlibaba----Nacos中有提到)
1.在Andy-api模块中的pom.xml文件中添加坐标
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
2.AT模式在第一阶段会生成 UNDO_LOG 日志插入到对应表中,且和本地业务是在同一个事务中完成,所以需要为每个分支事务涉及到的数据库准备undo_log 表
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';
3.配置客户端事务组
1.应用程序【客户端,以SpringBoot为例】配置一个 seata.tx-servicegroup=分组名称
2.然后通过 seata.service.vgroupMapping.分组名称 去获取一个TC集群的名称
3.拿到集群名称再通过一定规则去构造服务名称,就可以去注册中心获取真实的TC服务列表【不同注册中心服务名的构造方式不一样,前提是seata完成了注册,配置项 seata.registry.type=支持的注册中心类型 】
如果不从注册中心获取TC服务列表,那就需要直接配置TC服务列表,此时配置项 seata.registry.type=file (默认)
手动配置服务列表通过 seata.service.grouplist.集群名称=TC服务列表如下
seata:
tx-service-group: Andy_tx_group #事务分组名称
service:
vgroup-mapping:
default_tx_group: default # 分组对应的集群名称,可通过该名称构造服务名称去注册中心获取TC地址列表
grouplist: #不走注册中心,本地直接配置地址列表
default: 127.0.0.1:8091 # 集群对应的地址列表
这样做的好处是什么
多了一层事务分组到映射集群的配置,事务分组可以作为资源的逻辑隔离单位,出现某集群故障时可以快速failover,只切换对应分组,可以把故障缩减到服务级别,但前提也是你有足够server集群
4.在nacos中添加配置
客户端需要的相关配置项可参考:https://github.com/seata/seata/blob/1.5.2/script/client/spring/application.yml
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: Andy_tx_group #事务分组名称
access-key: aliyunAccessKey
secret-key: aliyunSecretKey
enable-auto-data-source-proxy: true
data-source-proxy-mode: AT
use-jdk-proxy: false
scan-packages: firstPackage,secondPackage
excludes-for-scanning: firstBeanNameForExclude,secondBeanNameForExclude
excludes-for-auto-proxying: firstClassNameForExclude,secondClassNameForExclude
client:
rm:
async-commit-buffer-limit: 10000
report-retry-count: 5
table-meta-check-enable: false
report-success-enable: false
saga-branch-register-enable: false
saga-json-parser: fastjson
saga-retry-persist-mode-update: false
saga-compensate-persist-mode-update: false
tcc-action-interceptor-order: -2147482648 #Ordered.HIGHEST_PRECEDENCE + 1000
sql-parser-type: druid
lock:
retry-interval: 10
retry-times: 30
retry-policy-branch-rollback-on-conflict: true
tm:
commit-retry-count: 5
rollback-retry-count: 5
default-global-transaction-timeout: 60000
degrade-check: false
degrade-check-period: 2000
degrade-check-allow-times: 10
interceptor-order: -2147482648 #Ordered.HIGHEST_PRECEDENCE + 1000
undo:
data-validation: true
log-serialization: jackson
log-table: undo_log
only-care-update-columns: true
compress:
enable: true
type: zip
threshold: 64k
load-balance:
type: RandomLoadBalance
virtual-nodes: 10
service:
vgroup-mapping:
Andy_tx_group: CS1 #去nacos中查找的集群名称
# grouplist:
# default: 127.0.0.1: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-tm-client-batch-send-request: false
enable-rm-client-batch-send-request: true
rpc-rm-request-timeout: 30000
rpc-tm-request-timeout: 30000
registry:
type: nacos
nacos:
application: seata-server
server-addr: 192.168.44.128:8848
group : "Andy-SEATA-GROUP"
namespace: "ba05dfd3-7f76-42dc-a9bf-52cae38df5d5"
username: "nacos"
password: "nacos"
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key: ""
#secret-key: ""
##if use Nacos naming meta-data for SLB service registry, specify nacos address pattern rules here
#slbPattern = ""
log:
exception-rate: 100
tcc:
fence:
log-table-name: tcc_fence_log
clean-period: 1h
saga:
enabled: false
state-machine:
table-prefix: seata_
enable-async: false
async-thread-pool:
core-pool-size: 1
max-pool-size: 20
keep-alive-time: 60
trans-operation-timeout: 1800000
service-invoke-timeout: 300000
auto-register-resources: true
resources:
- classpath*:seata/saga/statelang/**/*.json
default-tenant-id: 000001
charset: UTF-8
5.在项目的配置文件中加载nacos中的配置文件,并且在启动类上添加@EnableAutoDataSourceProxy,最后在需要事务的方法上添加 @GlobalTransactional 注解即可
6.手动制造一个异常,启动启动项目,我们可以发现,数据没有进行修改
7.注意:关于使用feign降级功能导致seata事务无法回滚的问题请看
https://github.com/seata/seata/issues/2088
八.Seata高可用
1.seata-server 目前使用的是一个单节点,能否抗住高并发是一个值得思考的问题。生产环境项目几乎都需要确保能扛高并发、具备高可用的能力,因此生产环境项目一般都会做集群保证高可用
2.如果要想实现高可用,就需要做一些动作来保证集群节点间数据的共享,比如:全局的事务信息,分支事务信息,全局锁信息等,目前seata支持的数据共享的方式有以下三种:
- file【集群不可用】
- redis
- db
可以参考:https://seata.io/zh-cn/docs/ops/deploy-ha.html
这里博主采用的是mysql方式
3.在当前seata节点上mysql数据库节点添加一个seata的库名
4.添加对应的表结构,sql脚本在seata/script/server/db目录下
5.编辑 seata/conf/application.yml 配置文件,配置store
cd seata/conf
seata:
store:
# support: file 、 db 、 redis
mode: db
db:
datasource: druid
dbType: mysql
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.44.128:3306/seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
user: root
password: root
redis:
host: 127.0.0.1
port: 6379
maxConn: 100
minConn: 1
database: 0
#password:
queryLimit: 100
6.重新启动并查看是否在nacos中完成注册
cd /seata/bin
sh seata-server.sh
4.将当前seata项目完整的复制到其他机器上,启动并查看是否在nacos中完成注册
如果还想加入更多节点,按次步骤操作即可!!!
5.重新启动本地服务,再次测试事务是否可以控制得住!!!