昆仑数据库分布式事务处理机制和原理

zhaowei 提交于 周二, 09/21/2021 - 16:26

昆仑分布式数据库的分布式事务处理机制基于经典的两阶段提交算法并在此基础上增强了其容灾能力和错误处理能力以便做到任意时刻昆仑数据库集群的任意节点宕机或者网络故障超时等都不会导致集群管理的数据发生不一致或者丢失等错误另外全局一致的MVCC也将在0.9版本完成不过这部分内容我们过段时间再详述

之所以要把分布式事务提交分成两阶段就是为了避免部分节点在执行事务提交期间发生故障导致如下的错误这些错误都会导致用户数据丢失或者出错

1. 一个分布式事务的一部分事务分支被提交另一部分事务分支被回滚

2. 应答给客户端事务提交成功但是分布式事务所有分支全部被回滚

3. 应答给客户端事务被回滚但是分布式事务部分或者全部分支被提交

4. 存储节点故障恢复时某个存储节点的事务分支不能被正确地恢复

在上面这些错误源中#4类错误处理由存储节点自身负责分布式事务处理机制负责处理前3类错误也是本文讨论的内容对于第#4类错误笔者曾经在FOSDEM 2021做过一次技术分享https://fosdem.org/2021/schedule/event/mysql_xa/国内视频连接在昆仑分布式数据库 MySQL XA事务处理的容灾技术 https://b23.tv/h7zzmR以后也会陆续撰文详述

经典的两阶段提交算法及其缺陷

两阶段提交(two phase commit,2PC)算法把分布式事务的提交分为preaprecommit两个阶段第一阶段事务管理器GTM发送prepare命令给所有的resource managerRM每个RMprepare分布式事务的本地分支,对于数据库系统来说也就是把它们的WAL日志刷盘以便即使RM宕机在其恢复之后仍然可以提交或者回滚这些prepared状态的事务分支。prepare一个事务之后这个事务进入prepared状态之后既可以commit也可以rollback如果GTM收到所有的RM返回的都是成功那么GTM就执行两阶段提交的第二阶段,即发送commit给每个参与的RM于是RM就提交其prepared状态的事务分支这样就完成了分布式事务的两阶段提交;如果第一阶段执行prepare命令时有RM返回了错误,那么GTM就发送rollback给所有参与者RM,让它们回滚GT的事务分支。

问题是如果两阶段提交流程中发生GTM或者RM宕机等故障那么这个两阶段提交流程就可能中断并且无法正确地继续的问题,导致上述#1,#2,#3描述的错误,这就会导致用户数据不一致或者出错我们昆仑分布式数据库研发团队对这个经典的两阶段提交流程做了优化以便解决这些问题达到坚不可摧的容灾能力

昆仑数据库分布式事务处理模块和组件

一个昆仑分布式数据库集群包含若干个彼此独立且功能相同的计算节点做分布式事务处理也就是本文的主要内容和分布式查询处理还包含若干个存储集群存储用户数据分片每个存储集群使用高可用机制确保节点宕机数据不丢失 还有一个结构与存储集群完全相同的元数据集群它存储着这个集群的关键元数据信息包括本文所说的commit log最后还有一个cluster_mgr模块, 负责维护集群运行状态并且处理因为节点故障而残留的prepared 状态的事务分支后面这部分会在本文详述

昆仑数据库分布式事务处理功能涉及的模块分布在计算节点存储集群和元数据集群和cluster_mgr模块中 1)。计算节点包含全局事务管理器Global Transaction ManagerGTM它掌握着一个计算节点中正在运行的每一个客户端连接Session会话中正在执行的分布式事务GT的内部状态关键信息包括事务GT读写了哪些存储集群storage shard)以及全局事务ID下图中的GT1GT2内部状态为GT1在存储集群1上执行的事务分支T11做了读写操作在存储集群2上执行的事务分支T12做了写入操作GT2在存储集群1上执行的事务分支T21做了只读操作在存储集群2上执行的事务分支T22做了写入操作

