[分布式]你应该知道的事务

事务

什么是事务

简单来说就是完成一系列由副作用事情,要求这些事情要么都成功,要么都不成功。

以我们买键盘为例,购买一个键盘简化成三个步骤:扣款→发货→收货。这三个事情都得完成了,咱们一个购买键盘的操作才算成功;假设卖家发货失败了,事务就得回滚,淘宝就得给咱们退钱。

要求

从上面的描述来看,一个事务应该满足以下条件:

  1. 原子性:表示事务不可分割,要么都搞定,有一个搞不定,就得回滚。
  2. 一致性:状态的一致性,处理前和处理后的数据要能够对的上。不能我花了50块买了键盘,最后支付宝账上少了100块
  3. 持久性:事务完成之后,会对后续的动作产生持续的影响。50块会被划走,键盘也会到货。
  4. 隔离性:这是描述事务和事务之间的关系,指的是事务在执行的过程中虽然是并行的,但是从最后的结果来看是串行的。
    1. 这里隔离性又可以分为四个等级:未提交读,提交读,可重复读,序列化读。跟兴趣的可以参考:浅析数据库事务的隔离性(isolation)

上面的几个部分就是我们在数据库系统里面常说的:ACID

组成部分

  1. 声明事务
  2. 开始和结束之间的所有操作
  3. 结束事务

简单分类

按照事务是否在单点的服务上执行,我们可以把事务分成

  1. 本地事务:在单个实例上执行的事务
  2. 分布式事务:多个实例上执行的事务,也就是一般意义上的分布式系统。

CAP理论

  1. C:一致性,数据在分布式系统的多个副本中始终保持一致
  2. A:可用性,在合适的时间范围内,返回用户期望的结果
  3. P:分区容错性,在某一个分区中的数据出现了隔离的时候,能够保证对外提供一致的,可用的服务。

Eric Brewer 教授在 2012 年就曾指出 CAP 理论证明不能同时满足一致性、可用性,以及分区容错性的观点。

分布式事务的问题来源

还是以在淘宝买键盘为例

在这里插入图片描述

如果淘宝先不做任何特殊处理,直接库存和扣款分别执行,可能会出现,两种错误的情况:库存不够但是扣款成功了;扣款失败,但是库存操作成功了。

这里就遇到了咱们上面提到的,不满足原子事务的四个原则:

  1. 原子性:很明显,事务被分成了两个部分,各自成功或失败,并没有保证,一起成功或一起失败的原子特性;
  2. 一致性:中间状态很明显就造成了数据的不一致

为了解决上面的问题,我们需要设计一些特殊的操作流程保证数据安全。同时为了应对不同的业务需求,也有一些不同的处理思路

  1. 强一致性:数据更新成功之后,任意时刻所有副本的数据都是一致的,一般采用同步的方式是先,成本较高,实现复杂。
  2. 弱一致性:数据更新成功之后,不承诺立刻就能读到最新的值,也不承诺多久之后可以读到最新的值。
  3. 最终一致性:数据更新成功之后,虽然不承诺立刻就能读到最新的值,但在一定时间内会一致的。

分布式事务解决方案

两阶段提交——强一致性

事务在这个设计中被分成两个阶段:1. 准备阶段;2. 提交阶段;

准备阶段

事务管理器给所有的事务参与者发送所需要执行的内容,每个参与者有两种返回结果:成功,可以执行,并在本地执行但不提交;不成功,无法执行(例如:权限校验失败)。

如果在准备阶段,有参与者反馈执行失败或超时,则由管理者出发回滚消息,事务参与者再执行回滚本地事务操作;

在这个阶段,参与者如果反馈成功,表示它已经做好了提交所有操作的准备,只要管理器确认提交,就可以立刻执行,即使突然宕机,只要节点重新启动,收到了commit指令,必须依旧能正确提交。

在这个阶段,参与者需要做一些持久化的操作,以应对各种情况,以扣款为例:

  1. 冻结50块,以保证这50块不被其他扣款操作划走,导致最后提交的时候因余额不足而失败
  2. 保存必要的日志,确保即使在收到abort之前宕机,重启之后也能根据日志,处理abort请求
  3. 保存必要的日志,确保即使在收到commit之前宕机,重启之后也能根据日志,处理commit请求
  4. 保存必要的日志,确保即使在给管理者发送Ready之前宕机,重启之后也能根据日志,正确的发送Ready消息给管理者。

提交阶段

如果所有的事务参与者都回复了成功消息,则发送提交消息,事务参与者在本地提交操作并执行事务。

事务执行完毕之后,释放使用过程中的锁资源

