绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
TDSQL-XA简介
2020-05-19 14:28:34

本文是我初于2017年4月发表在我的个人微信公众号里面,现发布在这里。今天从我的公众号里面贴进来了好多篇mysql xa相关的文章,都是我在2016年下半年到2017年底之设计和实现TDSQL的分布式事务处理功能期间发现和解决的问题。事实上之本文发表之后,我们的测试团队又找到了几个tdsql xa方面的bug,我都修复了。所以文末的bug列表其实只是一部分。


概述

TDSQL是腾讯TDSQL团队基于mysql分支(mariadb/percona)为金融联机交易场景推出的强一致分布式数据库集群系统,支持自动扩容,自动容灾等,在公司内部多个部门广泛使用和验证,对应腾讯公有云的入口是:CDB for MariaDB和DCDB for TDSQL。TDSQL-XA是支持分布式事务处理的TDSQL 版本,经过TDSQL团队长期的开发和验证,在不久的将来也会在云上对外提供服务。
很多用户认识到TDSQL XA基于MySQL的实现有诸多优势,包括国内外MySQL社区的持续的、强大的数据库内核开发能力,丰富的技术资源和人力资源积累,以及MySQL系列数据库经历了互联网时代非常广泛的和20多年时间的实际应用的考验和验证等等,因而对试用和测试TDSQL XA有很大的兴趣,详见文末。
应内外同行和用户的要求,本文将介绍TDSQL XA实现原理。TDSQL-XA重要的特性就是完全支持分布式事务处理,并且能够很好地完成容灾恢复。本文首先介绍TDSQL的架构,然后介绍TDSQL-XA中分布式事务处理的核心算法以及遇到的问题及其解决。

分布式事务处理简介

数据库分布式事务,就是一个数据库事务在多个数据库实例上面执行,并且多个实例上面都执行了写入(insert/update/delete) 操作。实现分布式事务处理的大难点,就是在这些多个数据库实例上面实现统一的数据库事务的ACID保障,而这里面重要的算法就是两阶段提交算法,这也是本文的主要内容。
分布式事务处理的开销比单DB实例上的事务处理开销要大一些,使用分布式事务会导致系统TPS降低,事务提交延时增大。所以是否使用分布式事务要根据实际应用需求来定:如果应用数据量并不大,一个DB实例就装得下并且一台机器的性能足以应对数据访问负载,那么就不需要使用分布式事务,从而获得更好的性能。
如果数据量非常大或者数据访问负载非常高,不得不使用分库分表把存储和负载分摊到多个DB实例(实际中一个集群的不同DB实例部署在不同的机器上面),并且应用需求确实要求有一些事务是需要写入到多个DB实例,那么使用分布式事务就会大大降低应用开发难度。使用了支持分布式事务处理的数据库系统,对应用开发者来说,他并不需要考虑这个事务会访问一个还是多个DB实例这件事,每个事务的查询语句的写法与使用单实例DB完全相同,就总可以获得事务的ACID保障。在使用分库分表技术的情况下,如果没有分布式事务支持,那么数据库访问中间件(比如之前的tdsql的网关,mycat等)通常需要限制事务只写入一个DB实例中,以避免数据不一致的问题(或者是伪分布式事务);在这种情况下为了实现一个转账功能,就要设计特殊并且巧妙的技巧(比如 黑名单技术)来确保转账功能不会出现数据不一致等问题。但是这样做对应用开发者的技术水平要求很高,应用开发难度大,开发调试时间大大增加。再考虑到实际上不同类型的业务类型可能需要设计不同的技术来解决数据一致性问题,这进一步增加了开发成本和技术门槛,这基本上是业内大多数公司会望而却步的。在这些情况下,使用分布式数据库系统,就显得非常有必要了。

TDSQL架构介绍

为了方便说明,使用下图辅助说明TDSQL主要模块的功能和工作机制。TDSQL的全部架构比下图还要复杂一些,本文只介绍XA直接相关的模块和功能,其他模块并不在此列出和介绍。







网关(gateway)

首先,GW是TDSQL基于mysql_proxy改造的网关(gate way),它接收用户的连接请求,并与后端mysql数据库建立连接;同时它接收每个连接上的SQL语句,然后发送给后端的mysql数据库,然后汇总后端返回的查询结果(如果是insert/update/delete那么通常不需要汇总),后返回终结果给客户端。网关可以用两种模式工作,一种称为noshard,此模式下网关几乎完全不处理(不解析)任何SQL语句,直接将用户请求数据包送给对应后端mysql的连接,从中接收结果再原封不动返回给客户端。此时后端只可以有一个mysql数据库实例与网关相连。另一种模式称为在group shard模式下,GW会解析这些SQL语句,并且把拆分后的SQL语句发送到后端的mysql数据库。一个TDSQL集群中,可以部署任意多个网关实例,每个实例是一个进程,都可以接受用户连接请求并且返回结果,它们相互不知道对方;并且网关是没有持久状态的,因而不需要考虑网关的crash safety和数据一致性。这样做使得网关的可扩展性可以无限增大。

