为什么我们要从MySQL迁移到TiDB?

作者:微信小助手

发布时间:2020-03-12T10:18:50

当一张百亿数据量的表放在你面前,你将面临着什么?加列?哭吧,怎么也得等个几天甚至几周。加索引?哭吧,不论你用 pt-online-schema,还是 gh-ost,你都面临着拷贝一张临时表用以存储临时数据,磁盘已经 80% 了,这一张表就占几百 G,你咋弄?


图片来自 Pexels


我先说几个最让你兴奋和开心的点吧:

  • 在 TiDB 里,你完全不用担心磁盘容量的问题。

  • 在 TiDB 里,原生支持 Online DDL,你完全不用担心第三方改表工具改表出现各种 Bug 的问题,相信用开源工具改过上 T 级别表的同学都遇到过或多或少的各类 error。

  • 在 TiDB 里,加列,主键扩容字段都是秒级的,比如我刚刚就刚对一张 19 亿的表加完了字段,1 秒完事,这在 MySQL 里要 8.0 才可以,而且还要求列在最后才行。

  • 在 TiDB 里,你会发现 count(*) 惊人的快,一张近 20 亿的表 coun(*) 大概在 1 分钟完事儿,当然,这取决于你的 KV 数量和磁盘性能。

  • 在 TiDB 里,从 MySQL 迁移将变得简单,图形化一键迁移,爽不爽?

  • 在 TiDB 里,绝大多数情况你会发现比单机 MySQL 有更好的性能,当然也不排除一些例外,例如 enum 这样奇葩的字段类型。

  • 在 TiDB 里,......,您且往下看,我慢慢和您说。


使用背景


360 云平台对 360 集团各大业务线均有提供服务支持,涉及的数据库支持方案有:MySQL、Redis、MongoDB、ES、GP、PiKA。


其中 MySQL 至今已有过万的实例,目前,对于一些写入量大的业务,已经出现瓶颈。


例如磁盘空间,虽然我们可以通过分库分表的方式,拆分出来,但这对业务和 DBA 来说都是不小的工作量,最痛苦的无异于这些大表的改表。


无论是单表上 T,还是分库分表,对一张大表执行 DDL 的代价是非常大的。


针对业务爆发式增长的数据量,我们开始调研选型是否有其他数据库可以代替传统的 MySQL。


通过调研我们了解到 TiDB,迁移平滑,基本上无需业务变动代码,完全的事务 ACID 支持,分布式的架构,自带高可用、Online DDL。


截止目前,360 云平台这边有三套 TiDB 集群,总节点数在 50+。有 9 个业务线接入使用,有过百亿级表 Online 加索引的案例,总数据量目前在 80T。


版本上,我们从 3.0.1 一路跟随到 3.0.5,DM 版本从内测到 1.0.2 GA 版本,共计提出 9 个 Bug 或改进项反馈。


后续我们计划将 TiDB 上到 360 HULK 云平台上,并定制化开发一些功能为业务提供可视化的服务,以便让 360 集团更多的业务线接触到 TiDB、使用 TiDB。


版本的选择我们之所以从大版本 3 开始,也是看到了一些 2.X 版本的社区反馈,尤其是索引执行计划这块,3.X 版本较之前的版本会好很多。DM 版本我们是直接选取的最新版,后一路跟随新版本升级。


集群架构


整体架构上,我们除了 TiDB 集群外,还用到了 DM 和 Pump、Drainer 套件。


这块主要是由于我们使用 TiDB 的业务有两种:


①老的 MySQL 业务, 因单机磁盘受限,导致单实例磁盘无法支撑爆炸式增长的数据量,数据比较重要,需要备份和支持 7*24 小时的恢复。


这类业务我们用到 DM 套件来实现无障碍迁移,1TB 的导入时间在 16 小时,这里如果是比较大的数据源,且 TiDB 是全新集群,可以使用 TiDB-Lightning,速度可以更快。


Lightning 的实测导入速度,37 分钟,导完 2 张大表共计 54G 的数据,符合 100G/H 预估, 是 loader 的 3 倍速度,loader 用时 2 小时 4 分钟。


说起 DM 使用这块文章后面会单独分享下这块需要注意的问题,如下图所示:

②全新的业务, 或由业务自行导入到 TiDB 集群中,这种业务数据量一般都会比较大,也是看中了 TiDB 支持 ACID 和分布式的特点。


目前网盾业务有多张表都过 10 亿级别,其中有张表到达了 100 亿+,建索引花了近 10 天(这块其实我们应当注意,不是分布式就一张表就完事儿了,因为表量级过大,清理老旧数据都是个问题)。


TiDB 现在支持分区表,但我们在使用过程中发现性能上和普通表有差距,期待后续版本能够让分区表功能和性能更加的完善。

