大家都知道,数据库集群一旦需要扩容,十之八九,都会涉及到数据迁移。只有少部分情况,由于对数据库使用的特殊性,或者数据库代理的特性,可以做到无数据迁移。而通过删数据来实现扩容的操作,想必对于大部分人来说闻所未闻,而实际上业内已经有两家知名的互联网公司持续多年使用“删、删、删”原则对数据库进行扩容,其中一家还将自己“删”成了中国头部出海游戏品牌。其实上述两家企业使用的数据库本身没什么特别,均为常用的 MySQL 或者 MySQL 的兼容版本,例如 AWS 的 Aurora。删数据扩容的关键也并不在数据库,而是数据库的代理。大家耳熟能详的老牌数据库QiHoo Atlas以及美团DBProxy,其实也都可以通过删除数据的方式来实现扩容,只是很少有人想过或者尝试过如此操作。
那么究竟如何使用“删、删、删”原则进行数据库扩容?该原则又适用于哪类数据库呢?接下来我们就以云上曲率自研的DBProxy(FPNN)为例,针对可停服的业务模块,为大家详细的分享具体的操作步骤。
首先,DBProxy和其他常见的主流数据库代理一样,是全对等代理。这就意味着,DBProxy集群内,不存在中心节点,DBProxy按需部署和启动,需要多少起多少。同时DBProxy也支持hash取模和按区段划分两种分库分表的方式。无论是哪种形式,均适用“删、删、删”原则。
其次,说到数据库扩容的方式无非是垂直扩容和水平扩容两种。其中水平扩容可以再分为按区段分片的扩容,以及按hash分片的扩容。其中最简单的是垂直扩容,最难的是按hash分片的扩容。遵循着由易入难的原则,我们先来介绍一下DBProxy的垂直扩容。
垂直扩容
垂直扩容其实是最简单的,因为只需要移动整个模块对应的数据库就行。
假设扩容前,我们有两个 MySQL 实例,分别是 MySQL Instance 01和MySQL Instance 02。MySQL Instance 01上部署了四个模块对应的数据库,MySQL Instance 02 上面也部署了另外4个模块的数据库。如下图所示:
假设MySQL Instance 01压力偏大,MySQL Instance 02正常。此时我们仅需要单扩MySQL Instance 01即可。根据“删、删、删”的原则,我们采取以下步骤:
1. 用 MySQL Instance 01 做一个镜像,并用镜像启动新的 MySQL 实例,作为 MySQL Instance 03;
2. 修改 DBProxy 的配置库:
3. 在MySQL Instance 01上直接删除数据库 Module-C、Model-D,在MySQL Instance 03 上直接删除数据库 Module-A、Model-B。
完毕!
MySQL Instance 01 扩容后示意图
如果 MySQL instance 02 也需要同步扩容,那只需要将 MySQL instance 01 的扩容操作对 MySQL instance 02 再做一遍即可。
MySQL instance 01、02 同步扩容后示意图
以上就是DBProxy对垂直扩容时的具体操作。
按区段分片的水平扩容
按区段划分的水平扩容又分为两种:增加段区 & 特定区段扩容。首先,我们来看 DBProxy 对于增加区段是如何操作的。
增加段区
假设我们同样有两个 MySQL 实例,分别是 MySQL Instance 01 和 MySQL Instance 02。MySQL Instance 01上部署着01-04区段的数据库,MySQL Instance 02 上面部署着05-08区段的数据库。如下图所示:
根据“删、删、删”的原则,对于增加新的区段,我们采取以下步骤:
1. 启动新的 MySQL 实例作为 MySQL Instance 03 并部署区段09-12;
2. 修改 DBProxy 的配置库:
新增区段扩容后,如下图所示:
关于特定区段的扩容,DBProxy 同样有两种方式可选择:奇偶分库 or 将原始逻辑库拆分,然后按照垂直分库处理。奇偶分库是 DBProxy 为了兼容特定历史遗留项目而开发的特需功能,对于任一区段,只能实现一次扩容。对任意区段更通用的扩容方式建议使用“拆分原始逻辑库”的方式。
奇偶分库
假设我们需要对区段 02 进行奇偶分库,区段 02 位于MySQL Instance 01 上,对应数据库 Range-02。如下图所示:
根据“删、删、删”的原则,对于需要奇偶扩容的区段,我们采取以下步骤:
1. 使用 MySQL Instance 01做一个镜像,并用镜像启动新的 MySQL 实例,作为MySQL Instance 01-2;
2. 修改 DBProxy 的配置库:
3. 在 MySQL Instance 01上,直接删除数据库 Range-02 中分表键为偶数的数据;在 MySQL Instance 01-2,直接删除数据库 Range-02 中分表键为奇数的数据,并删除 MySQL Instance 01-2 上,并不需要的 Range-01、Range-03、Range-04 三个区段的数据。
完毕!
区段 02 进行奇偶分库后示意图
拆分原始逻辑库
假设我们需要对区段 N 进行拆分扩容。区段 N 位于 MySQL Instance XX 上,对应数据库 DB-xx。内部有 Table-A、Table-B、Table-C、Table- D 四个业务表。如下图所示:
根据“删、删、删”的原则,对于需要奇偶扩容的区段,我们采取以下步骤:
1.用 MySQL Instance XX 做一个镜像,并用镜像启动新的 MySQL 实例,作为 MySQL Instance XX-1;
2.修改 DBProxy 的配置库:
3.在 MySQL Instance XX上,直接删除数据库 DB-xx 中的数据表 Table-C、Table-D;在 MySQL Instance XX-1上,直接删除数据库DB-xx中的数据表 Table-A、Table-B。
完毕!
区段N进行拆分逻辑库后示意图
按哈希分片的水平扩容
按哈希分片的数据,如果以2的倍数进行的分库分表,则水平扩容其实分为:结构拆分 & 数据拆分两个阶段。其中结构拆分又分为:实例拆分与库拆分两个子阶段。
如果在建表时,就创建了足够的分表(2的次方),那大部分的扩容,都无需走到数据拆分阶段。只有分表不够用的情况下,才需要进行数据拆分。
实例拆分
我们先来看一下实例拆分,假设我们现在有两个 MySQL 实例:MySQL Insgtance 01 和 MySQL Insgtance 02,上面部署着 DB-01 至 DB-08, 8个分库,如下图所示:
扩容时,我们将按照2的倍数进行扩容。假设当前计划扩容一倍,根据“删、删、删”的原则,我们采取以下步骤:
1.用 MySQL Instance 01 与 MySQL Instance02 分别各做一个镜像,并用镜像启动对应的新的 MySQL 实例,分别作为 MySQL Instance 03 和 MySQL Instance 04;
2.修改 DBProxy 的配置库:
3.删!
完毕!
2倍实例拆分后示意图
如此下去,可扩至单实例单库。如下图所示:
到目前为止,仅为实例和库的处理。只要一个库内还有多张表,单实例单库,并不是 DBProxy 2倍快速扩容的尽头。
库拆分
当实例拆分达到尽头,已处于单实例单库的阶段时,再次扩容就进入了库拆分的阶段。我们以 MySQL Instance 01 和 DB-01 为例,讲述如何进行库的拆分。
假设经多次扩容后,MySQL Instance 01 上仅有唯一个库 DB-01,DB-01 中有四个数据表:Table-01 至 Table-04,如下图所示:
此时,我们需要对该实例继续进行2倍扩容。根据“删、删、删”的原则,我们采取以下步骤:
1.用 MySQL Instance 01 制作一个镜像,并用镜像启动新的 MySQL 实例,作为 MySQL Instance 01-2;
2.修改 DBProxy 的配置库:
3.删!
完毕!
2倍扩容拆分后示意图
如此下去,可扩至单实例单库单表。如下图所示:
当扩展到单实例单库单表时,2倍扩容的结构拆分阶段达到尽头,之后的扩容,将直接进入数据拆分阶段。
数据拆分
当结构拆分来到尽头,已处于单实例单库单表的阶段时,再次扩容就进入了数据拆分的阶段。此时我们继续以上文的情况为例。上文已扩展到单实例单库单表,如下图所示:
此时我们继续进行2倍扩容。
根据“删、删、删”的原则,我们采取以下步骤:
1.MySQL Instance 01、MySQL Instance01-2、MySQL Instance01-3、MySQL Instance01-4 分别各做一个镜像,并用镜像启动对应的新的 MySQL实例,分别作为MySQL Instance 01-5、MySQL Instance01-6、MySQL Instance01-7、MySQL Instance01-8;
2.修改 DBProxy 的配置库:
3.删!
完毕!
2倍扩容并进行数据拆分后示意图
此后,对于后续的扩容需求,继续按照数据拆分扩容即可。
结语
以上便是在常见的分库分表情形下,如何通过删数据的方式进行数据库扩容的具体流程。根据多年的经验积累,拥有千万级用户的社交网络及全球互联的大型游戏企业,后端数据库扩容一般不超过40分钟。其中80%以上的时间消耗在数据库镜像生成和启动上,实际扩容时间仅需要5分钟左右。