计算节点的GTSS后台进程负责成组批量写入全局事务的commit log日志到元数据集群中 昆仑数据库会确保每一个记录了Commit log的全局事务GT都一定会完成提交具体的两阶段提交流程见下文本节先把相关模块介绍完 元数据集群也是一个高可用的MySQL 集群它的commit log记录着每一个两阶段提交的事务的提交决定这些提交决定是给cluster_mgr做错误处理使用的实际生产系统场景下极少会真的用到但是其信息非常关键只有当计算节点或者存储节点发生宕机断电等故障和问题时才会被cluster_mgr用来处理残留的prepared状态的事务分支

存储集群是一个MySQL在存储集群中mysql的会话THD)对象内部包含分布式事务分支简称 XA事务的状态在下图中存储节点1包含分布式事务GT1的事务分支GT1.T11GT2的事务分支GT2.T21的本地执行状态存储节点2包含分布式事务GT1的事务分支GT1.T12GT2的事务分支GT2.T22的本地执行状态

最后是cluster_mgr, 它是一个独立进程借助元数据集群中的元数据与存储集群和计算节点交互辅助它们工作在分布式事务处理这个场景下它负责处理因为计算节点和/或存储节点宕机而残留的prepared状态的事务分支根据每个事务分支所属的全局事务的commit log来决定提交或者回滚其事务分支具体会在下文详述

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

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. 两阶段提交正常运行的时序图

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

 


第一阶段

当客户端/应用发送commit语句时GTM根据分布式事务GT的内部状态选择提交策略 --- GT写入的存储集群少于2个时GT访问过的所有存储集群执行一阶段提交MySQL中这个SQL语句是XA COMMIT ... ONE PHASE在分布式事务做一阶段提交过程中如果发生任意节点宕机那么这些节点本地完成恢复即可正常工作用户数据不会错乱不一致具体来说如果宕机的节点包含那个唯一做过写入的节点WN那么WN完成本地恢复后如果GTWN的事务分支TX被恢复了那么GT的全部改动全部在TX就是生效的否则GT的全部改动全部在TX就没有生效 --- 无论如何GT的原子性都是保持的

如果宕机的节点全部都是GT的只读节点那么GT的任何改动都没有丢失也不会造成GT的状态出错或者数据不一致执行只读事务分支的存储节点重启并完成恢复后那些之前运行中的为只读事务保留的undo log都会被InnoDB自动purge其他之前运行时的内部状态全部在内存中随着重启已经都消失了因此完全可以忽略只读事务一阶段提交的任何错误所以这种情况下对其唯一的写入节点的commit语句可以正常继续执行

GT写入的存储集群不少于2个时GTMGT写入过的所有存储集群执行两阶段提交并且对GT只读访问过的每个存储集群执行一阶段提交执行两阶段提交时第一阶段全部返回成功后才会执行第二阶段的提交XA COMMIT) 命令否则第二阶段会执行XA ROLLBACK命令回滚所有两阶段提交的事务分支

批量写Commit log

在开始第二阶段提交之前GTM会请求GTSS进程为每个GT写入commit log并且等待其成功返回只有成功为GT写入commit logGTM才会对GT开始第二阶段提交否则直接回滚这些prepared状态的事务分支GTM在每个后端进程backend processPostgreSQL术语也就是执行一个用户连接中的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 状态的事务分支

元数据节点会不会成为性能瓶颈

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

经过验证并与我们的预期相符的是100TPS的极高吞吐率情况下元数据集群也完全不会成为性能瓶颈

具体来说1000个连接的sysbench测试满负荷运行时GTSS批量写入commit log的这个组的规模通常在200左右其他的工作负载以及相关参数配置默认cluster_commitlog_group_size = 8 cluster_commitlog_delay_ms=10这个规模可能更大或者更小考虑到每行commit log数据量不到20个字节是与工作负载无关的固定长度),也就是200个存储集群的分布式事务会导致元数据集群执行一个写入约4KB WAL日志的事务那么即使集群整体TPS达到100万每秒元数据集群也只有5TPS每秒写入20MB WAL日志对于现在的SSD存储设备来说是九牛一毛完全可以负担的所以即使存储集群满负荷运行元数据集群的写入负载仍然极低 --- 元数据集群不会成为昆仑数据库集群的性能瓶颈GTSS会最多等待cluster_commitlog_delay_ms毫秒以便收集至少cluster_commitlog_group_size个事务批量发送给元数据集群通过调整这两个参数可以在commit log组规模和事务提交延时之间取得平衡

第二阶段

commit log写入成功后GTSS进程会通知所有等待其commit log写入结果的用户会话连接进程这些进程就可以开始第二阶段提交了