TiDB 在 360 云平台的使用情况


对于这一全新的数据库,我们本着大胆用,不拘泥于传统的态度进行使用。

我们的 MySQL 现在也正在适配 8.0 版本,MongoDB、ES 也都是时刻关注着新版本情况来评估是否适合云平台。


因此 TiDB 的上线也是从离线业务→边缘业务→核心业务来过渡的。


经过大量的测试、也参考了其他公司的使用情况,我们计划将 TiDB 纳入 360 HULK 云平台,并计划后期对其不断完善在云平台中的功能,对全公司业务线开放使用。


定制化开发一些 MySQL 已经具备的,例如 SQL 审核、慢查统计、冗余索引检测、自增索引阈值等各项基础功能等等。


虽然在使用过程中遇到了一些小问题,例如索引的选取、参数的调优,因为一些配置导致的性能抖动,但都得到了 PingCAP 同学快速的响应和回复,这对我们推进 TiDB 有重大的帮助。


一键迁移工具 DM 干货分享


DM 使用经验如下:


①权限


官网手册上只说明需要如下权限:

TiDB Lightning 需要以下权限:
  • SELECT

  • UPDATE

  • ALTER

  • CREATE

  • DROP


存储断点的数据库额外需要以下权限:
  • INSERT

  • DELETE


但实测过程中发现还需要如下权限:
  • 上游 (REPLICATION SLAVE 权限必须具备,要不增量同步会 access deny)。

  • 下游 (不加 super 会导致 checksum table 无法执行)。


②TiKV Region Score


PD 监控中 -Statistics-balance 中,有 Store-region-score 监控项,这里记录的是各个节点的 Score 得分,正常情况下,我们期望的结果是得分接近的,这说明无需进行 Region 大规模迁移。

③PD 调度原理


Region 负载均衡调度主要依赖 balance-leader 和 balance-region 两个调度器。


二者的调度目标是将 Region 均匀地分散在集群中的所有 Store 上,但它们各有侧重:
  • balance-leader 关注 Region 的 Leader,目的是分散处理客户端请求的压力。

  • balance-region 关注 Region 的各个 Peer,目的是分散存储的压力,同时避免出现爆盘等状况。


我们这里重点关注的是 balance-region,当它出现不均衡的时候,我们可以直接在监控中看到类似下图所示:

调度期间,不可避免的会出现 IO 争用、磁盘的 lantency,都会出现不同程度的上涨,从业务上的反馈看,就会出现积压,响应不及时等等。而当 Region Balance 完成后, Duration 等都会恢复正常水平。

因此,我们要关注的地方有两点:
  • 如何控制或减小 Region Balance 大规模迁移时对业务的影响;

  • 如何提前规避因磁盘导致的大规模 Region Balance。


对于第一点,我们迁移的时候是有参数可以控制的。 这里无论是磁盘空间阈值,还是 Region Balance 调度速度,或者 Drop 大量表后调整空 Region Merge 的速度,其实都是可以通过 pd-ctl 的 config set 命令来实时调节。

例如:
  • high-space-ratio 0.7 #设置空间充裕阈值为 0.7。当节点的空间占用比例小于指定值时,PD 调度时会忽略剩余空间这个指标,主要针对实际数据量进行均衡。

  • region-schedule-limit 8 #最多同时进行 8 个 Region 调度。这个值主要影响 Region Balance 的速度,值越大调度得越快,设置为 0 则关闭调度。Region 调度的开销较大,所以这个值不宜调得太大。也可以通过减小该值来限制调度region对集群产生的影响。

  • merge-schedule-limit 12 #最多同时进行 12 个 merge 调度。设置为 0 则关闭 Region Merge。Merge 调度的开销较大,所以这个值不宜调得过大。

  • leader-schedule-limit 8 #最多同时进行 8 个 leader 调度。这个值主要影响 Leader Balance 的速度,值越大调度得越快,设置为 0 则关闭调度。Leader 调度的开销较小,需要的时候可以适当调大。

  • max-merge-region-keys 50000 #设置 Region Merge 的 keyCount 上限为 50k。当 Region KeyCount 大于指定值时 PD 不会将其与相邻的 Region 合并。

  • max-merge-region-size 16 #设置 Region Merge 的 size 上限为 16M。当 Region Size 大于指定值时 PD 不会将其与相邻的 Region 合并。设置为 0 表示不开启 Region Merge 功能。


TIPS: 理解了作用和原理,上述参数都可以根据需求自行控制。

例如当我们在 Drop 大量的表后,会产生很多的空 Region。在 Region 数量较多的情况下,Raftstore 需要花费一些时间去处理大量 Region 的心跳,从而带来一些延迟,导致某些读写请求得不到及时处理。


如果读写压力较大,Raftstore 线程的 CPU 使用率容易达到瓶颈,导致延迟进一步增加,进而影响性能表现。


