目录
一 CAP定理
1.1 一致性(Consistency)
Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致;在分布式系统中,一个服务代码部署到了两台服务器上,客户端访问这两个服务器的同一个数据时,得到的数据应该是一样的。
1.2 可用性(Availability)
Availability (可用性):用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝 。
1.3 分区容错
Partition(分区):因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区。
Tolerance(容错):在集群出现分区时,整个系统也要持续对外提供服务。
1.4 CAP定理内容
分布式系统节点通过网络连接,一定会出现分区问题(P) 当分区出现时,系统的一致性(C)和可用性(A)就无法同时满足
思考:elasticsearch集群是CP还是AP? ES集群出现分区时,故障节点会被剔除集群,数据分片会重新分配到其它节点,保证数据一致。因此是低可用性,高一致性,属于CP
二 BASE理论
BASE理论是对CAP的一种解决思路,包含三个思想:
- Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
- Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
- Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
而分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论:
- AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
- CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。
三 Seata
3.1 Seata的组成
Seata事务管理中有三个重要的角色:
- TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
3.2 Seata的几种分布式解决方案
- XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
- TCC模式:最终一致的分阶段事务模式,有业务侵入
- AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
- SAGA模式:长事务模式,有业务侵入
3.3 部署seata服务
进入页面下载中心,然后找对应的版本下载,这里下载的是1.4.2的binary,也可以下载源码看看;
下载完成后,解压;
修改conf目录下的registry.conf文件:
# tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等
type = "nacos"
nacos {
# seata tc 服务注册到 nacos的服务名称,可以自定义
application = "seata-tc-server"
serverAddr = "127.0.0.1:8848"
group = "DEFAULT_GROUP"
namespace = ""
# 必须制定这个集群,且在项目的配置文件中也必须指定,否则无法连接到seata服务
cluster = "HZ"
username = "nacos"
password = "nacos"
}
}
config {
# 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
type = "nacos"
# 配置nacos地址等信息
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
特别注意,为了让tc服务的集群可以共享配置,我们选择了nacos作为统一配置中心。因此服务端配置文件seataServer.properties文件需要在nacos中配好。
# 数据存储方式,db代表数据库,注意要配置自己的数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
# 事务、日志等配置
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.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
对应的数据库表创建sql:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- 分支事务表
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` tinyint(4) NULL DEFAULT NULL,
`client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime(6) NULL DEFAULT NULL,
`gmt_modified` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- 全局事务表
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`timeout` int(11) NULL DEFAULT NULL,
`begin_time` bigint(20) NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`xid`) USING BTREE,
INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
最后,进入之前解压的bin目录,运行其中的seata-server.bat(window系统),seata-server.sh(mac)即可;然后访问nacos页面,就会找到这个服务。
3.4 微服务集成seata
3.4.1 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--版本较低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<!--seata starter 采用1.4.2版本-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
3.4.2 修改配置文件
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
# 参考tc服务自己的registry.conf中的配置
type: nacos
nacos: # tc
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-tc-server # tc服务在nacos中的服务名称
cluster: SH
tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
service:
vgroup-mapping: # 事务组与TC服务cluster的映射关系
seata-demo: HZ
namespace:命名空间,就是nacos服务列表下的public或者test;默认就是public。
group:分组;每一个服务对应属于的群组;默认是DEFAULT_GROUP。
service:服务的名称。
cluster:集群,服务属于哪一个集群。通过vgroup-mapping进行映射。
四:seata各模式学习
4.1 XA模式
XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。基于数据库本身的特性实现的;并没有用到undolog。
第一阶段下,各资源管理器和事物管理器进行准备(各个服务进行注册);例如,A服务下a方法是入口,里面调用了B服务和C服务的方法,先调用B服务,B服务的代码没有问题,正常执行完毕,就会进入就绪状态,等待其他服务都就绪;
第二阶段下,事物管理器收到所有的资源管理器都就绪的信号后,就会提醒各资源管理器进行提交。
假如在第一阶段中,有任何一个资源管理器失败了,事物管理器就会通知之前成功的服务进行回滚操作。
去掉TM就和上面的流程差不多一样了,1.4执行完后,事物并没有提及(数据库的数据还没变动),等所有分支都提交,确认状态。因为事物并没有提交,所以各服务都在占用数据库的资源,资源浪费。但是没有代码入侵,只依赖数据库就能实现ACID,且大部分数据库都支持。
4.2 AT模式
AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。区别就是有一个undolog用来记录之前的数据快照,方便进行数据回滚。并且XA模式执行完业务后不会提交sql,但是AT会。
undolog中的数据会根据提交还是回滚这两种操作去执行对应的逻辑。最后都会删除。
4.3 XA和AT的区别总结
- XA模式一阶段不提交事务,锁定资源(数据库资源有浪费);AT模式一阶段直接提交,不锁定资源(会读取到脏数据???)。
- XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
- XA模式强一致(因为XA会占据数据库资源,不提交);AT模式最终一致(先提交,后根据具体各服务具体状态进行操作,保证最终一直性)
4.4 AT模式脏写问题
AT模式,在分布式事物进行过程中,锁的是表中某一行的数据,对数据库资源进行了部分释放,算是对XA的优化;但是会出现脏写现象;
XA模式,在事物进行过程中,不会释放db锁(锁的是表),
如上图中,首先事物1开始执行,获取到了DB锁,将money从100改成了90,且保存了快照数据,然后会提交事物(较于XA的不同处),数据库值已经变成90了;如果这时有其他的事物想要回去此表的DB锁,是可以获取成功的;若没有其他事物见缝插针,这时事物失败了是可以按照之前的快照进行数据回滚,将monry改成100,但是假如这个事物1释放DB锁,且在事物1完整执行完业务代码的这段时间段中,有另一个事物(分布式或者普通的事物)将monry改成了80,且提交了事物,如果事物1这时回滚了,会按照之前的数据快照100去修改数据;脏写问题;
所以,后面为了解决这种问题,引入了全局锁,但是这种只能解决分布式事物这种情况,没法解决普通事物,因为普通事物并没有注册在全局锁的表中;
事物2也是分布式锁,事物1在修改完成后,会提交事物并且释放DB锁,这时由于DB锁是释放的状态,事物2就可以获取DB锁,修改数据,尝试获取全局锁,这时,因为全局锁已被事物1占住,事物2无法获取全局锁,但是事物2却在占据DB锁,这时,事物1如果后续逻辑执行失败,就需要回滚事物1的数据,需要拿到DB锁去回滚,这时,就会形成死锁;
事物2在获取全局锁的时候,有重试机制,重试300毫秒,这个时间要小于DB锁的等待时间,所以,事物2的会先释放掉,80也就没改成功,事物1就会拿到DB锁,然后去回滚数据。
这种和XA的有点像,XA是执行完业务不会释放DB锁,任何人都不能对这条数据进行增删改查;但是上图锁的是TC锁,锁的是seata的事物,如果是普通的事物,是可以修改成功的
上图是,假如事物2是普通的事物,它在事物1释放DB锁的间隙中,去执行了一个修改money为80的业务代码,然后这时事物1要去回滚数据,就会出现事物2数据丢失的现象;这时候就需要借助seata中的前快照和后快照去判断,事物1是否能进行回滚操作(利用修改后的快照进行判断,修改前的快照进行回滚),如果真出现这种异常,就需要人工介入。
4.5 TCC
TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:
- Try:资源的检测和预留;
- Confirm:完成资源操作业务,事物提交;要求 Try 成功 Confirm 一定要能成功。
- Cancel:预留资源释放,可以理解为try的反向操作。回滚预留资源。
4.5.1 空回滚及业务悬挂
以后继续执行,指的是阻塞解开了,业务继续走try,这时,就不能继续执行了,并且不能执行confirm。
4.6 seata高可用
4.6.1 配置
将之前的seata目录直接复制一份(cp -r seata seata2),改名seata2,然后将seata2中的registry.conf中的cluster = "HZ"改成cluster = "HN"就行了;启动命令:seata-server.sh -p 8092,改用8092端口,和之前的8091区分开。
nacos寻找服务流程:先找命名空间,这个默认的都是public,然后找群组,在找服务名,然后去找服务详情中的集群名称。
实现热部署,避免HZ挂掉了,还需要手动更改配置文件中的mapping 映射,重启服务。
4.6.2 热部署
在nacos上配置一个client.properties的文件,群组为SEATA_GROUP,内容:
# 事务组映射关系,后续HZ挂了,可以将这里改成HN,实现热部署
service.vgroupMapping.seata-demo=HZ
# 下面都是一些固定配置
service.enableDegrade=false
service.disableGlobalTransaction=false
# 与TC服务的通信配置
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
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
# RM配置
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=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
# TM配置
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
# undo日志配置
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
client.log.exceptionRate=100
然后,在代码的配置文件中的seata下添加:
# 实现seata高可用,指向nacos配置文件中的client.properties文件,实现热部署
# 实现热部署,就不需要配置上面的vgroup-mapping
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
group: SEATA_GROUP
data-id: client.properties
#这个之前配置的东西就可以注释了
# service:
# vgroup-mapping: # 事务组与TC服务cluster的映射关系
# seata-demo: HZ