MySQL SET与强同步

TDSQL后端DB是mysql系列的数据库,目前正式使用的是基于MariaDB-10.1.9改造的版本。由于MariaDB-10.1.9对分布式事务的支持远不如MySQL-5.7.17完善,所以开发TDSQL-XA时我们使用Percona-server-5.7.17-11。后端mysql数据库通常做一主两备的配置,这样3个数据库实例整体称为一个set。 如上图{M1,S11,S12} 是一个set,本文中为了方便说明,命名它为set1;{M2,S21,S22} 是另一个set,本文中命名为set2。 在TDSQL中,主备做强同步复制,确保每一个在主机上确认提交成功的事务的binlog,一定已经被至少一个备机接收并且写入relay log文件。具体做法是,主机执行commit语句时候完成group commit的binlog sync阶段后,会等待备机确认收到这个事务的binlog,而备机只有收到一个事务的完整的binlog并且flush&sync到relay log后,才会发送确认消息给主机。收到备机的确认后主节点上面用户的commit语句才会返回给用户确认提交成功。
这样一旦主节点消失了,那么拥有新binlog的备机就被选为主节点,在成为主节点之前它会执行完毕所有relay log上面收到的事件,因此其数据是新的,含有了所有已经向用户确认完成提交的事务的数据。后续如果消失的主节点重新加入集群,那么它仍可以作为备机运行,不过如果此时它有多余的已提交事务(它crash时候等待强同步确认的事务没能够等到备机确认收到这些事务的binlog),那么这些事务会被闪回。这些事务虽然已经在mysql内部完成提交但是TDSQL并没有向用户确认提交成功,因此闪回了也并没有破坏数据库的ACID属性。闪回工具基于binlog生成做逆操作的SQL语句并执行这些语句,它与数据库回滚并不同,因为这些事务其实在数据库内部已经提交完成了。
TDSQL的强同步与官方的半同步复制插件不同,这里的强同步等待是我们完全重新实现的,它的性能比官方半同步有大幅提高,主要是因为:
首先,以mysql-5.7为例,使用官方半同步插件时,如果wait_point是AFTER_SYNC并且binlog_order_commit=true时,一个事务提交时做半同步等待如果长时间收不到备机的ack的话(设置半同步等待超时很长时间),3阶段的提交队列中位于SYNC阶段队列的事务即使全部完成了sync binlog,也无法进入COMMIT阶段队列,只能阻塞等待,于是位于flush阶段的事务即使完成flush binlog,也无法进入sync阶段,而开始binlog提交的事务也无法进入flush队列。这样一个事务阻塞等待半同步ack就可能导致很对事务也无法继续事务提交进程,所以系统的TPS很容易会因为备机ack返回不够及时而降低。
另外,TDSQL的强同步等待不会占用工作线程,做的是异步等待,而mysql/mariadb的半同步等待都是在工作线程中做同步等待,这样也会损失性能。

Agent

后,每个mysql实例在本机上面对应有一个agent程序,用于监控这个mysql实例的运行状况,以及完成TDSQL集群管理所需的本地任务。在XA中我们赋予这个agent进一步的职责,详见下文。

网关的Group shard 查询处理

