理解分布式事务

理解分布式事务,第1张

理解分布式事务 1. 为什么需要分布式事务

首先,复杂的业务场景是需要事务的,这是毋庸置疑的,至于需要分布式事务则可能是因为业务数据太多,进行了分库,或者newsql数据酷中对数据进行了shard,将数据存放在了不同的服务器上,对这些不属于同一进程或不属于同一台物理机的数据进行 *** 作时,也希望可以支持事务功能,这就是分布式事务。

2. 二阶段提交

目前来讲,比较常见的分布式事务实现方式是二阶段提交。主要实现方式如下:

    从分布式集群中选择一个节点作为协调者客户端将本次事务请求发给协调者协调者收到请求后,在本地记录事务状态,然后将写入请求分发给所有需要参与该事物的其他节点,这些其他节点叫作参与者。参与者收到请求后,在本地进行prepare *** 作,即对需要写入的数据加锁,然后将新版本数据写入,在本地记录事务状态,完成后给协调者回复一个prepareOk。如果失败则给协调者恢复rollback协调者收到所有的prepareok之后,就可以提交事务,将事物状态改为commit,然后向所有参与者发起commit请求。参与者收到commit请求后,完成事务提交,释放锁信息,给协调者进行回commitOk。协调者收到所有commitOk之后,就可以回复客户端,删除事务信息,本次事务以成功提交而结束,。如果在第5步中收到了任意参与者的rollback信息,那么协调者都将决定回滚该事物,流程和commit一致。
    可以看到在步骤中,每次事务状态改变后,就需要持久化那么事务上下文的信息什么时候才能删除?
    参与者在完成commit之后,就可以清理事务上下文。协调者需要在收到所有的commitOk请求后,才能清理事务上下文。协调者这时候才能清理的原因是要确保所有的参与者都收到了commit/rollback请求。看个例子,协调者决定提交一个事务,提交后向所有参与者发送commit请求,发送之后清理事务上下文,这时候有个参与者挂了,过了两分钟才重启,这时候该参与者永远也无法知道事务该提交还是回滚,因为协调者上的事务上下文已经被清理了。所以这是不安全的,协调者的上下文必须在知道所有节点都完成commit/rollback之后才能清理。
3. 二阶段提交能够满足ACID吗

首先分析一下2PC为什么符合原子性。虽然有多个参与者参与事务,但是事务状态的推动都是靠协调者来进行了,只有协调者可以推动事物的整体状态,所以当协调者做出决定的时候,事务要么是commit,要么是rollback,不可能存在一部分参与者提交,一部分参与者回滚的情况,不论如何,事务总是符合原子性的。
分析一下隔离性,在二阶段提交的过程中,所有的写入 *** 作都会使用锁保护,如果不考虑性能,所有的数据读取也需要加锁,那么最终的对数据读写就能达到串行化,这是靠二阶段提交中加上SS2PL来实现的。当然也不一定非要使用锁的方式,和单机事务一样,也可以在二阶段提交中使用OCC等方式来进行并发控制。
持久性,上述算法协调者会在收到所有的commitOk请求后,完成事务提交,这时候所有参与者都已经将数据写入了,保证了持久性。

4. 异常处理

二阶段提交这种方案乍一看很简单,就是靠协调者根据各种情况,推动事物状态,但是在分布式场景中,可能出现各种预期之外的情况,下面简单分析一下:

4.1 协调者宕机
    在prepare状态宕机。协调者收到客户端时候请求后会持久化事务上下文,那么重启之后可以恢复出来,继续给所有的参与者发送prepare消息,之后流程继续。只要协调者没进入到commit或者rollback状态,宕机重启流程都是一样的,只是参与者可能会收到相同的prepare请求,幂等处理即可(不能上次prepare进行了commit,这次相同的prepare请求却需要rollback)在进入commit状态后宕机。其实和prepare相似,协调者会持久化commit状态,之后给所有参与者发送commit请求,如果这时候宕机了,能够恢复出commit事务上下文,给所有参与者发送commit消息。
4.2 参与者宕机
    参与者宕机对事物状态就更没影响了,因为状态只靠协调者推进。但是参与者宕了之后,写调整需要进行一些处理,比如长期收不到所有节点的prepareOk请求,那么超时后参与者可以决定rollback。如果协调者长期收不到commitOk请求, 那么参与者需要一直尝试给未完成的节点发送commit请求,直到收到所有的commitOk,在此之前协调者也无法清理事务上下文。

2PC的故障处理非常麻烦,而且存在一些问题,比如某个事物协调者宕机了,但是所有参与者完成了prepare,这时候参与者就只能等协调者恢复,在此之前都必须持有之前上的锁,会阻塞集群的其他事务,这是2PC让人很不喜欢的一点,所以在spanner中进行了改进,使用paxos为每个节点进行3副本冗余,当某个节点宕机后其他副本迅速顶上,防止2PC阻塞住。

5. 优化

简单分析一下2PC提交的延迟:

    客户端发送给协调者请求(1次网络)协调者存储事务上下文(1次落盘)协调者给所有参与者发请求(1次网络)参与者收到prepare请求后写入数据并持久化事务状态(一次落盘)参与者回复prepareOk(1次网络)协调者完成提交,更新并持久化事务状态(1次落盘)协调者给所有参与者发送commit(1次网络)参与者更新事务状态为commit(1次落盘)参与者回复commitOk(1次网络)
    10.协调者结束事务,回复客户端(1次网络)

相当于6/2次网络来回,然后4次落盘。来看看如何优化延迟:

优化一,没必要等到所有节点都commitOk之后再回复请求,协调者更新了事务状态为commit之后,就可以对客户端进行提交,剩下的参与者提交和事务上下文回收可以异步进行。即上述的7,8,9步可以不需要,通过异步执行。

优化二,并行提交,客户端没必要把所有请求都发给参与者,让参与者给协调者发信息。客户端可以直接将请求发送给所有参与者,从所有参与者中选出一个协调者来处理事务即可。即1234可以合成为1步(客户端向所有参与者发起事务请求,并指定其中一个是协调者。)

优化三,第6步是收到了所有prepareOk后更新事务状态,然后持久化状态,根据优化1这时候就可以回复客户端。这一步的持久化状态可以不需要吗?理论上是可以行的,因此如果参与者进入了commit或者rollback状态,那一定是因为参与者全部完成了prepare,而参与者prepare的上下文在每个参与者中一定存在,所以这协调者持久化commit/rollback状态可以异步执行,即使宕机了,该信息丢失了,可以重启后,重新尝试向所有的参与者发起prepare请求,参与者会回复commit或者rollback请求,这样协调者可以回复出来commit或者rollback状态。

所以优化后最终逻辑为:

    客户端向所有参与者发起请求,选择其中一个为协调者(1次网络)所有参与者完成prepare阶段,更新事务状态(1次写盘)所有参与者向协调者发起prepareOk请求(1次网络)协调者收到所有prepareOk之后,完成提交,回复客户端(1次网络)后面全是异步执行,定期给所有节点发送commit请求,收到所有commitOk后进入事务上下文清理阶段。
    整体来说只需要1次写盘,就可以完成提交。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5705290.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存