乐趣区

关于数据库:分布式事务处理两阶段提交机制和原理


一、背景

笔者在上篇文章中回顾了经典的两阶段提交算法原理及缺点,有趣味可点击查看原文《「技术探讨」经典的两阶段提交算法原理及缺点》,此篇不做详述。

为防止经典的两阶段提交算法缺点的产生,昆仑分布式数据库的分布式事务处理机制基于经典的两阶段提交算法,并在此基础上加强了其容灾能力和错误处理能力。

故此能够做到任意时刻昆仑数据库集群的任意节点宕机或者网络故障、超时等都不会导致集群治理的数据产生不统一或者失落等谬误。

二、昆仑数据库如何分布式事务处理两阶段提交?

昆仑数据库分布式事务处理性能波及的模块散布在计算节点,存储集群和元数据集群和 cluster_mgr 模块中(如图 1)。

2.1 计算节点模块

计算节点蕴含全局事务管理器(Global Transaction Manager,GTM),它把握着一个计算节点中正在运行的每一个客户端连贯(即 Session,会话)中正在执行的分布式事务 GT 的外部状态,要害信息包含事务 GT 读写了哪些存储集群(storage shard),以及全局事务 ID 等。

下图(如图 1)中的 GT1,GT2 外部状态为:

GT1 在存储集群 1 上执行的事务分支 T11 做了读写操作,在存储集群 2 上执行的事务分支 T12 做了写入操作。

GT2 在存储集群 1 上执行的事务分支 T21 做了只读操作,在存储集群 2 上执行的事务分支 T22 做了写入操作。

计算节点的 GTSS 后盾过程负责成组批量写入全局事务的 commit log 日志到元数据集群中。

昆仑数据库会确保每一个记录了 Commit log 的全局事务 GT,都肯定会实现提交。具体的两阶段提交流程见下文详述,本节先把相干模块介绍完。

2.2 元数据集群模块

元数据集群也是一个高可用的 MySQL 集群,它的 commit log 记录着每一个两阶段提交的事务的提交决定。

这些提交决定是给 cluster_mgr 做错误处理应用的,理论生产零碎场景下极少会真的用到,然而其信息十分要害。

只有当计算节点或者存储节点产生宕机、断电等故障和问题时,才会被 cluster_mgr 用来解决残留的 prepared 状态的事务分支。

2.3 存储集群模块

存储集群是一个 MySQL 在存储集群中,mysql 的会话(THD)对象外部,蕴含分布式事务分支(简称 XA 事务)的状态:

在下图(如图 1)中存储节点 1 蕴含分布式事务 GT1 的事务分支 GT1.T11 和 GT2 的事务分支 GT2.T21 的本地执行状态。

在下图(如图 1)中存储节点 2 蕴含分布式事务 GT1 的事务分支 GT1.T12 和 GT2 的事务分支 GT2.T22 的本地执行状态。

2.4 cluster_mgr 模块

cluster_mgr 是一个独立过程,借助元数据集群中的元数据,与存储集群和计算节点交互,辅助它们工作。

在分布式事务处理这个场景下,它负责解决因为计算节点和 / 或存储节点宕机而残留的 prepared 状态的事务分支,依据每个事务分支所属的全局事务的 commit log 来决定提交或者回滚其事务分支(具体会在下文详述)。

图 1. 昆仑数据库分布式事务处理波及的功能模块和组件

三、如何实现两阶段提交?
在用户发送 begin transaction 给计算节点时,计算节点会在其外部开启一个新的分布式事务 GT 对象(GTM 会为这个分布式事务 GT 建设外部状态)。

而后在 GT 事务运行期间首次读写一个存储集群时,GTM 会发送蕴含 XA START 在内的若干条 SQL 语句启动 GT 在这个存储集群中的事务分支,并初始化事务状态。

而后发送 DML 语句来读写数据。计算节点会对收到的 SQL 语句做解析、优化、执行并计算应该向哪些指标分片发送什么样的 SQL 语句实现部分数据读写工作,只读写的确含有指标数据的存储集群。

计算节点与一组存储节点的通信总是做异步通信,确保存储节点并发执行 SQL 语句(不过这部分内容不是本文探讨的重点)。

在一个分布式事务 GT 执行 commit 之前如果产生了昆仑数据库集群中的节点、网络故障或者存储节点的局部 SQL 语句执行出错,那么计算节点的 GTM 会回滚事务 GT 及其在存储节点上的所有事务分支,GT 相当于没有被执行过,它不会对用户数据造成任何影响。

上面详述计算节点执行客户端发送 commit 的语句的分布式事务提交流程。事务提交的失常状况流程(时序图)见图 2。

图 2. 两阶段提交失常运行的时序图

3.1 第一阶段
当客户端 / 利用发送 commit 语句时,GTM 依据分布式事务 GT 的外部状态抉择提交策略(当 GT 写入的存储集群少于 2 个时,对 GT 拜访过的所有存储集群执行一阶段提交)。

在 MySQL 中这个 SQL 语句是 XA COMMIT … ONE PHASE;在分布式事务做第一阶段提交过程中如果产生任意节点宕机,那么这些节点本地实现复原即可失常工作,用户数据不会错乱、不统一。

具体来说,如果宕机的节点蕴含那个惟一做过写入的节点 WN,那么 WN 实现本地复原后,如果 GT 在 WN 的事务分支 TX 被复原了,那么 GT 的全副改变(全副在 TX 中)就是失效的,否则 GT 的全副改变(全副在 TX 中)就没有失效(无论如何 GT 的原子性都是放弃的)。

