事务,一般指数据库事务,就是将多条SQL语句一起执行的功能,就称为一个数据库事务。数据库事务可以确保该事务范围内的所有操作都可以全部成功或者全部失败。
数据库事务具有4个属性,分别是:原子性,一致性,隔离性和持久性,也被称为ACID特性。
分布式事务是分布式系统里的一个重要属性,特指事务的发起者、资源及资源管理器和事务协调者分别位于分布式系统的不同节点之上。从本质上来说,分布式事务就是为了保证在分布式场景下,数据操作的正确执行。
就比如银行跨行转账:在农业银行的A用户要给在工商银行的B用户转100块钱,那我们是不是就要保证A用户的余额-100,B用户的余额+100,这就是分布式事务。简单点来说就是避免银行的系统出现错误,导致A用户已经转账成功,但B用户没有收到钱,A用户的余额-100而B用户的余额没有+100.
分布式事务也部分遵守ACID原则:
分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。简单点来讲就是:将我们微服务架构中拆分出来的服务分别部署在不同的服务器上,这就是分布式微服务架构。当然,这只是字面上的理解,真正的分布式系统需要考虑很多东西,比如:分布式锁,分布式事务,分布式数据库等等。
可以说分布式是为了减轻一台服务器的压力,利用多台服务器来共同承担压力,而微服务则是分散业务能力。分布式是为了分散压力,微服务是为了分散能力。
微服务和分布式之间的联系可以理解为:分布式可以是微服务,当微服务不一定是分布式,因为微服务可以部署在一台服务器上。分布式系统遵循CAP原则,但不能同时满足三个。
一致性(Consistency): 指强一致性,在写操作完成后开始的任何读操作都必须返回该值,或者后续写操作的结果。也就是说,在一致性系统中,一旦客户端将值写入任何一台服务器并获得响应,那么之后client从其他任何服务器读取的都是刚写入的数据。
一致性又分为强一致性,弱一致性和最终一致性,因为在分布式系统中,一般会选择牺牲一些一致性来换取可用性和分区容错性。(注意:牺牲一致性不代表完全抛弃一致性,而是将强一致性变成弱一致性)
可用性(Availability): 可用性(高可用)是指:每次向未崩溃的节点发送请求,总能保证收到响应数据(允许不是最新数据)
分区容忍性(Partition tolerance): 分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,也就是说,服务器A 和B 发送给对方的任何消息都是可以放弃的,也就是说A和B可能因为各种意外情况,导致无法成功进行同步,分布式系统要能容忍这种情况。除非整个网络环境都发生了故障。
一般情况下,分布式系统中,必须满足 CAP 中的 P,此时只能在 C/A 之间作出取舍。因为如果选择了CA ,舍弃了P,说白了就是一个单体架构。而CA的选择,一般遵循Base理论。
分布式事务的解决放方案有很多种,比如:2PC,3PC,TCC,SEGA事务等等,每一种事务解决方案都有他自己的优缺点和相对应的应用场景。
在分布式架构下,每个节点只知晓自己操作的失败或者成功,无法得知其他节点的状态。当一个事务跨多个节点时,为了保持事务的原子性与一致性,而引入一个协调者 来统一掌控所有参与者 的操作结果,并指示它们是否要把操作结果进行真正的提交 或者回滚 (rollback)。就是说建立一个事务协调者来管理所有的事务参与者,用来指挥执行统一的提交或者回滚。
2PC事务解决方案是比较常用的事务解决方案,它保证了事务的原子性以及尽量保证了事务的一致性,做到了事务的统一提交和回滚。
2PC解决方案主要是将事务分为两个阶段来执行,分别是事务的准备阶段和事务的提交阶段,其中所涉及到的角色分别是事务协调者(事务的发起者)和事务参与者(事务的执行者)。
准备阶段: 由事务的协调者发起询问参与者是否可以提交事务,但是这一阶段并未提交事务。
1.调用接口,方法等向事务的协调者发起请求
2.协调者向所有的事务参与者发送询问信息是否可以提交事务,并等待回复
3.事务参与者收到信息后开始执行事务操作,记录相关的事务日志,当不提交事务
4.参与者执行事务成功,向协调者发送同意提交,否则就发送终止
提交阶段: 协调者发起正式提交事务的请求,当所有参与者都回复同意时,则意味着完成事务
1.事务协调者向事务参与者发送提交事务请求
2.事务参与者向事务协调者发送同意消息
3.事务参与者完成所有的事务提交操作,释放在整个事务期间内占用的资源,并返回ack完成信息
4.事务协调者接收到事务参与者发送过来的ack信息,事务完成
准备阶段出现异常: 如果任意一个参与者节点在准备阶段,或者协调者节点在准备阶段的询问超时之前无法获取所有参与者节点的响应消息时,那么这个事务将会被回滚。
1.事务参与者在准备阶段返回终止消息或响应时间超时
2.事务协调者向事务参与者发送事务回滚请求
3.事务参与者根据事务日志消息里的undo文件执行事务回滚,并释放占用的资源
4.事务参与者向事务协调者发送事务回滚成功的ack消息,事务取消
提交阶段正常执行(全流程): 不管发生什么情况,在第二阶段时候,事务到最后都会结束并释放相对应的资源
1.用户使用接口,想事务协调者发起请求
2.事务协调者向事务参与者发送事务内容,并询问是否可以提交,等等回复
3.事务参与者开启本地事务,记录相关的日志消息,但不提交事务
4.事务参与者回复同意
5.事务协调者向事务参与者发起正式提交事务请求
6.事务参与者提交事务,释放占用的资源
7.事务参与者回复事务成功提交信息
8.事务协调者收到成功提交的信息,事务完成,释放占用的资源
2PC事务处理优缺点:
优点: 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域(但也不能保证100%一致性)。
缺点:
1.性能问题 :执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
2.可靠性问题 :参与者发生故障—>协调者需要给每个参与者额外指定超时机制,超时后整个事务失败。
3.数据一致性问题 :二阶段无法解决的问题:协调者在发出commit
消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
4.实现复杂 :牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
三阶段提交协议,是二阶段提交协议的改进版本,三阶段提交有两个改动点:1.在协调者和参与者都加入超时机制和2.将2PC的准备阶段拆为两个阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
简单点来说就是:3PC一方面引入超时机制,另一方面把2PC的准备阶段再次一分为二,这样三阶段提交就分为:CanCommit,PreCommit和DoCommit三个阶段。
CanCommit阶段: 这个阶段其实和2PC的准备阶段很像。协调者向参与者发送CanCommit请求,事务参与者检查自身状态,如果可以提交就返回Yes响应,否则返回No响应。当事务协调者收到yes回复时,开始进入下一个阶段,如果收到了No,则取消事务。
1.用户通过Api或接口等方式向事务协调者发起请求
2.事务协调者向事务参与者发送CanCommit请求,询问事务参与者是否正常
3.事务参与者检查自身状况,看自身是否可以正常执行
4.事务参与者检查完后向协调者发送反馈信息,如果为Yes,开始进入下一个阶段,否则就取消
PreCommit阶段: 这个阶段主要是根据CanCommit阶段返回的信息做出响应,共有俩种结果,分别是收到yes回复和收到No回复。当所有的参与者都回复yes时,这个阶段的响应就跟2PC的准备阶段一样,如果有一个甚至多个回复了No,那就开始事务取消或回滚。
全部回复Yes:
1.事务参与者全部回复Yes
2.事务协调者收到后发送PreCommit请求
3.事务参与者执行事务,记录相关日志,但并没有提交事务
4.事务参与者向事务协调者发送相关的ack回复信息
一个甚至多个回复No:
1.事务参与者回复No
2.事务协调者收到后发送abort请求,叫事务参与者中断事务
3.事务参与者中断事务
4.事务参与者向事务协调者回复ack信息
DoCommit阶段: 这个阶段是事务的真正提交状态,跟2PC的提交阶段类似,但3PC引入了超时机制,所以就算在这个阶段中协调者出现问题,或者协调者与参与者网络出现问题,都会导致参与者无法接收到协调者发出的 do Commit 请求或 abort 请求。但是,参与者都会在等待超时之后,继续执行事务提交。
DoCommit阶段正常(全过程) :
1.用户,系统通过api等方式向事务协调者发起请求
2.事务协调者向事务参与者发送CanCommit请求
3.事务参与者检查自身状况,看能不能提交事务
4.事务参与者根据自身状况回复消息,假如为Yes
5.事务协调者收到事务参与者发送过来的信息,开始发送PreCommit请求进入下一阶段
6.事务参与者开始执行事务,但不提交
7.事务参与者回复ack信息,向事务协调者汇报事务提交信息
8.事务协调者收到信息后向事务参与者发送DoCommit请求
9.事务参与者收到信息后(注意:因为有超时机制的存在,这里不管事务参与者有没有收到信息,事务提交都会注册执行),开始提交事务
10.事务参与者提交完事务后发送ack消息
11.事务协调者收到信息后,事务结束,并释放整个过程占用的资源
1.用户,系统通过api等方式向事务协调者发起请求
2.事务协调者向事务参与者发送CanCommit请求
3.事务参与者检查自身状况,看能不能提交事务
4.事务参与者根据自身状况回复消息,假如为Yes
5.事务协调者收到事务参与者发送过来的信息,开始发送PreCommit请求进入下一阶段
6.事务参与者开始执行事务,但有时中途出现错误,导致事务中断
7.事务参与者回复ack信息为No,向事务协调者汇报事务提交情况
8.事务协调者收到信息后向事务参与者发送回滚请求
9.事务参与者收到信息后,根据事务的日记信息进行事务回滚
10.事务参与者回滚完事务后发送ack消息
11.事务协调者收到信息后,事务结束,并释放整个过程占用的资源
3PC的优缺点:
优点: 相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段 3 中协调者出现问题时,参与者会继续提交事务。
缺点: 数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 doCommit 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
TCC(Try Confirm Cancel)方案是一种应用层面侵入业务的两阶段提交。是目前最火的一种柔性事务方案,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作 。
TCC方案分为两个阶段,分别是Try(尝试)和Confirm(确认),Cancel(取消)
Try(尝试): 主要是对业务系统做检测及资源预留 (加锁,锁住资源)
CC阶段: 主要是根据第一阶段的结果,决定是执行confirm还是cancel
简单点来说就是在我们在执行事务业务逻辑(一个业务中有事务)的时候,先做一次尝试操作,然后将尝试操作所设计到的事务资源锁住,如果尝试阶段成功执行,则执行Confirm确认操作,释放锁。相反,如果尝试阶段失败,就执行Cancel取消操作,释放资源,这里的取消操作主要是针对被锁住的资源来说的。
1.当业务应用,即我们的一些业务逻辑接口,向事务协调器说要开启事务时,业务应用会先调用相关的服务中的try尝试接口,模拟一遍业务的运行
2.当尝试阶段成功时,会向事务系统器发送响应信息
3.事务协调器接收到相对应的信息后,执行相关的confirm或者cancel操作
Try(尝试阶段): 以商城系统为例,假设商品库存为 100,购买数量为 2,这里检查和更新库存的同时,冻结用户购买数量的库存,同时创建订单,订单状态为待确认。
TCC 机制中的 Try 仅是一个初步操作,它只是一个尝试,它需要和后续的确认操作一起才能真正构成一个完整的业务逻辑,这个阶段主要完成:1.完成所有业务检查(保证一致性),2.预留必须的业务资源(准隔离,将该资源上锁),3.进行尝试操作
如上所示:假设我们要购买2件商品,此商品总库存为100,那么相对应的业务逻辑就大概是:商品库存-2,创建订单。这时候TCC的Try(尝试)阶段的大概流程就是:
1.检查更新库存服务,将库存量-2
2.将减少的那2件库存量冻结并上锁
3.创建订单,该订单为待确认状态
Confirm(确认阶段): Confirm阶段主要是根据Try阶段的完成情况来定的,如果Try阶段全部尝试成功,那么就执行confirm操作,Confirm操作和Cancel操作都支持重复尝试特性,如果他们操作失败,那么会一直尝试操作,直到成功为止。
当我们的Try(尝试)阶段成功时,则执行Confirm(确认)阶段:
1.释放冻结库存的资源
2.修改订单状态为成功
注意: 这里使用的资源一定是 Try 阶段预留的业务资源。在 TCC 事务机制中认为,如果在 Try 阶段能正常的预留资源,那 Confirm 一定能完整正确的提交。Confirm 阶段也可以看成是对 Try 阶段的一个补充,Try+Confirm 一起组成了一个完整的业务逻辑。
Cancel(取消阶段): 如果Try(尝试)阶段的结果为失败,则执行Cancel操作
1.当Try(尝试)阶段为失败时,首先释放冻结库存的资源,将该资源返回给库存总量
2.将订单的状态修改为取消
TCC的优缺点:
优点:
缺点: TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
Saga 事务核心思想是将长事务拆分为多个 本地短事务,由 Saga 事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
Saga 事务基本协议如下:
简单点说就是:Seaga事务将我们一整个事务拆分为一个个小事务,这些小事务都是在相对于的服务中,就是服务中的本地事务,然后每一个本地事务都有一个补偿动作,用于事务的回滚。
对于事务异常,Saga提供了两种恢复策略,分别是向前恢复和向后恢复。
向后恢复: 这种做法的效果是撤销掉之前所有成功的子事务,使得整个 Saga 的执行结果撤销。
如上所示:当我们在下单支付时,碰见支付失败,那么事务的回滚是反过来的,向后恢复整体的流程顺序为:T1,T2,T3,C3,C2,C1
先前恢复: 先前恢复的话有一个前提,就是所有的子事务到最后都会成功,然后如果一个子事务出现故障的话,他会一直重试,直到事务成功,适用于必须成功的场景。
Saga事务有两种不同的实现方式,分别是命令协调和事件编排。
命令协调: 该方式会有一个中央协调器(Orchestrator,简称 OSO)以命令/回复的方式与每项服务进行通信,全权负责告诉每个参与者该做什么以及什么时候该做什么。
如上所示,当我们在发起订单业务时,整个流程如下:
1.事务发起方的主业务逻辑请求 OSO 服务开启订单事务
2.OSO 向库存服务请求扣减库存,库存服务回复处理结果。
3.OSO 向订单服务请求创建订单,订单服务回复创建结果。
4.OSO 向支付服务请求支付,支付服务回复处理结果。
5.主业务逻辑接收并处理 OSO 事务处理结果回复。
中央协调器必须事先知道执行整个订单事务所需的流程(例如通过读取配置)。如果有任何失败,它还负责通过向每个参与者发送命令来撤销之前的操作来协调分布式的回滚。
基于中央协调器协调一切时,回滚要容易得多,因为协调器默认是执行正向流程,回滚时只要执行反向流程即可。
Seaga事务命令协调模式优缺点:
优点:
1.服务之间关系简单,避免服务之间的循环依赖关系,因为 Saga 协调器会调用 Saga 参与者,但参与者不会调用协调器。
2.程序开发简单,只需要执行命令/回复(其实回复消息也是一种事件消息),降低参与者的复杂性。
3.易维护扩展,在添加新步骤时,事务复杂性保持线性,回滚更容易管理,更容易实施和测试。
缺点:
1.中央协调器容易处理逻辑容易过于复杂,导致难以维护。
2.存在协调器单点故障风险。
事件编排: 在这个方式中,没有中央协调器(没有单点风险)时,每个服务产生并观察其他服务的事件,并决定是否应采取行动。在事件编排方法中,第一个服务执行一个事务,然后发布一个事件。该事件被一个或多个服务进行监听,这些服务再执行本地事务并发布(或不发布)新的事件。
事件编排主要是已监听任务和发布任务为主,它的流程主要是:
1.事务发起方的主业务逻辑发布开始订单事件。
2.库存服务监听开始订单事件,扣减库存,并发布库存已扣减事件。
3.订单服务监听库存已扣减事件,创建订单,并发布订单已创建事件。
4.支付服务监听订单已创建事件,进行支付,并发布订单已支付事件。
5.主业务逻辑监听订单已支付事件并处理。
事件编排是实现 Saga 模式的自然方式,它很简单,容易理解,不需要太多的代码来构建。如果事务涉及 2 至 4 个步骤,则可能是非常合适的。
Seaga事务编排模式优缺点:
优点:
1.避免中央协调器单点故障风险。
2.当涉及的步骤较少服务开发简单,容易实现。
缺点:
1.服务之间存在循环依赖的风险。
2.当涉及的步骤较多,服务间关系混乱,难以追踪调测。
2PC/3PC :依赖于数据库,能够很好的提供强一致性和强事务性,但相对来说延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操作的情况,不适合高并发和高性能要求的场景。
TCC :适用于执行时间确定且较短,实时性要求高,对数据一致性要求高,比如互联网金融企业最核心的三个服务:交易、支付、账务。
Saga 事务 :由于 Saga 事务不能保证隔离性,需要在业务层控制并发,适合于业务场景事务并发操作同一资源较少的情况。Saga 相比缺少预提交动作,导致补偿动作的实现比较麻烦,例如业务是发送短信,补偿动作则得再发送一次短信说明撤销,用户体验比较差。Saga 事务较适用于补偿动作容易处理的场景。