腾讯计费平台部托管着公司90%以上的虚拟账户。如QB、Q点、包月服务、游戏的二级账户等,为了保证能顺畅支撑公司各大业务的实时在线交易。而且在各种灾难场景下数据是一致而且可用的,对系统的可用性、一致性切换要求很高,因此计费团队历来都很重视高一致性存储系统的建设。
到眼下为止,计费高一致性存储层的解决方式大致经过了3个阶段,本文将分享最新的基于MySQL的分布式解决方式。
随着业务的发展,基于内存的NoSQL解决方式HOLD平台在高峰期一天支撑3000亿读写,证明了分布式Cache的巨大价值。但随着各种业务的接入,NoSQL方案的不足也逐步显现出来了,例如以下所看到的。
- 适用的业务场景比較有限,仅提供get/set操作,有不少业务场景希望能通过记录中的其它字段做索引来查询,比方流水类业务。
- 不是全部的数据都是热点,一台64GB内存机器提供的有效内存空间大概在50GB左右,而採用Fusion卡的机型容量一般在1TB以上。对照起来。假设全部数据放入分布式Cache明显是一种极大的浪费。最合理的当然是热点在HOLD。冷数据採用基于磁盘的存储。
- 计费平台部多年来在支付领域有了相当多的技术积累,HOLD作为NoSQL系统功能有限,因此建造一套更加强大通用的高一致性存储系统将整个支付领域的实时数据(重点是账户数据、用户订单数据。以及海量的流水数据)统一管理起来很有价值。
基于上面的分析。结合我们在MySQL领域多年的应用和优化经验,终于决定在MySQL存储引擎基础之上,打造一套分布式的SQL系统。
- 保持原来的MySQL协议,这样曾经訪问MySQL系统的C++、Java各类系统都不须要改动。DBA能继续保持原来大部分使用习惯。
- 自己主动的跨IDC容灾切换,同一时候保证数据一致性,对于提交成功的事务保证一笔不丢,达到银行级对容灾的要求。
- 灵活的容量伸缩机制,对业务透明,解决MySQL本身扩容不灵活的问题。
- 重点支持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、username、password进行鉴权;
- 记录完整的SQL运行信息。与秒级监控平台对接完毕实时的SQL请求的时耗,成功率等指标监控分析;
- 对count、distinct、sum、avg、max、min、order by、group by等聚合类SQL一般须要訪问后端的多个set,网关会分析结果并做合并再返回。暂不支持跨set join和分布式事务;
- 网关无状态,既支持与业务部署到一起,也能够独立部署(可通过TGW或者LVS做容灾)。
自己主动扩容机制
眼下。针对MySQL的扩容,一般有以下两种策略。
- 垂直扩容。
一般通过升级硬件来实现,比方更换更好的CPU,将传统的sas盘换成FusionIO卡这类,然后针对新硬件调整好參数。在硬件结构变化比較大的时候,性能甚至能达到上十倍的提升。但垂直扩容有比較大的局限,就是这样的模式随着业务的突增还是比較easy达到瓶颈,特别是面对互联网海量用户的时候。所以在互联网应用场景下,一般仅将垂直扩容当做一个辅助的手段。
- 水平扩容。经常使用的有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半同步复制】
到了MySQL 5.5版本号的时候。Google提供了一个半同步半异步的插件。确保必须收到一个备机的应答才让事务在主机中提交。当备机应答超时的情况下,强同步就会自己主动退化成异步模式(这也是半同步半异步名字的由来)。
图5 半同步复制
这套方案相对异步复制,在数据的可靠性方面确实好非常多,在主机本身故障的情况下,基本能保证不丢失事务(由于最后一个事务,至少有一个备机上存在),但一旦退化成异步复制就回到过去了。TDSQL没直接採用这套方案。是由于:在主备跨IDC(ping延迟2-3毫秒)时性能非常非常低。
【Cluster方案】
除了上面的方案外。开源社区还有三个Cluster解决方式,各自是Oracle的NDB引擎、Percona XtraDB Cluster和MariaDB Galera Cluster。从公开资料的性能对照上来看。后2者在性能和系统灵活性等方面都强于NDB(同一时候採用NDB意味着也放弃了InnoDB引擎,NDB主要是基于全内存的。而且须要快速网络环境支持,所以不考虑了);Percona XtraDB Cluster和MariaDB Galera Cluster强同步机制的底层都是採用Galera这套强同步的架构。MariaDB
Galera Cluster具有例如以下很吸引人的特性:
- MariaDB Galera Cluster 是一套在MySQL InnoDB存储引擎上面实现multi-master及数据实时同步的系统架构,业务层面无需做读写分离工作,数据库读写压力都能依照既定的规则分发到各个节点上去。
- 同步复制Synchronous replication:保证节点间数据一致性;
- Active-active multi-master拓扑逻辑:多主的拓扑结构,能够觉得没有备机的概念;
- 可对集群中任一节点进行数据读写:假如一个set有3个节点,则3个节点能够同一时候读写。上次全然不用关心主备切换和读写分离;
- 自己主动成员控制,故障节点自己主动从集群中移除;
- 自己主动节点增加。
- 真正并行的复制,基于行级:同一个表能够在集群中不论什么节点更新。支持不带where条件,但一次更新的记录条数有限制。
- 每一个节点都包括完整的数据副本。
眼下来看,Galera是一套相当完美的方案。可是。在跨IDC的性能測试中。其性能下降比較大,另外,实现方案也比較复杂,眼下对它的代码理解还不够透彻,所以临时没有在计费领域大范围推广使用。
但我相信这个方向是对的,有吸引力的。随着兴许Galera越来越完好。我们对它研究得越透彻。或许有一天会採用这套方案。
【性能測试和分析】
上面的三种复制模式对照測试,数据如图6所看到的。
图6 三种复制模式的对照
从图6的数据能够看出,半同步和Galera模式对性能的损耗还是很大的。Galera的毛刺尤其严重,所以在跨IDC环境下还不是适合计费这样对延迟要求很低的场景。
为什么性能损耗会这么严重呢?这个看明确MySQL的网络模型就清楚了。外界可查的MySQL最早的公开版本号应该是1996年的3.1.1.1版本号。这么多年来,网络模型基本上变化不大。与Apache有点类似,有点差别的是MySQL採用的是每一个连接一个线程的模型,这套模型最大的优点就是开发特别简单,线程内部都是同步调用。仅仅要不訪问外部接口,支撑每秒几百上千的请求量也基本够用。由于大部分情况下IO是瓶颈。只是随着当前硬件的发展,尤其是SSD、FusionIO的出现,IOPS从200+/s进化到几十万甚至百万次/s。IO基本上不再是瓶颈,若再採用这套模型并採用堵塞的方式调用延迟较大的外部接口。则CPU都会堵塞在等网络应答上了,性能自然上不去。
只是在MySQL5.6企业版和MariaDB、Percona中都引入了线程池。使得网络模型灵活了非常多,图7是简化后的对照模型。
图7 简化的对照模型
TDSQL採用的强同步方案
从上面的分析可知,半同步半异步是比較轻量级的高一致性容灾方案。但受限于已有的同步网络模型,CPU利用不起来。我们假设在线程池基础之上做一些改动,參考半同步的思路就能够实现一个高性能的强同步方案。
眼下的做法是採用与Linux内核处理中断的思路:将上面线程池模型的第三个环节(运行SQL的逻辑)拆成两个部分:
- 上半部分:任务运行到写binlog为止。然后将会话保存到session中。接着运行下一轮循环去处理其它请求了。这样就避免让线程堵塞等待应答了;
- 然后:MySQL自身负责主备同步的dump线程会将binlog马上发送出去,备机的IO线程收到binlog并写入到relay log之后。再通过UDP给主机一个应答;
- 在主机上,开一组线程来处理应答。收到应答之后找到相应的会话。运行下半部分的commit。send应答,绑定到epoll等操作。绑定到epoll之后这个连接又能够被其它线程检測到并运行了。
改造后性能提升明显。如图8所看到的。
图8 改造后的性能
数据高可用性保障机制
除上述强同步机制外。TDSQL还做了下面增强,以提升数据的可用性。
- 推荐一个set最少配置3个跨IDC的节点。能够按业务的要求对备机开放查询服务。
- 支持灵活添加节点。比方认为3个节点还不够。能够很方便地添加节点。TDSQL会自己主动完毕数据的全量和增量复制,此处主要依赖Xtrabackup实现物理复制,性能測试数据表明:一个小时大概能够拷贝500GB数据到新节点。那么对于Z3(1.1TB盘,一般最多用800GB左右)。新添加的节点大概1.5个小时左右就有了全量数据,此功能也能够用在坏盘等情况下替换节点的时候使用。很方便。
- 细心的同学可能会发现上面的强同步还有点小缺陷:比方主机用kill -9杀掉。那么可能写了binlog但没有来得及发送到远端,此时当然也不会返回给业务成功,备机上不存在这笔数据。但主机起来之后会多出来这笔事务。
我们的做法是对新增的事务依据row格式的binlog做闪回,当然回退不了的比方drop table之类的,就直接提醒运维手工确认是否清除数据库。然后会由Xtrabakcup机制自己主动从新的备机全量拉取数据重构。
- 节点的监控通过跨IDC部署的ZooKeeper来保证。而且主备切换由一套自己主动化的严格流程来保证。
接下来的方向
- 当将高一致性容灾、高可用性、自己主动容量伸缩做实后,随着业务的接入,集群的规模会越来越大,TDSQL必将会更加依赖实时的资源调度、隔离框架,因此有必要研究怎样将TDSQL与Docker结合起来。
- 如前所述,Galera集群是个非常好的发展方向,我们会持续研究并实践。
- 眼下大部分MySQL还在使用单个连接单线程模型。线程池也刚起步。以后随着大家对性能要求越来越高,这块或许能够继续突破,比方结合线程池+协程或许是个非常好的方向。假设真能引入协程,或许为MySQL添加调用外部接口的结构会灵活非常多。
- TDSQL将数据拆是拆的彻底了,但作为完整的分布式数据库、合也须要考虑,比方跨库少量记录的join,规模受限的分布式事务等。眼下的做法是数据按小时入TDW,在TDW上做OLAP分析。
作者简单介绍:雷海林,2007年增加腾讯,10年以上的Linux后台Server开发经验。眼下主要从事分布式Cache、实时大数据处理引擎、分布式MySQL(TDSQL)设计和开发工作。
本文选自程序猿电子版2015年6月A刊,该期很多其它文章请查看这里。2000*至今全部文章文件夹请查看程序猿封面秀。欢迎订阅程序猿电子版(含iPad版、Android版、PDF版)。
本文为CSDN原创文章,未经同意不得转载,如需转载请联系market#csdn.net(#换成@)