分布式事务提交 - db 代理

    之前做 toC 的项目时,有做过一个分布式 db 的代理层,内部的很多东西都是很有意思。本来最初是想要做一个大表添加字段的工具,但做着做着,有同事提出能不能添加一些自动分表的功能,从这一步开始,就让这个项目从原本的工具,变成了一个代理层的分布式 db。大致的功能包含了一层双缓存(LRU 队列和 redis 缓存)、id 生成器(mysql 的自增 id 不利于分表扩容、使用指定主键,又一定程度降低了写入的效率)、分表代理(按主键 hash)、添加列(非锁表)、动态分表(非锁表)。开发过程中也遇到了一些痛点,本文的重点就放在分布式事务上面。

    事务是本章的基础,假设对事务的概念还不是很清晰的朋友,请止步~~~

    在分布式服务中,有着一个著名的 cap 理论,c - consistency(一致性)、a - availability(可用性)、p - partition tolerance(分区容错性)。简单介绍一下

    一致性:一般我们指强一致性,即对于分布式中多节点,时刻均能保证每个节点上的数据完全一致

    可用性:表示分布式集群对用户提供的服务状态,指时刻都能在用户容忍的时间内保证数据的处理和返回

    分区容错性:指分布式集群中任何分区(节点)发生故障或由于网络的不稳定性,导致部分节点不可用,整个集群依然能提供可用的服务

    一般(Eric Brewer)来讲,对于分布式的 cap 理论,我们无法全面满足所有的条件,仅仅能选择其中的两种作为需求去满足,在当下的一个业务性质开发中,我们往往是抛弃了强一致性,并在业务代码(大部分分布式代理已经支持)中,通过处理保证在一定的时间窗口中,数据的弱一致性。当然分布式的 db 这样的场景,我们当然是不能接受数据的不一致问题,所以必须保证其强一致性,同时,对于集群的可用性,我们也是不能接受某个时间段,db 的完全不可访问。所以在做的过程中,我们使用了 db-pxc 去解决分区容错性的问题,确保整个 db 集群的高可用。

    进入正题,我们使用关系型数据库,难保一个业务逻辑中对多处数据进行修改,那就一定要确保事务性,但分布式事务就是一个不大不小的难题,不大是指业内其实已经有很成熟的方案,不小呢,是指无论是实现这个方案的成本,还是代价,都有可能是超乎想象的~

    2pc 二阶段提交:顾名思义,将原本的 commit 分为两次任务,分别为预提交和提交,通讯方式可以用下图来概括

 

 

  1,pre-commit 阶段

    执行代理层机器作为协调者,对 key 进行分区后,对所有节点先发起预提交的申请,这时候在各个节点会尝试开启事务并执行各自的命令,将操作数据落到 undo 日志中,并根据写入情况返回 yes/no

  2,do-commit 阶段

    上一阶段如果有返回失败,即执行回退命令;如果均返回成功,则执行 commit 命令,各个节点会提交 commit 命令进行写数据到 redo 日志、脏页、binlog 中。到这里才算一个节点事务的真正结束,后返回给协调者 yes/no,并由协调          者确定是否进行回退

    二阶段提交有一些致命的缺陷,比如假设在 do-commit 阶段中,协调者给第一个节点发送 commit 之后,服务异常导致协调者和第一个阶段执行完事后崩溃,就会导致整个集群中其他所有机器进行盲等的一种状态,同时恢复后进行重新选举也都无法恢复数据(因为所有的节点都不知道 pre-commit 的结果,只能进行 rollback),就会导致无法和已经执行 commit 的节点保持数据事务性

    在上述的原因下,提出了 3pc 三阶段提交的解决方案:在 pre-commit 阶段之前在添加一个 can-commit 阶段,不画图来阐述具体流程,每一步几乎与二阶段的提交类似。

  1,can-commit 阶段

    先征求所有节点是否能进行事务操作,返回 yes/no

  2,pre-commit 阶段(与二阶段提交相同)

  3,do-commit 阶段 (与二阶段提交相同)

    三阶段提交的区别主要在于,参与者也存在一定的超时机制,当长时间未接受到协调者的执行,则默认失败,重新触发选举(这里降低了协调者异常后的盲等待时间);添加 pre-commit 缓冲阶段,确保在 do-commit 阶段出现上述的情况后,其他所有阶段依然保留 can-commit 均成功的阶段,则恢复过来后可以重新尝试继续后续的两个阶段,确保数据的一致性

    开发中的痛点:分布式死锁问题,多段提交可以有效避免分布式死锁问题,但往往真正高并发下经常会导致 can-commit / pre-commit 阶段的回退,这种情况如果进行死锁等待,会直接影响到 db 的可用性的评估,所以之前直接使用鸵鸟方式,如果发生锁的抢占直接返回死锁报错。到项目完善一直没有找到一个更好的办法去解决,也就不了了之。有大神有解决方案的还请告知~~~

 

posted @ 2020-05-19 20:42  不想写代码的DBA  阅读(243)  评论(0编辑  收藏  举报