因此我们会希望尽快的进行 Region Merge,来避免过多的 Region 对集群造成性能损耗时,我们可以同时调小 max-merge-region-keys、max-merge-region-size,来让其更快的触发 Merge 操作,同时调大 merge-schedule-limit 提高并发度。


例如当我们发现某台 KV 磁盘空间剩余 40% 开始大量调度时,我们可以将 high-space-ratio 调整到 0.7,以临时避免调度对业务产生的影响。


我们也可以控制调度的并发度,来减少对业务产生的影响,实测这都是立竿见影的参数,大家如果遇到这些问题可供参考。


对于第二点,尤其是使用 DM 期间,将 DM-worker 和 TiKV 混合部署的情况下,要注意清理全量备份留下的文件和 Relaylog。

默认调度策略是当磁盘剩余的有效空间不足 40%,处于中间态时则同时考虑数据量和剩余空间两个因素做加权和当作得分,当得分出现比较大的差异时,就会开始调度。


所以 DM 导入完成后,要记得删除全量备份。就是 dumped_data.task_xxx 文件夹,这个全量备份一般都会比较大,如果 dm-worker 和 TiKV 混部,就会出现某个 TiKV 节点磁盘已使用率高于其他。


这时 PD 的 store region score 就会相比其他节点出现异常,引起性能抖动和 Duration 升高。

一直等待其追上后,才会像下图这样:

此时,balancer 已达平衡状态:

Duration 恢复正常水平,如下图 16:54 分时的情况:

QPS 也不再积压,恢复正常水准:

关于 relay-log,默认是不清理的,就和 MySQL 的 expire_logs_days 一样,这块可以通过 dm-worker 的配置文件来进行配置。


例如将 Expires 配置为 7,代表 7 天后删除:

[purge]
interval = 3600
expires = 7
remain-space = 15


Expires 来控制保留天数。 默认 expires=0,即没有过期时间,而 remain-space=15 意思是当磁盘只剩于 15G 的时候开始尝试清理,这种情况我们极少会碰到,因此这个清理方式其实基本上是用不到的。


所以建议有需要删除过期 relay-log 的小伙伴,直接配置 Expires 保留天数就可以了。


DM 导入完成后,应该提供是否在完成后自动删除全备文件的选项,可以默认不删,由使用者决定是否删除。


从使用者角度来说,全量备份目录无论是全量一次性导入还是 all 增量同步,后续都不会再使用到。


如果 dm-worker 和 TiKV 混部,会导致全备文件占据大量磁盘空间,引起 TiKV Region 评分出现异常,导致性能下降,已转化为 PRM 产品需求。


④关于 DM 使用期间出现数据丢失的问题


在早期还没有 dm-portal 自动化生成 task 时,我们都是自行编写 DM 的 task 同步文件。后来有了 dm-portal 自动化生成工具,只要图形页面点点点就可以了。

但该工具目前有一个问题是,没有全库正则匹配,即便你只勾选一个库,他底层是默认把每张表都给你配置一遍。


这就会出现当上层 MySQL 新创建某张表的时候,下游会被忽略掉,例如当你使用改表工具 gh-ost 或者 pt-online-schema-change,你的临时表都会被当做为不在白名单内而被忽略,这个问题使用者需要注意。

我们也已经反馈给了官方。未来不久的版本估计就可以修复。

["skip event"] [task=task_20357] [unit="binlog replication"] [event=query] [statement="ALTER TABLE `360`.`_data_201910_gho` ADD COLUMN `raw_url_md5` CHAR(32) NOT NULL DEFAULT '' COMMENT 'raw_url md5'"]
["skip event"] [task=task_20357] [unit="binlog replication"] [event=query] [statement="ALTER TABLE `360`.`_data_201910_gho` ADD INDEX `idx_rawurl_md5`(`raw_url_md5`)"] [schema=flow]
["skip event"] [task=task_20357] [unit="binlog replication"] [event=query] [statement="ALTER TABLE `360`.`_data_201910_gho` DROP INDEX `idx_raw_url`"] [schema=flow]


这里日志可以看到 event 被 skip 了。

⑤关于 DM 使用期间偶发性 1062 主键冲突的问题


query-error task 能看到具体的报错信息,下游看都没有该值:

mysql> select * from client where clientid='82b51e6f6eb64955487f978dd94a2c81e492f6170xxxxxxxxxxxxxxxxxxxxxxxxx';
Empty set (0.00 sec)


再去上游看,结果发现也没有值,业务逻辑应该是后来 delete 了:

mysql> select * from client where clientid='82b51e6f6eb64955487f978dd94a2c81e492f6170xxxxxxxxxxxxxxxxxxxxxxxxx';
Empty set (0.00 sec)


因为上游也没有值,去上