网关的后端在group shard模式下有多于一个set与它相连。以一个多行插入语句为例,如果网关发现该语句中一些行映射到了set1另一些行映射到了set2,那么网关就把应该发送到set1/set2的语句分别组成一个新的insert语句,然后分别发给set1/set2。这就要求在创建表的时候,用户需要指定一个列做shardkey,然后网关会解析这个特殊的建表语句从而记录这个group shard表的shard key用户后续做set映射。TDSQL的网关扩展了建表语句的语法,允许用户指定shardkey列。对于update/delete语句,如果在where条件中指定了shard key,那么直接发送拆分后的语句到一个或者多个set;否则发送原语句到所有set。后,TDSQL在group shard模式下还允许创建广播表,这样的表在后端的每个set上面都有一份,向这样的表插入的每一行都会被广播到每个set上面,这样所有set上面这个表是完全相同的。
这里描述的是TDSQL-XA中网关的工作方式,事实上在实现了分布式事务处理之前,网关默认情况下不允许在一个事务中向多个set发送insert/update/delete语句(用户可以自担风险打开开关),因为那样做的话,在事务提交过程中做的还是1阶段提交,如果某个set提交失败了,那么这个分布式事务就处于不一致的状态,因为其修改在部分set上面提交了,在另外一些set上面回滚了。现在TDSQL有了分布式事务功能后,这么做就是安全的了。
网关对于select语句的处理也类似上面对insert/update/delete的处理 ---- 指定了shardkey就发送到对应set,否则发送到全部set。不过网关需要依次发送每个set返回的select结果给客户端。而且炫酷的是,我们TDSQL的网关还支持在select语句中使用group by, order by,并且其实现方式非常高效,不需要缓存后端set返回的查询结果,而是以流式处理来收集后端set的结果实现分组和排序。另外网关现在还支持两个sharded表使用shard key做等值连接,以及使用shard key的子查询,例如:
Select*from t1, t2 where t1.shardkey=t2.shardkey;
Select * from t1 where t1.shardkey in (select t2.shardkey where t2.shardkey > 100);
因为这两种特殊情况下,网关只需要把这个语句发送给每个set去执行,然后把全部结果返回给客户端即可。

TDSQL分布式事务处理算法概要

首先,MySQL已经支持分布式事务处理了,是一个可用的RM(2PC算法中的术语) ,它支持XA START/XA END/XA PREPARE/XA COMMIT/XA ROLLBACK语句;外部客户端(TDSQL XA中就是网关)给mysql发送这些命令就可以完成一个mysql数据库实例上面的分布式事务分支的事务启动与提交流程。因此网关只要发送这些XA事务命令给mysql即可,mysql就应该能够完成做为RM需要做的事情(不过其实它还是有不少bug)。后文会列举mysql/mariadb在这个方面的区别以及问题。
由于TDSQL的网关在group shard模式下已经能够解析SQL语句,因此在此基础上实现分布式事务处理是直接了当的。在网关中要实现的就是分布式事务两阶段提交算法中的协调器(coordinator) ---- 只要在网关中维护每个全局事务的状态,记录好每个全局事务写入的set,然后在提交阶段做两阶段提交即可。网关在执行一个事务的insert/update/delete语句时,会记录这个语句修改了哪个set;并且次访问一个set时会发送一个XA START在这个set上面启动事务分支。用户发送commit语句提交事务时,网关如果发现这个全局事务只对0个或者1个set做了insert/delete/update,那么直接做1阶段提交也就是执行xa commit one phase --- 由于在事务开始时候无法知道到底会做2PC还是1PC,所以网关发给后端的事务启动语句总是执行xa start来开启一个事务,于是如果做1阶段提交也不能发送commit给后端而是发送xa commit one phase;只有网关写入了多于1个set时候,才做两阶段提交 --- 此时网关首先发送xa prepare ‘gtid’ 给在该全局事务中参与写入的set,收到全部这些set的成功确认后,写入这个全局事务对应的commit log,再发送xa commit ‘gtid’ 给这些后端set。如果有set返回了错误,或者写入commit log失败,那么网关发送 xa rollback ‘gtid’ 给所有参与这个全局事务的set,这样这个全局事务就回滚了。
为了避免网关也需要容灾支持,我在初的原型实现中,把全局事务的commit log记录到了zookeeper上面,这样网关就是一个没有持久状态的组件,也就不需要实现对网关的容灾,网关在任何时候crash或者丢失,那么丢失的都只是其上的连接中运行期的事务。这么做很方便因为TDSQL本来已经使用zookeeper做一些元数据存储。后来改为在后端set当中存储commit log,这样就不会在关键路径上依赖zookeeper。所有写入了commit log的全局事务,一定会提交成功,通常是网关发送 xa commit完成提交的,但是在网关或者后端set因为各种原因消失的灾难情况下,终agent会完成本地mysql上面prepared事务的提交,如果agent发现commit log上决定提交一个全局事务但是它的本地分支仍然处于prepared状态,它就会提交这个分支。Master上面的Agent也会回滚超时未被提交的prepared本地事务,如果commit log上面没有这个事务的提交决定的话。
要强调的是,TDSQL XA中,网关发送xa prepare语句和xa commit语句后,都是做了强同步等待的,也就是说可以确保这个事务的binlog已经存储到了至少一个备机的relay log中,因此在使用工作正常无误的mysql版本的前提下,提交过程中发送主备切换并不会丢失prepared事务,也不会丢失终提交的事务。不过实际情况是percona在这方面也有很多bug,详见下文。
基于对原型版本的进一步的分析论证后,在概念验证原型的基础上,我们做了一些设计修改。首先我们决定把commit log放到后端一个set(这个分布式事务读或者写的个set)上面,同时支持了在提交超时后,让用户自行检查提交状态等功能。把Commit log放入后端set,就可以直接使用到TDSQL已有的容灾功能,这样commit log就是安全的了,也不用担心zookeeper的性能和容灾问题。当然,这样也意味着分布式事务提交过程多了一个访问后端set的步骤 --- 写入commit log。不过,这个步骤是批量完成的 --- 网关后台线程会汇集正在提交的分布式事务然后在独立的连接和事务中完成对每个set的写入,并且每个事务的commit log只写入一个set中,因而这个开销并没有显著增加事务的提交耗时或者降低TPS。