总结

  • 解决的问题
  1. 解决数据一致性问题,满足一致性原则,事务只有执行成功或失败两种可能
  2. 无论成功或者失败,事务都会达到一个最终一致的状态
  • 缺点
  1. 管理者单点问题,如果管理者宕机,会严重影响事务的执行
  2. 两阶段提交执行过程中,所有的参与者都需要听从协调者的统一调度,期间处于阻塞状态而不能从事其他操作,这样效率极其低下。
  3. 在提交阶段,如果因为网络问题导致部分commit消息无法发送,就会导致一部分参与者接收到了commit而一部分没有,于是就出现了数据不一致。

三阶段提交——强一致性

由于二阶段提交存在的上述缺点,所以又有人提出了二阶段提交的改良版,三阶段提交。

在这个设计中,事务被分成了三个阶段,分别是:预询盘(can_commit)、预提交(pre_commit)和以及事务提交(do_commit)。

预询盘

在这个阶段,管理者会向参与者确认是否可以执行这个事务,参与者给出一个预估的结果。

这个过程和二阶段的准备阶段比较像,主要是对资源情况进行一个确认,该失败的失败,该预留的预留。

预提交

如果有参与者反馈超时,或者反馈无法执行,则发送abort消息,如果所有参与者都反馈了可以执行,则执行一下步骤:

  1. 管理者给所有的参与者发送事务执行的消息
  2. 参与者收到执行消息之后开始执行,但不提交
  3. 参与者执行完毕之后将执行结果反馈给管理者

提交

如果预提交过程,所有的任务都顺利执行,那么管理者下发commit消息,让参与者提交事务。

在本阶段如果因为管理者网络问题,导致参与者迟迟不能收到 commit 或 rollback 请求,那么参与者将不会如两阶段提交中那样陷入阻塞,而是等待超时后继续 commit,相对于两阶段提交降低了同步阻塞。

总结

  • 解决的问题

主要还是二阶段的阻塞问题,毕竟没收到commit或者rollback,参与者自己还是能从阻塞的状态回过神来,自己就commit完,恢复了。

事件队列方案——最终一致性方案

通过事件的方式,将事件中需要完成的操作通过消息提交到消息队列中,在消息队列里面保证消息已定会被消费者正确的消费掉,失败了就重试。

TCC补偿模式——最终一致性方案

依次的调用事务中的不同操作,某一个操作执行失败了就依次回滚之前执行成功的操作。实现的要点是

  1. 要记录调用链
  2. 每个操作都得是可以回滚的,同时要保证回滚操作是幂等的
  3. 必须按失败原因执行不同的回滚策略。

稍微复杂的版本是,执行之前先进行资源锁定,只有锁定成功了再真正的依次执行,否则,对锁定资源进行释放。

缓存和数据库一致

  1. 定时刷新缓存;或者为缓存中的数据设置过期时间;这样就可以在能够容忍的时间内达到和数据库数据一致了
  2. 数据更新之后删除缓存或者触发缓存更新;这样就能保证下次拿到的一定是最新的数据了

解决框架

Seate,阿里开源的一套分布式事务框架

总结

虽然两阶段/三阶段是非常规范的协议,但是在互联网中很少使用,有几个原因:

  1. 性能问题,阻塞性协议,高并发场景下并不适用
  2. mysql5.7之前存在缺陷,支持并不好
  3. 运维和实现比较复杂

如果我们不追求二阶段提交要求的强一致性,有一个非常朴素的想法,依次触发每一个事务,如果失败了,把之前所有的事务都回滚掉,这个就被称作Saga。但 Saga 这种方式并不能保证隔离性,于是出现了 TCC。在实际交易逻辑前先做业务检查、对涉及到的业务资源进行“预留”,或者说是一种“中间状态”,如果都预留成功则完成这些预留资源的真正业务处理,典型的如票务座位等场景。

当然还有像 Ebay 提出的基于消息表,即可靠消息最终一致模型,但本质上这也属于 Saga 模式的一种特定实现。

仔细对比这些方案与二阶段,会发现这些方案本质上都是将两阶段提交从资源层提升到了应用层。

  • Saga 的核心就是补偿,一阶段就是服务的正常顺序调用(数据库事务正常提交),如果都执行成功,则第二阶段则什么都不做;但如果其中有执行发生异常,则依次调用其补偿服务(一般多逆序调用未已执行服务的反交易)来保证整个交易的一致性。应用实施成本一般。
  • TCC 的特点在于业务资源检查与加锁,一阶段进行校验,资源锁定,如果第一阶段都成功,二阶段对锁定资源进行交易逻辑,否则,对锁定资源进行释放。应用实施成本较高。
  • 基于可靠消息最终一致,一阶段服务正常调用,同时同事务记录消息表,二阶段则进行消息的投递,消费。应用实施成本较低。

参考

  1. 事务处理
  2. 分布式事务:两阶段提交与三阶段提交
  3. 常用的分布式事务解决方案有哪些?
  4. 分布式事务选型的取舍
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值