全⾯解析腾讯数据库TDSQL架构
腾讯计费平台部托管着公司90%以上的虚拟账户,如QB、Q点、包⽉服务、游戏的⼆级账户等,为了保证能顺畅⽀撑公司各⼤业务的实时在线交易,并且在各种灾难场景下数据是⼀致并且可⽤的,对系统的可⽤性、⼀致性切换要求⾮常⾼,因此计费团队历来都⾮常重视⾼⼀致性存储系统的建设。
到⽬前为⽌,计费⾼⼀致性存储层的解决⽅案⼤致经过了3个阶段,本⽂将分享最新的基于MySQL的分布式解决⽅案。
随着业务的发展,基于内存的NoSQL解决⽅案HOLD平台在⾼峰期⼀天⽀撑3000亿读写,证明了分布式Cache的巨⼤价值;但随着各种业务的接⼊,NoSQL⽅案的不⾜也逐步显现出来了,如下所⽰。
1、适⽤的业务场景⽐较有限,仅提供get/set操作,有不少业务场景希望能通过记录中的其他字段做索引来查询,⽐如流⽔类业务。
2、不是所有的数据都是热点,⼀台64GB内存机器提供的有效内存空间⼤概在50GB左右,⽽采⽤Fusion卡的机型容量⼀般在1TB以上,对⽐起来,如果所有数据放⼊分布式Cache明显是⼀种极⼤的浪费,最合理的当然是热点在HOLD,冷数据采⽤基于磁盘的存储。
3、计费平台部多年来在⽀付领域有了相当多的技术积累,HOLD作为NoSQL系统功能有限,因此建造⼀套更加强⼤通⽤的⾼⼀致性存储系统将整个⽀付领域的实时数据(重点是账户数据、⽤户订单数据,以及海量的流⽔数据)统⼀管理起来⾮常有价值。
基于上⾯的分析,结合我们在MySQL领域多年的应⽤和优化经验,最终决定在MySQL存储引擎基础之上,打造⼀套分布式的SQL系统。
1、保持原来的MySQL协议,这样以前访问MySQL系统的C++、Java各类系统都不需要修改,DBA能继续保持原来⼤部分使⽤习惯。
2、⾃动的跨IDC容灾切换,同时保证数据⼀致性,对于提交成功的事务保证⼀笔不丢,达到银⾏级对容灾的要求。
3、灵活的容量伸缩机制,对业务透明,解决MySQL本⾝扩容不灵活的问题。
4、重点⽀持OLTP类型的在线业务。
整体架构
针对上⾯的需求,TDSQL最终的结构如图1所⽰(与当前⼤部分中⼼化的分布式系统类似)。
图1 TDSQL架构
系统由三个模块组成:Scheduler、Agent、⽹关,三个模块的交互都是通过ZooKeeper完成,极⼤简化了各个节点之间的通信机制,相对于第⼆代HOLD的开发简单了很多。
Scheduler作为集的管理调度中⼼,主要功能包括:
♦  管理set,提供创建、删除set、set内节点替换等⼯作;
♦  所有的DDL操作统⼀下发和调度;
♦  监控set内各个节点的存活状态,当set内主节点故障,发起⾼⼀致性主备切换流程;
♦  监控各个set的CPU、磁盘容量、各个表的资源消耗情况,必要的时候⾃动发起扩容流程;
♦  Scheduler⾃⾝的容灾通过ZooKeqzer的选举机制完成,保证中⼼控制节点⽆单点。
Agent模块负责监控本机MySQL实例的运⾏情况,主要功能包括:
♦  ⽤短连接的⽅式周期性访问本机的MySQL实例,检测是否可读、可写,若发⽣异常,会将异常信息上报到ZooKeeper,最终会由上⾯描述的Scheduler模块检测到这个异常情况,从⽽发起容灾切换;
♦  检测主备复制的执⾏情况,会定期上报主备复制的延时和延迟的事务数,若发⽣了主备切换,⾃动向
新主机重建主备,因此MySQL的主备不需要DBA⼲预,对于新增的实例会⾃动采⽤xtrabackup通过主机⾃动重建数据;
♦  检测MySQL实例的CPU利⽤率和各个表的请求量、数据量、CPU利⽤率,上报到ZooKeeper,ZooKeeper通过全局的资源情况抉择如何扩容、缩容;
♦  监控是否有下发到⾃⾝的扩容任务,如有则会执⾏扩容流程(下⾯会有描述);
♦  监控是否要发⽣容灾切换,并按计划执⾏主备切换流程。
⽹关基于MySQL Proxy开发,在⽹络层、连接管理、SQL解析、路由等⽅⾯做了⼤量优化,主要特点和功能如下:
♦  解析SQL,将识别出的DDL语句直接存到ZooKeeper,让Keeper来统⼀调度;
♦  Watch ZooKeeper的路由信息,拉取最新的路由表保存到本地⽂件和内存;
♦  将SQL请求路由到对应的set,⽀持读写分离;
♦  对接⼊的IP、⽤户名、密码进⾏鉴权;
♦  记录完整的SQL执⾏信息,与秒级监控平台对接完成实时的SQL请求的时耗,成功率等指标监控分析;
♦  对count、distinct、sum、avg、max、min、order by、group by等聚合类SQL⼀般需要访问后端的多个set,⽹关会分析结果并做合并再返回,暂不⽀持跨set join和分布式事务;
♦  ⽹关⽆状态,既⽀持与业务部署到⼀起,也可以独⽴部署(可通过TGW或者LVS做容灾)。
⾃动扩容机制
oracle数据库表结构怎么看⽬前,针对MySQL的扩容,⼀般有下⾯两种策略。
♦  垂直扩容。⼀般通过升级硬件来实现,⽐如更换更好的CPU,将传统的sas盘换成FusionIO卡这类,然后针对新硬件调整好参数,在硬件结构变化⽐较⼤的时候,性能甚⾄能达到上⼗倍的提升。但垂直扩容有⽐较⼤的局限,就是这种模式随着业务的突增还是⽐较容易达到瓶颈,特别是⾯对互联⽹海量⽤户的时候,所以在互联⽹应⽤场景下,⼀般仅将垂直扩容当做⼀个辅助的⼿段。
♦  ⽔平扩容。常⽤的有2种⽅法,⼀是不同的库或者表部署到不同的实例,⼆是⼀张表需要根据某个字段拆分到不同的字表中(数据分⽚),这种策略在互联⽹系统中⾮常常见,很多系统会将这2种⽔平扩容的⽅法结合起来使⽤;
通过上述2种扩容⽅法的⽐较,为了应对海量扩展的需求,应该是重点选⽤⽔平扩容的⽅法。但⽔平扩容的实现⼀般对业务是有感知的,⽐如采⽤什么规则来拆表,拆开的表放到哪些节点,如果某个⼦表还有瓶颈应该怎么扩容,扩容是否还需要业务配合等等这些事情如果全部交给业务会⽐较繁琐,因此这些需求应该尽量全部交给TDSQL⾃⾝来完成,对业务完全透明。
分表逻辑
在TDSQL中,每个表(逻辑表)可能会拆分成多个⼦表(建表的时候通过在建表语句中嵌⼊注释的⽅式提供⼀个shard字段名,最多会拆分出1W个⼦表),每个⼦表在MySQL上都是⼀个真实的物理表,这⾥称为⼀个shard,因此⼀张表的数据可能会按这样的⽅式分布在多个Set 中,如图2所⽰
图2 TDSQL的逻辑表
每个SQL请求到达⽹关之后,⽹关会做词法和语法解析,重点会解析出shard字段,如果带了shard字段
就可以直接查询路由表并发送到某个具体的set中。计费的OLTP类业务99%的请求都会带上shard字段;如果某笔请求没有shard字段,查询路由之后会将请求发送到所有的shard对应的set中,并对所有返回的结果做⼀些聚合运算。
扩容流程
上⾯描述了shard的⽅式,但是这样的shard结构不是固定不变的,当Scheduler检测到某个set,某个表的CPU、磁盘超过阈值之后就会启动扩容流程。
这⾥描述下具体的扩容流程。扩容过程中⼀般都要尽量避免影响业务,⽬前来看存在2种⽐较成熟的策略。
策略1先切后搬:先修改路由,将需要迁⾛的数据的请求直接发送到新set,在新set交易过程中如发现本地的数据不存在,则去原set拉取数据,然后再通过⼀些离线的策略将要迁移的数据全量再搬迁⼀次,HOID平台就是采⽤这样的策略。
策略2先搬后切:让请求继续在原set交易,扩容程序⾸先记录⼀个binlog位置点,并将源set中符合迁移条件的数据全部迁移出去,最后再将搬迁过程中新增的binlog追完,最后修改路由规则,将请求发送到新set。
综合来看,策略1最⼤的优点是假如是因为压⼒⼤做的迁移,可能很快就能将部分请求发送新set了,实现对原set的压⼒分担;策略2实现上在最后的追路由阶段需要更多的精细化控制,实现会稍微复杂点,但策略2有个⾮常⼤的好处就是扩容过程中回滚⾮常⽅便,如有异常直接⼲掉扩容任务即可。
对于TDSQL这类数据库业务系统来说,策略1实现会⾮常⿇烦,因为请求到达新set之后可能需要去源set拉取数据,这个需要对MySQL本⾝进⾏修改;另外假如⼀个批量更新的update操作,可能要往新⽼set都发送⼀次请求,⽐较复杂,所以最终选择了策略2。策略2会有更⼤的通⽤性,开发模式基本上可以统⼀到所有类似的系统。
下⾯描述采⽤策略2具体的扩容流程。假如要将Set1中的t_shard_1的数据迁移⼀半到Set4中的t_shard_4(1667-3333)。
图3 策略2的扩容流程
Scheduler⾸先在Set4中创建好表t_shard_4。
后将扩容任务下发到Set1中的agent模块,agent检测到扩容任务之后会采⽤mysqldump+where条件的
⽅式将t_shard_1中shard号段为1667-3333的记录导出来并通过管道⽤并⾏的⽅式插⼊到Set4(不会在本地存⽂件,避免引起过多的IO),⽤mysqldump导出镜像的时候会有⼀个binlog位置。
从mysqldump记录的binlog位置开始读取binlog并插⼊到到Set4,追到所有binlog⽂件末尾的时候(这需要⼀个循环,每次循环记录从开始追binlog截⽌到追到⽂件结尾消耗的时间,必须保证追单次循环要在⼏秒之内完成,避免遗留的binlog太多导致最后⼀次追binlog消耗太多的时间,从⽽影响业务过久),对原来的表t_shard_1重命名t_shard_5,此时针对这个表不会再有新请求,若还有请求过来都会失败,然后再追⼀次binlog到⽂件结尾(因为上⾯的循环保证了追binlog不会太耗时间了,所以此次会快速完成),然后上报状态到ZooKeeper,表明扩容任务完成。
Scheduler收到扩容完成的信息之后会修改路由表,最后由⽹关拉取到新路由完成整体的扩容;从表重命名开始到⽹关拉取到新路由,这段时间这个原始shard不可⽤,从我们测试结果来看这个不可⽤的时间是200毫秒左右;如果某个⽹关异常,拉取不到新路由,继续访问⽼表
t_shard_1会⼀直失败,这样就可以保证数据的⼀致性。
容灾机制
对于TDSQL来说,我们希望容灾做到⾃动切换,⾃动恢复,主备⼀致性(保证业务提交的事务在切换过程不丢失),跨IDC容灾。
【MySQL异步复制】
在MySQL发展的早期,就提供了异步复制的技术,只要写的压⼒不是特别⼤,在⽹络条件较好的情况下,发⽣主备切换基本上能将影响控制到秒级别,因此吸引了很多开发者的关注和使⽤。但这套⽅案提供的⼀致性保证,对于计费或者⾦融⾏业是不够的。
图4是异步复制的⼤致流程,很显然主机提交了binlog就会返回给业务成功,没有保证binlog同步到了备机,这样在切换的瞬间很有可能丢失这部分事务。
图4 异步复制
【MySQL半同步复制】
到了MySQL5.5版本的时候,Google提供了⼀个半同步半异步的插件,确保必须收到⼀个备机的应答才让事务在主机中提交;当备机应答超时的情况下,强同步就会⾃动退化成异步模式(这也是半同步半异步名字的由来)。
图5 半同步复制
这套⽅案相对异步复制,在数据的可靠性⽅⾯确实好很多,在主机本⾝故障的情况下,基本能保证不丢失事务(因为最后⼀个事务,⾄少有⼀个备机上存在),但⼀旦退化成异步复制就回到过去了。TDSQL没直接采⽤这套⽅案,是因为:在主备跨IDC(ping延迟2-3毫秒)时性能⾮常很低。
【Cluster⽅案】
除了上⾯的⽅案外,开源社区还有三个Cluster解决⽅案,分别是Oracle的NDB引擎、Percona XtraDB Cluster和MariaDB GaleraCluster,从公开资料的性能对⽐上来看,后2者在性能和系统灵活性等⽅⾯都强于NDB(同时采⽤NDB意味着也放弃了InnoDB引擎,NDB主要是基于全内存的,并且需要⾼速⽹络环境⽀持,所以不考虑了);PerconaXtraDB Cluster和MariaDB Galera Cluster强同步机制的底层都是采⽤Galera这套强同步的架构。
MariaDB GaleraCluster具有如下⾮常吸引⼈的特性:
♦  MariaDB Galera Cluster 是⼀套在MySQL InnoDB存储引擎上⾯实现multi-master及数据实时同步的系统架构,业务层⾯⽆需做读写分离⼯作,数据库读写压⼒都能按照既定的规则分发到各个节点上去;
♦  同步复制Synchronous replication:保证节点间数据⼀致性;
♦  Active-active multi-master拓扑逻辑:多主的拓扑结构,可以认为没有备机的概念;
♦  可对集中任⼀节点进⾏数据读写:假如⼀个set有3个节点,则3个节点可以同时读写,上次完全不⽤关⼼主备切换和读写分离;
♦  ⾃动成员控制,故障节点⾃动从集中移除;
♦  ⾃动节点加⼊;
♦  真正并⾏的复制,基于⾏级:同⼀个表可以在集中任何节点更新,⽀持不带where条件,但⼀次更新的记录条数有限制;
♦  每个节点都包含完整的数据副本。
⽬前来看,Galera是⼀套相当完美的⽅案。但是,在跨IDC的性能测试中,其性能下降⽐较⼤,另外,实现⽅案也⽐较复杂,⽬前对它的代码理解还不够透彻,所以暂时没有在计费领域⼤范围推⼴使⽤。但我相信这个⽅向是对的,有吸引⼒的,随着后续Galera越来越完善,我们对它研究得越透彻,也许有⼀天会采⽤这套⽅案。
【性能测试和分析】
上⾯的三种复制模式对⽐测试,数据如图6所⽰。
图6 三种复制模式的对⽐