TDSQL XA容灾测试

基本的功能测试不在此赘述,只介绍我们的典型的功能测试 ---转账。测试程序先灌入数据 ---- 大量虚构的账户信息,然后发起大量连接,并发执行大量的转账事务,然后定期停止测试线程,确认所有账户的余额总和保持不变,并且确认主备节点上面每一行相同,以及确认主备机正常运行。在此基础上,测试程序在测试运行期间,大量地频繁地随机kill掉master的mysqld, slave的mysqld进程,网关进程,agent,测试程序自身,等等。然后确认上述余额总和保持不变以及主备上面数据一致。 这个经典测试帮助我们发现了多个mysql和TDSQL XA 的bug。

MySQL 在XA方面的bug 以及解决

我初做的概念验证原型使用的后端mysql仍然是mariadb(TDSQL-MARIADB-10.1.9),但是mariadb-10.1.9对XA的支持有很多问题,而经过对比验证后我发现mysql-5.7在这方面有了很大进步,所以决定使用percona-server-5.7 版本。这两者在XA事务支持方面的对比我在之前写过公众号文章详述过。但没想到的是,我后来才发现,mysql-5.7的XA支持也是远远没有做到完美,我用了大量时间来修复它的bug。附录中是其中一部分bug。
我们的容灾测试(如上所述)中发现了mysql5.7 XA的很多个bug(大多与容灾有关):当强同步,主备切换,闪回等功能与XA融合的时候,percona-server-5.7也暴露出了很多个bug;在分布式事务处理与replication 的结合地带,有很多bug。从中可以看出,在TDSQL XA之前,MySQL可能从没有真正被用来组成支持分布式事务的分布式数据库集群在生产系统中使用,或者说如果使用了的话,那么那些分布式数据库系统都存在着极大的可靠性隐患。如果不考虑灾难情况,那么系统一切运行正常,但是在灾难情况下(比如主、备机消失(断电,crash,网络隔离,硬件故障等),主、备存储介质失效等)可能发生数据丢失,数据不一致等严重问题。所以容灾能力是检验一个分布式数据库系统的可靠性和整体质量的试金石。我已经向mysql官方团队报告了一些bug,另一些也会后续报告给mysql官方。大多数发现的bug我已经修复或者想办法规避了。这里规避的方法主要是在网关和agent等模块实现功能来处理mysql没有正确处理的事情。以后我会将这些bug都报告给官方,并提交修复patch。由于我们做了大量长期的大强度的功能,性能和容灾测试,所以我们现在对TDSQL-XA 的功能正确性,稳定性和容灾能力有了很强的信心。

外部用户的试用测试

目前已经有国内多家大型银行和金融机构在测试TDSQL XA,有些用户是主动联系我们要求测试和试用,有些用户已经帮我们提出了很珍贵的意见和问题。这让我们感到责任重大,也有了很强烈的自豪感和成就感。我们会持续不断努力,把这TDSQL XA 做好!

附录

我报告给mysql官方的bug列表:
bugs.mysql.com/bug.php?
bugs.mysql.com/bug.php?
bugs.mysql.com/bug.php?
bugs.mysql.com/bug.php?
bugs.mysql.com/bug.php?
这些bug每一个可以造成的伤害都非常大!会导致丢失事务,备机卡死等问题。更多的bug我会在不久的将来报告给mysql官方开发团队。另外,我会在以后逐步撰文讲解TDSQL-XA的更多功能的设计和实现,包括全局死锁处理,事务隔离级别以及容灾恢复机制等等。


分享好友

分享这个小栈给你的朋友们,一起进步吧。

TDSQL专区
创建时间:2020-05-19 14:26:20
一键解锁TDSQL全部资料。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 小雨滴
    专家
戳我,来吐槽~