分布式事务:分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
例如:电商系统中的订单系统与库存系统
这时候就需要保证事务的一致性了,单数据源的用单机事务来保证。多数据源就需要依赖分布式事务来处理。
XA的两阶段提交方案
XA协议:
---XA 协议由 Oracle Tuxedo 首先提出的,并交给 X/Open 组织,作为资源管理器(数据库)与事务管理器的接口标准。目前,Oracle、Informix、DB2 和 Sybase 等各大数据库厂家都提供对 XA 的支持。XA 协议采用两阶段提交方式来管理分布式事务。XA 接口提供资源管理器与事务管理器之间进行通信的标准接口。
---XA 就是 X/Open DTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。XA 接口函数由数据库厂商提供。
---X/Open 组织(即现在的 Open Group)定义了分布式事务处理模型。X/Open DTP 模型(1994)包括应用程序(AP)、事务管理器(TM)、资源管理器(RM)、通信资源管理器(CRM)四部分。一般,常见的事务管理器(TM)是交易中间件,常见的资源管理器(RM)是数据库,常见的通信资源管理器(CRM)是消息中间件。
XA协议的一阶段提交:
一阶段提交协议相对简单。优点也很直观,它不用再与其他的对象交互,节省了判断步骤和时间,所以在性能上是在阶段提交协议中最好的。但缺点也很明显:数据库确认执行事务的时间较长,出问题的可能性就随之增大。如果有多个数据源,一阶段提交协议无法协调他们之间的关系。
XA协议的二阶段提交:
二阶段提交协议分为两个阶段:
应用程序调用了事务管理器的提交方法,此后第一阶段分为两个步骤:
---事务管理器通知参与该事务的各个资源管理器,通知他们开始准备事务。
---资源管理器接收到消息后开始准备阶段,写好事务日志并执行事务,但不提交,然后将是否就绪的消息返回给事务管理器(此时已经将事务的大部分事情做完,以后的内容耗时小)。
第二阶段也分为两个步骤:
---事务管理器在接受各个消息后,开始分析,如果有任意其一失败,则发送回滚命令,否则发送提交命令。
---各个资源管理器接收到命令后,执行(耗时很少),并将提交消息返回给事务管理器。事务管理器接受消息后,事务结束,应用程序继续执行。
分为两个步骤的原因:一是因为分两步,就有了事务管理器统一管理的机会;二尽可能晚地提交事务,让事务在提交前尽可能地完成所有能完成的工作,这样,最后的提交阶段将是耗时极短,耗时极短意味着操作失败的可能性也就降低。同时,二阶段提交协议为了保证事务的一致性,不管是事务管理器还是各个资源管理器,每执行一步操作,都会记录日志,为出现故障后的恢复准备依据。
缺点:
---二阶段提交协议的存在的弊端是阻塞,因为事务管理器要收集各个资源管理器的响应消息,如果其中一个或多个一直不返回消息,则事务管理器一直等待,应用程序也被阻塞,甚至可能永久阻塞。
---两阶段提交理论的一个广泛工业应用是 XA 协议。目前几乎所有收费的商业数据库都支持 XA 协议。XA 协议已在业界成熟运行数十年,但目前它在互联网海量流量的应用场景中,吞吐量这个瓶颈变得十分致命,因此很少被用到。
TCC解决方案:
TCC 是由支付宝架构师提供的一种柔性解决分布式事务解决方案,主要包括三个步骤
---Try:预留业务资源/数据效验
---Confirm:确认执行业务操作
---Cancel:取消执行业务操作
TCC原理:TCC 方案在电商、金融领域落地较多。TCC 方案其实是两阶段提交的一种改进。其将整个业务逻辑的每个分支显式的分成了 Try、Confirm、Cancel 三个操作。Try 部分完成业务的准备工作,confirm 部分完成业务的提交,cancel 部分完成事务的回滚。基本原理如下图所示。
事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的 try 接口,完成一阶段准备。之后事务协调器会根据 try 接口返回情况,决定调用 confirm接口或者 cancel 接口。如果接口调用失败,会进行重试。
微服务倡导服务的轻量化、易部署,而 TCC 方案中很多事务的处理逻辑需要应用自己编码实现,复杂且开发量大。
TCC的关键流程:
TCC优点:让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能。
TCC缺点:对应用的侵入性强。业务逻辑的每个分支都需要实现 try、confirm、cancel三个操作,应用侵入性较强,改造成本高。实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm 和 cancel 接口必须实现幂等。
分布式事务中间件解决方案:分布式事务中间件其本身并不创建事务,而是基于对本地事务的协调从而达到事务一致性的效果。典型代表有:阿里的GTS,开源框架LCN。
其实现原理如下:
LCN框架:LCN并不生产事务,LCN只是事务的协调工。
在设计框架之初的 1.0 ~ 2.0 的版本时,框架设计的步骤是如下的,各取其首字母得来的 LCN 命名。
锁定事务单元(lock)、确认事务模块状态(confirm)、通知事务(notify)
tx-lcn 官方地址:https://www.txlcn.org/
tx-lcn Github 地址:codingapi/tx-lcn
tx-lcn 服务下载地址:https://pan.baidu.com/s/1cLKAeE#list/path=%2F
tx-lcn 服务源码地址:https://github.com/codingapi/tx-lcn/tree/master/tx-manager
LCN框架执行原理:
LCN执行步骤:
---创建事务组:事务组是指的我们在整个事务过程中把各个节点(微服务)单元的事务信息存储在一个固定单元里。但这个信息并不是代表是事务信息,而是只是作为一个模块的标示信息。创建事务组是指在事务发起方开始执行业务代码之前先调用 TxManager 创建事务组对象,然后拿到事务标示 GroupId 的过程。
---添加事务组:添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息添加通知给TxManager 的操作。
---关闭事务组:是指在发起方执行完业务代码以后,将发起方执行结果状态通知TxManager 的动作。当执行完关闭事务组的方法以后,TxManager 将根据事务组信息来通知相应的参与模块提交或回滚事务。
业务执行流程图:
LCN的事务协调机制:
如图:假设服务已经执行到关闭事务组的过程,那么接下来作为一个模块执行通知给TxManager,然后告诉他本次事务已经完成。那么如图中 Txmanager 下一个动作就是通过事务组的 id,获取到本次事务组的事务信息;然后查看一下对应有那几个模块参与,然后如果是有 A/B/C 三个模块;那么对应的对三个模块做通知、提交、回滚。
提交的时候是提交给了TxClient 模块。然后 TxCliient 模块下有一个连接池,就是框架自定义的一个连接池(如图 DB 连接池);这个连接池其实就是在没有通知事务之前一直占有着这次事务的连接资源,就是没有释放。但是他在切面里面执行了 close 方法。在执行 close的时候。如果需要(TxManager)分布式事务框架的连接。他被叫做“假关闭”,也就是没有关闭,只是在执行了一次关闭方法。实际的资源是没有释放的。这个资源是掌握在 LCN 的连接池里的。
当TxManager通知提交或者事务回滚的时候TxManager 会通知我们的 TxClient 端。然后 TxClient 会去执行相应的提交或回滚。提交或回滚之后再去关闭连接。这就是 LCN 的事务协调机制。说白了就是代理 DataSource 的机制;相当于是拦截了一下连接池,控制了连接池的事务提交。
LCN的事务补偿机制:
---LCN 的补偿事务原理是模拟上次失败事务的请求,然后传递给 TxClient 模块然后再次执行该次请求事务。简单的说:lcn 事务补偿是指在服务挂机和网络抖动情况下 txManager 无法通知事务单元时。(通知不到也就两种原因服务挂了和网络出问题)在这种情况下 TxManager 会做一个标示;然后返回给发起方。告诉他本次事务有存在没有通知到的情况。那么如果是接收到这个信息之后呢,发起方就会做一个标示,标示本次事务是需要补偿事务的。这就是事务补偿机制。
事务补偿是指在执行某个业务方法时,本应该执行成功的操作却因为服务器挂机或者网络抖动等问题导致事务没有正常提交,此种场景就需要通过补偿来完成事务,从而达到事务的一致性。
当执行关闭事务组步骤时,若发起方接受到失败的状态后将会把该次事务识别为待补偿事务,然后发起方将该次事务数据异步通知给 TxManager。TxManager 接受到补偿事务以后先通知补偿回调地址,然后再根据是否开启自动补偿事务状态来补偿或保存该次切面事务数据。
案例:
需求:创建三个服务分别为:springcloud-portal、springcloud-order、springcloud-inventory。在springcloud-portal 服务中处理创建订单的请求,然后分别请求 springcloud-order 以及springcloud-inventory服务。在springcloud-order中插入一条订单数据,在springcloud-inventory中对商品的数量做更新。
创建项目,正常编写业务逻辑代码:略
使用LCN:下载LCN事务协调器
直接导入maven工程,修改配置文件:
#######################################txmanager-s
tart#################################################
#服务端口
server.port=8888
#tx-manager 不得修改
spring.application.name=tx-manager
spring.mvc.static-path-pattern=/**
spring.resources.static-locations=classpath:/stati
c/
#######################################txmanager-e
nd#################################################
#zookeeper 地址
#spring.cloud.zookeeper.connect-string=127.0.0.1:2
181
#spring.cloud.zookeeper.discovery.preferIpAddress
= true
#eureka 地址
eureka.client.service-url.defaultZone=http://user:
123456@eureka1:8761/eureka/,http://user:123456@eureka
2:8761/eureka/
#######################################redis-start
#################################################
#redis 配置文件,根据情况选择集群或者单机模式
##redis 集群环境配置
##redis cluster
#spring.redis.cluster.nodes=127.0.0.1:7001,127.0.0
.1:7002,127.0.0.1:7003
#spring.redis.cluster.commandTimeout=5000
##redis 单点环境配置
#redis
#redis 主机地址
spring.redis.host=192.168.70.145
#redis 主机端口
spring.redis.port=6379
#redis 链接密码
spring.redis.password=
spring.redis.pool.maxActive=10
spring.redis.pool.maxWait=-1
spring.redis.pool.maxIdle=5
spring.redis.pool.minIdle=0
spring.redis.timeout=0
#####################################redis-end####
###############################################
#######################################LCN-start##
###############################################
#业务模块与 TxManager 之间通讯的最大等待时间(单位:秒)
#通讯时间是指:发起方与响应方之间完成一次的通讯时间。
#该字段代表的是 Tx-Client 模块与 TxManager 模块之间的最
大通讯时间,超过该时间未响应本次请求失败。
tm.transaction.netty.delaytime = 5
#业务模块与 TxManager 之间通讯的心跳时间(单位:秒)
tm.transaction.netty.hearttime = 15
#存储到 redis 下的数据最大保存时间(单位:秒)
#该字段仅代表的事务模块数据的最大保存时间,补偿数据会永
久保存。
tm.redis.savemaxtime=30
#socket server Socket 对外服务端口
#TxManager 的 LCN 协议的端口
tm.socket.port=9999
#最大 socket 连接数
#TxManager 最大允许的建立连接数量
tm.socket.maxconnection=100
#事务自动补偿 (true:开启,false:关闭)
# 说明:
# 开启自动补偿以后,必须要配置
tm.compensate.notifyUrl 地址,仅当
tm.compensate.notifyUrl 在请求补偿确认时返回 success 或者
SUCCESS 时,才会执行自动补偿,否则不会自动补偿。
# 关闭自动补偿,当出现数据时也会
tm.compensate.notifyUrl 地址。
# 当 tm.compensate.notifyUrl 无效时,不影响 TxManager
运行,仅会影响自动补偿。
tm.compensate.auto=false
#事务补偿记录回调地址(rest api 地址,post json 格式)
#请求补偿是在开启自动补偿时才会请求的地址。请求分为两种:
1.补偿决策,2.补偿结果通知,可通过通过 action 参数区分
compensate 为补偿请求、notify 为补偿通知。
#*注意当请求补偿决策时,需要补偿服务返回"SUCCESS"字符串
以后才可以执行自动补偿。
#请求补偿结果通知则只需要接受通知即可。
#请求补偿的样例数据格式:
#{"groupId":"TtQxTwJP","action":"compensate","json
":"{"address":"133.133.5.100:8081","className":
"com.example.demo.service.impl.DemoServiceImpl","c
urrentTime":1511356150413,"data":"C5IBLWNvbS5leGF
tcGxlLmRlbW8uc2VydmljZS5pbXBsLkRlbW9TZXJ2aWNlSW1wbAwS
BHNhdmUbehBqYXZhLmxhbmcuT2JqZWN0GAAQARwjeg9qYXZhLmxhb
mcuQ2xhc3MYABABJCo/cHVibGljIGludCBjb20uZXhhbXBsZS5kZW
1vLnNlcnZpY2UuaW1wbC5EZW1vU2VydmljZUltcGwuc2F2ZSgp",
"groupId":"TtQxTwJP","methodStr":"public int
com.example.demo.service.impl.DemoServiceImpl.save()
","model":"demo1","state":0,"time":36,"txGro
up":{"groupId":"TtQxTwJP","hasOver":1,"isComp
ensate":0,"list":[{"address":"133.133.5.100:889
9","isCompensate":0,"isGroup":0,"kid":"wnlEJo
Sl","methodStr":"public int
com.example.demo.service.impl.DemoServiceImpl.save()
","model":"demo2","modelIpAddress":"133.133.5.
100:8082","channelAddress":"/133.133.5.100:64153
","notify":1,"uniqueKey":"bc13881a5d2ab2ace89ae5
d34d608447"}],"nowTime":0,"startTime":1511356150
379,"state":1},"uniqueKey":"be6eea31e382f1f0878d
07cef319e4d7"}"}
#请求补偿的返回数据样例数据格式:
#SUCCESS
#请求补偿结果通知的样例数据格式:
#{"resState":true,"groupId":"TtQxTwJP","action":"n
otify"}
tm.compensate.notifyUrl=http://ip:port/path
#补偿失败,再次尝试间隔(秒),最大尝试次数 3 次,当超过
3 次即为补偿失败,失败的数据依旧还会存在 TxManager 下。
tm.compensate.tryTime=30
#各事务模块自动补偿的时间上限(毫秒)
#指的是模块执行自动超时的最大时间,该最大时间若过段会导
致事务机制异常,该时间必须要模块之间通讯的最大超过时间。
#例如,若模块 A 与模块 B,请求超时的最大时间是 5 秒,则建
议改时间至少大于 5 秒。
tm.compensate.maxWaitTime=5000
#######################################LCN-end####
#############################################
logging.level.com.codingapi=debug
启动事务协调器,访问管理页面:http://ip:port
创建事务管理客户端:
在所有需要处理分布式事务的微服务中增加下述依赖:为统一资源版本,使用properties 统一管理版本信息。
<properties>
使用 LCN 做分布式事务管理时,服务需要知道事务协调器的 URL(客户端与事务协调器是通过 URL 来建立连接的)。我们需要实现一个接口 TxManagerTxUrlService,这个接口实现类可以使用独立应用定义,在微服务应用中引入:
@Service
将该项目分别注入给 springcloud-inventory、springcloud-order、springcloud-portal
在客户端服务的全局配置文件中增加下述配置:
# 定义事务协调器所在位置。根据具体环境定义其中的 IP 地址和端口。
tm.manager.url=http://127.0.0.1:8899/tx/manager/
使用LCN提供的注解实现分布式事务处理:
---在整个业务的事务代码中添加注解@TxTransaction 。
---在事务的起始代码中的@TxTransaction注解中添加isStart=true
在服务网关中配置LCN:
修改网关配置文件:
#路由规则:采用服务名称指定路由方式
zuul.routes.tx-manager.path=/tx/**
zuul.routes.tx-manager.serviceId=tx-manager
修改LCN客户端配置文件:
#http://服务网关 IP:服务网关端口
tm.manager.url=http://localhost:9090/tx/tx/manager/