腾讯计费平台部托管着公司90%以上的虚拟账户,如QB、Q点、包月服务、游戏的二级账户等,为了保证能顺畅支撑公司各大业务的实时在线交易,并且在各种灾难场景下数据是一致并且可用的,对系统的可用性、一致性切换要求非常高,因此计费团队历来都非常重视高一致性存储系统的建设。
到目前为止,计费高一致性存储层的解决方案大致经过了3个阶段,本文将分享最新的基于MySQL的分布式解决方案。
随着业务的发展,基于内存的NoSQL解决方案HOLD平台在高峰期一天支撑3000亿读写,证明了分布式Cache的巨大价值;但随着各种业务的接入,NoSQL方案的不足也逐步显现出来了,如下所示。
基于上面的分析,结合我们在MySQL领域多年的应用和优化经验,最终决定在MySQL存储引擎基础之上,打造一套分布式的SQL系统。
整体架构
针对上面的需求,TDSQL最终的结构如图1所示(与当前大部分中心化的分布式系统类似)。
图1 TDSQL架构
系统由三个模块组成:Scheduler、Agent、网关,三个模块的交互都是通过ZooKeeper完成,极大简化了各个节点之间的通信机制,相对于第二代HOLD的开发简单了很多。
Scheduler作为集群的管理调度中心,主要功能包括:
Agent模块负责监控本机MySQL实例的运行情况,主要功能包括:
网关基于MySQL Proxy开发,在网络层、连接管理、SQL解析、路由等方面做了大量优化,主要特点和功能如下:
自动扩容机制
目前,针对MySQL的扩容,一般有下面两种策略。
通过上述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具有如下非常吸引人的特性:
目前来看,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的逻辑)拆成两个部分:
改造后性能提升明显,如图8所示。
图8 改造后的性能
数据高可用性保障机制
除上述强同步机制外,TDSQL还做了以下增强,以提升数据的可用性。
接下来的方向
作者简介:雷海林,2007年加入腾讯,10年以上的Linux后台Server开发经验,目前主要从事分布式Cache、实时大数据处理引擎、分布式MySQL(TDSQL)设计和开发工作。
腾讯计费平台部托管着公司90%以上的虚拟账户,如QB、Q点、包月服务、游戏的二级账户等,为了保证能顺畅支撑公司各大业务的实时在线交易,并且在各种灾难场景下数据是一致并且可用的,对系统的可用性、一致性切换要求非常高,因此计费团队历来都非常重视高一致性存储系统的建设。
到目前为止,计费高一致性存储层的解决方案大致经过了3个阶段,本文将分享最新的基于MySQL的分布式解决方案。
随着业务的发展,基于内存的NoSQL解决方案HOLD平台在高峰期一天支撑3000亿读写,证明了分布式Cache的巨大价值;但随着各种业务的接入,NoSQL方案的不足也逐步显现出来了,如下所示。
基于上面的分析,结合我们在MySQL领域多年的应用和优化经验,最终决定在MySQL存储引擎基础之上,打造一套分布式的SQL系统。
整体架构
针对上面的需求,TDSQL最终的结构如图1所示(与当前大部分中心化的分布式系统类似)。
图1 TDSQL架构
系统由三个模块组成:Scheduler、Agent、网关,三个模块的交互都是通过ZooKeeper完成,极大简化了各个节点之间的通信机制,相对于第二代HOLD的开发简单了很多。
Scheduler作为集群的管理调度中心,主要功能包括:
Agent模块负责监控本机MySQL实例的运行情况,主要功能包括:
网关基于MySQL Proxy开发,在网络层、连接管理、SQL解析、路由等方面做了大量优化,主要特点和功能如下:
自动扩容机制
目前,针对MySQL的扩容,一般有下面两种策略。
通过上述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具有如下非常吸引人的特性:
目前来看,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的逻辑)拆成两个部分:
改造后性能提升明显,如图8所示。
图8 改造后的性能
数据高可用性保障机制
除上述强同步机制外,TDSQL还做了以下增强,以提升数据的可用性。
接下来的方向
作者简介:雷海林,2007年加入腾讯,10年以上的Linux后台Server开发经验,目前主要从事分布式Cache、实时大数据处理引擎、分布式MySQL(TDSQL)设计和开发工作。