分布式理论之分布式事物

假如没有分布式事务

在一系列微服务系统当中,假如不存在分布式事务,会发生什么呢?让我们以互联网中常用的交易业务为例子:

img

上图中包含了库存和订单两个独立的微服务,每个微服务维护了自己的数据库。在交易系统的业务逻辑中,一个商品在下单之前需要先调用库存服务,进行扣除库存,再调用订单服务,创建订单记录。

正常情况下,两个数据库各自更新成功,两边数据维持着一致性。

img

但是,在非正常情况下,有可能库存的扣减完成了,随后的订单记录却因为某些原因插入失败。这个时候,两边数据就失去了应有的一致性。

img

 

什么是分布式事务?

分布式事务用于在分布式系统中保证不同节点之间的数据一致性。分布式事务的实现有很多种,最具有代表性的是由Oracle Tuxedo系统提出的XA分布式事务协议。

XA协议包含两阶段提交(2PC/tow phase commit)三阶段提交(3PC)两种实现,这里我们重点介绍两阶段提交的具体过程。

XA两阶段提交

在XA协议中包含着两个角色:事务协调者(coordinator)事务参与者(participants, 或cohorts)。让我们来看一看他们之间的交互流程:

第一阶段:

 

 

img

 

在XA分布式事务的第一阶段,作为事务协调者的节点会首先向所有的参与者节点发送Prepare请求。

在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“完成”消息。

当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。

第二阶段:

 

 

img

 

 

在XA分布式事务的第二阶段,如果事务协调节点在之前所收到都是正向返回,那么它将会向所有事务参与者发出Commit请求。

接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将会向事务协调者返回“完成”消息。

当事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成。

 

以上所描述的是XA两阶段提交的正向流程,接下来我们看一看失败情况的处理流程:

第一阶段:

 

img

 

 

第二阶段:

 

img

 

在XA的第一阶段,如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。

于是在第二阶段,事务协调节点向所有的事务参与者发送Abort请求。接收到Abort请求之后,各个事务参与者节点需要在本地进行事务的回滚操作,回滚操作依照Undo Log来进行。

 

在异步环境(asynchronous)并且没有节点宕机(fail-stop)的模型下,2PC可以满足全认同、值合法、可结束,是解决一致性问题的一种协议。但如果再加上节点宕机(fail-recover)的考虑,2PC是否还能解决一致性问题呢?

  • coordinator如果在发起提议后宕机,那么participant将进入阻塞(block)状态、一直等待coordinator回应以完成该次决议。这时需要另一角色把系统从不可结束的状态中带出来,我们把新增的这一角色叫协调者备份(coordinator watchdog)。coordinator宕机一定时间后,watchdog接替原coordinator工作,通过问询(query) 各participant的状态,决定阶段2是提交还是中止。这也要求 coordinator/participant 记录(logging)历史状态,以备coordinator宕机后watchdog对participant查询、coordinator宕机恢复后重新找回状态。 从coordinator接收到一次事务请求、发起提议到事务完成,经过2PC协议后增加了2次RTT(propose+commit),带来的时延(latency)增加相对较少。

XA三阶段提交

3PC(three phase commit)即三阶段提交,既然2PC可以在异步网络+节点宕机恢复的模型下实现一致性,那还需要3PC做什么,3PC是什么鬼?

在2PC中,一个participant的状态只有它自己和coordinator知晓,假如coordinator提议后自己宕机了,并且在watchdog启用前,一个participant也宕机了。。其他participant就会进入既不能回滚、又不能强制commit的阻塞状态,直到participant宕机恢复。这引出两个疑问:

能不能去掉阻塞,使系统可以在commit/abort前回滚(rollback)到决议发起前的初始状态 当次决议中,participant间能不能相互知道对方的状态,又或者participant间根本不依赖对方的状态

coordinator 接收完participant的反馈之后,进入阶段2,给各个participant发送准备提交(prepare to commit)指令。participant接到准备提交指令后可以锁资源,但要求相关操作必须可回滚。coordinator接收完确认(ACK)后进入阶段3、进行commit/abort,3PC的阶段3与2PC的阶段2无异。协调者备份(coordinator watchdog)、状态记录(logging)同样应用在3PC。 participant如果在不同阶段宕机,我们来看看3PC如何应对:

  • 阶段1: coordinator或watchdog未收到宕机participant的vote,直接中止事务;宕机的participant恢复后,读取logging发现未发出赞成vote,自行中止该次事务

  • 阶段2: coordinator未收到宕机participant的precommit ACK,但因为之前已经收到了宕机participant的赞成反馈(不然也不会进入到阶段2),coordinator进行commit;watchdog可以通过问询其他participant获得这些信息,过程同理;宕机的participant恢复后发现收到precommit或已经发出赞成vote,则自行commit该次事务

  • 阶段3: 即便coordinator或watchdog未收到宕机participant的commit ACK,也结束该次事务;宕机的participant恢复后发现收到commit或者precommit,也将自行commit该次事务