如果宕机的节点全部都是 GT 的只读节点那么 GT 的任何改变都没有失落,也不会造成 GT 的状态出错或者数据不统一。

执行只读事务分支的存储节点重启并实现复原后,那些之前运行中的只读事务残留的 undo log 都会被 InnoDB 主动革除,其余之前运行时的外部状态全副在内存中,随着重启曾经都隐没了,因而齐全能够疏忽只读事务一阶段提交的任何谬误。

所以这种状况下,对其惟一的写入节点的 commit 语句能够失常继续执行。

当 GT 写入的存储集群不少于 2 个时,GTM 对 GT 写入过的所有存储集群执行两阶段提交,并且对 GT 只读拜访过的每个存储集群执行一阶段提交。

执行两阶段提交时,第一阶段全副返回胜利后才会执行第二阶段的提交(XA COMMIT) 命令,否则第二阶段会执行 XA ROLLBACK 命令回滚所有两阶段提交的事务分支。

3.2 如何批量写 Commit log?
在开始第二阶段提交之前,GTM 会申请 GTSS 过程为每个 GT 写入 commit log 并且期待其胜利返回。

只有胜利为 GT 写入 commit log 后 GTM 才会对 GT 开始第二阶段提交,否则间接回滚这些 prepared 状态的事务分支。

GTM 在每个后端过程(backend process 是 PostgreSQL 术语,也就是执行一个用户连贯中的 SQL 语句的过程,每个用户连贯绑定一个后端过程)中会把每个要开始第二阶段提交的分布式事务的 ID 等要害信息放入 GTSS 的申请队列而后期待 GTSS 告诉申请实现。

GTSS 会把申请队列中所有的 commit log 写入申请转换为一条 SQL insert 语句发给元数据集群,该集群执行 insert 语句实现 commit logging 并向 GTSS 确认胜利,而后 GTSS 即可告诉每一个期待着的后端过程开始第二阶段提交。

如果 commit log 写入失败那么计算节点会发送回滚命令(XA ROLLBACK) 让存储集群回滚 GT 的事务分支。

如果 commit log 写入超时那么计算节点会断开与存储集群的连贯以便让 cluster_mgr 预先解决。

所有确认写入 commit log 的分布式事务肯定会实现提交,如果产生计算节点或者存储节点故障或者网络断连等,那么 cluster_mgr 模块会依照 commit log 的指令来解决这些 prepared 状态的事务分支。

3.3 元数据节点会不会成为性能瓶颈?
肯定会有读者放心,把所有计算节点发动的分布式事务的 commit log 写到同一个元数据集群中,那么元数据集群会不会成为性能瓶颈?会不会呈现单点依赖?

通过验证并与咱们的预期相符的是:在 100 万 TPS 的极高吞吐率状况下,元数据集群也齐全不会成为性能瓶颈。

具体来说,在 1000 个连贯的 sysbench 测试满负荷运行时,GTSS 批量写入 commit log 的这个组的规模通常在 200 左右,其余的工作负载以及相干参数配置(默认 cluster_commitlog_group_size = 8 和 cluster_commitlog_delay_ms=10)下,这个规模可能更大或者更小。

思考到每行 commit log 数据量不到 20 个字节(是与工作负载无关的固定长度),也就是 200 个存储集群的分布式事务会导致元数据集群执行一个写入约 4KB WAL 日志的事务,那么即便集群整体 TPS 达到 100 万每秒,元数据集群也只有 5 千 TPS,每秒写入 20MB WAL 日志,对于当初的 SSD 存储设备来说是沧海一粟,齐全能够累赘的。

所以即便存储集群满负荷运行,元数据集群的写入负载依然极低(元数据集群不会成为昆仑数据库集群的性能瓶颈)。

GTSS 会最多期待 cluster_commitlog_delay_ms 毫秒以便收集至多 cluster_commitlog_group_size 个事务批量发送给元数据集群。

通过调整这两个参数能够在 commit log 组规模和事务提交延时之间获得均衡。

3.4 第二阶段
当 commit log 写入胜利后,GTSS 过程会告诉所有期待其 commit log 写入后果的用户会话(连贯)过程,这些过程就能够开始第二阶段提交了。

第二阶段中,GTM 向以后分布式事务 GT 写入过的每个存储节点并行异步发送提交(XA COMMIT)命令,而后期待这些节点返回后果。

无论后果如何(断连,存储节点故障),GTM 都将返回胜利给用户。因为第二阶段开始执行就意味着这个事务肯定会实现提交。

甚至如果第二阶段进行过程中计算节点宕机或者断网了那么这个事务仍将提交,此时利用零碎后端(也就是数据库的客户端)会发现自己的 commit 语句没有返回直到数据库连贯超时(通常应用层也会让终端用户连贯超时)或者返回了断连谬误。

四、总结

笔者和团队在昆仑分布式数据库中的两阶段提交形式,能够胜利防止经典的两阶段提交算法的缺点。

而在此分布式事务处理两阶段提交机制和原理上,笔者和加强其容灾能力和错误处理能力,能够做到任意时刻昆仑数据库集群的任意节点宕机或者网络故障、超时等都不会导致集群治理的数据产生不统一或者失落等谬误。

下篇文章详述《分布式事务对于两阶段提交的错误处理》,届时欢送探讨~

END

退出移动版