第二阶段中GTM向当前分布式事务GT写入过的每个存储节点并行异步发送提交XA COMMIT)命令然后等待这些节点返回结果无论结果如何断连存储节点故障),GTM都将返回成功给用户因为第二阶段开始执行就意味着这个事务一定会完成提交甚至如果第二阶段进行过程中计算节点宕机或者断网了那么这个事务仍将提交此时应用系统后端也就是数据库的客户端会发现自己的commit语句没有返回直到数据库连接超时通常应用层也会让终端用户连接超时或者返回了断连错误

错误处理

在生产环境的分布式数据库集群的工作场景中通常只有不到0.01%的分布式事务提交会发生错误但是我们仍然需要处理所有可能发生的错误因为哪怕执行了100亿笔事务只要有1笔发生了提交错误都会导致用户数据出错数据库系统就是要确保事务永远正确地提交ACID保障始终成立没有例外这对于分布式数据库系统来说会比单机数据库更加复杂因为可能的错误来源更多多个计算节点和多个存储节点及其之间的网络连接这也是为什么数据库系统的设计和实现会如此复杂而分布式数据库系统的涉及和实现更加复杂

下面我们就看一下昆仑分布式数据库集群如何处理分布式事务提交过程中发生的错误我们分别讲述两阶段提交的每个阶段的错误处理以及批量写入commit log的错误处理

第一阶段

图4. 第一阶段提交失败的处理

4. 第一阶段提交失败的处理

 

如果prepare阶段发生语句错误网络断连或者超时那么GTM会提交rollback记录请求给GTSS并且不等待其返回结果就立刻发送rollback命令给出错的节点并且断连超时的连接然后返回错误给客户端告知客户端该事务GT被回滚GTSS会在commit log中记录GT的提交指令为ROLLBACK, 这样cluster_mgr 随后处理GTprepared事务分支时会回滚它们

Commit logging 错误处理

图5. commit log写入失败的处理

 

5. commit log写入失败的处理

 

如果GTSS写入commit  log 出错或者超时那么GTM会回滚GT的所有preapred事务分支也就是发送XA ROLLBACK GT写入的所有存储集群然后不论其结果如何都返回Aborted’给客户端标明GT被回滚了即使XA ROLLBACK发送失败了那么这个事务分支仍然会按预期被cluster_mgr回滚

第二阶段错误处理

图6. 第二阶段提交失败的处理

 

6. 第二阶段提交失败的处理

 

如果第二阶段发生网络错误或者超时那么仍然返回提交成功给客户端这是因为只要记录了commit log提交的任何分布式事务都必须完成提交如果执行第二阶段期间任何计算存储节点发生宕机或者网络故障那么cluster_mgr进程会根据commit log的指令来处理这些事务分支 --- 如果指令是提交那么就提交GT的所有事务分支如果指令是回滚或者无法找到GTcommit log那么就回滚GT的所有事务分支如果第二阶段进行过程中计算节点宕机或者断网了那么这个事务仍将提交此时应用系统后端也就是数据库的客户端会发现自己的commit语句没有返回直到数据库连接超时通常应用层也会让终端用户连接超时或者返回了断连错误

延时损耗

由于两阶段提交的preparecommit阶段都需要等待存储引擎flush WAL日志并且在两个阶段之间还需要等待commit log写入元数据集群所以两阶段提交的时耗一定比执行相同的SQL DML语句但做一阶段条会增加一些 根据这个性能报告昆仑数据库的两阶段提交在普通的服务器硬件配置和千兆网络情况下会增加约30毫秒的延时这个30毫秒包括了commit log的写入多执行一个阶段的等待时间以及所有这些额外步骤增加的网络通信时间开销等。这样的表现是非常好的,而且在商用服务器硬件和万兆网络环境下,或者在公有云平台等更好的网络和硬件上,可以确定这个延时增加会少于30毫秒

总结

昆仑分布式数据库的分布式事务处理机制确保了分布式事务执行和提交的一致性和容灾能力在事务提交期间任何节点网络故障都不会导致事务的ACID保障失效从而确保了用户数据正确0.9版本中昆仑数据库将支持全局MVCC一致性那时将另外撰文介绍昆仑分布式数据库的全局MVCC的工作机制

添加新评论

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。