因为有了准备提交(prepare to commit)阶段,3PC的事务处理延时也增加了1个RTT,变为3个RTT(propose+precommit+commit),但是它防止participant宕机后整个系统进入阻塞态,增强了系统的可用性,对一些现实业务场景是非常值得的。

 

HBase Snapshot的两阶段提交

hbase为指定表执行snapshot操作时,实际上真正执行snapshot的是对应表的所有region。这些region因为分布在多个RegionServer上,所以需要一种机制来保证所有参与执行snapshot的region要么全部完成,要么都没有开始做,不能出现中间状态,比如某些region完成了,某些region未完成。

 

HBase使用两阶段提交协议(2PC)来保证snapshot的分布式原子性。prepare阶段协调者会向所有参与者发送prepare命令,所有参与者开始获取相应资源(比如锁资源)并执行prepare操作确认可以执行成功,通常核心工作都是在prepare操作中完成的。并返回给协调者prepared应答。协调者接收到所有参与者返回的prepared应答之后(表明所有参与者都已经准备好提交),在本地持久化commit状态,进入commit阶段,协调者会向所有参与者发送commit命令,参与者接收到commit命令之后会执行commit操作并释放资源,通常commit操作都非常简单。

接下来就看看hbase是如何使用2PC协议来构建snapshot架构的,基本步骤如下:

  1. prepare阶段:HMaster在zookeeper创建一个’/acquired-snapshotname’节点,并在此节点上写入snapshot相关信息(snapshot表信息)。所有regionserver监测到这个节点之后,根据/acquired-snapshotname节点携带的snapshot表信息查看当前regionserver上是否存在目标表,如果不存在,就忽略该命令。如果存在,遍历目标表中的所有region,分别针对每个region执行snapshot操作,注意此处snapshot操作的结果并没有写入最终文件夹,而是写入临时文件夹。regionserver执行完成之后会在/acquired-snapshotname节点下新建一个子节点/acquired-snapshotname/nodex,表示nodex节点完成了该regionserver上所有相关region的snapshot准备工作。

  1. commit阶段:一旦所有regionserver都完成了snapshot的prepared工作,即都在/acquired-snapshotname节点下新建了对应子节点,hmaster就认为snapshot的准备工作完全完成。master会新建一个新的节点/reached-snapshotname,表示发送一个commit命令给参与的regionserver。所有regionserver监测到/reached-snapshotname节点之后,执行snapshot commit操作,commit操作非常简单,只需要将prepare阶段生成的结果从临时文件夹移动到最终文件夹即可。执行完成之后在/reached-snapshotname节点下新建子节点/reached-snapshotname/nodex,表示节点nodex完成snapshot工作。

  1. abort阶段:如果在一定时间内/acquired-snapshotname节点个数没有满足条件(还有regionserver的准备工作没有完成),hmaster认为snapshot的准备工作超时。hmaster会新建另一种新的节点/abort-snapshotname,所有regionserver监听到这个命令之后会清理snapshot在临时文件夹中生成的结果。

 

可以看到,在这个系统中HMaster充当了协调者的角色,RegionServer充当了参与者的角色。HMaster和RegionServer之间的通信通过Zookeeper来完成,同时,事务状态也是记录在Zookeeper上的节点上。HMaster高可用情况下主HMaster宕机了,从HMaster切成主后根据Zookeeper上的状态可以决定事务十分继续提交或者abort。

巨人的肩膀:

https://github.com/wangzhiwubigdata/God-Of-BigData/blob/master/

http://hbasefly.com/2017/09/17/hbase-snapshot/

https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653193461&idx=1&sn=d69ccec780ae6d3b0c722cf09fa154d1&chksm=8c99f62fbbee7f39cd221bd0ecc9105a5c16e353d82d2407e7f295da9f9172cfd4889d3f12c8&scene=21#wechat_redirect

猜你喜欢

转载自www.cnblogs.com/zz-ksw/p/12727415.html