TiDB 在饿了么归档环境的应用

1,707 阅读18分钟
原文链接: zhuanlan.zhihu.com

背景

随着业务增长,公司数据规模不断膨胀,表变多、变大。一方面占用的磁盘、CPU 等物理资源疾速上涨,另一方面大表性能下降且变更困难。实际上,很多大表的数据无需保留很久,比如某些业务可能只需近 3 周或近 3 个月的数据。对此类表,可依据业务要求,在线上环境只保留指定天数的数据,其余超出时间范围的过期数据可予以删除。出于某些原因,比如对账,线上业务查冷数据等,从生产环境删除的数据不能直接丢弃,需要存档以备不时之需,所谓归档。

方案

目前我们公司主要使用的是 MySQL 数据库服务,那么我们使用大磁盘容量的机器搭建几套主从结构的 MySQL 集群,配上高可用机制,将生产环境指定库表、指定时间范围之外且满足其他指定条件的数据按照一定的顺序定期分批导入到这些目标集群,并在每批确认导入成功后对源生产环境的数据予以删除,下批再从上次结束的位点开始导入、删除,如此循环重复,使生产环境表的数据始终维持在指定的时间范围。至于归档目标环境的数据则可以根据公司的要求统一保留指定的时间,比如 1 年。对于归档目标环境过期的数据可以进行直接删除处理。

问题

上述是一个典型的归档系统需要完成的基本功能和基本的处理流程,如果数据规模不是很大且增幅稳定而且表结构固定,那么上述流程可以很好的运行的。但现实的情况是公司数据规模庞大、数据增长迅速,而且由于业务发生变化等原因,后端的表结构可能需要跟着变化。而且,还需要考虑归档对生产集群正常业务的影响、对主从延迟的影响和 DDL 的影响等(这部分内容不在本文讨论范围)。

对于表结构变更有两种处理方式,

  • “温和型”:在发现源端表结构发生变更后,相应的在目标端的表上执行同样的变更,以确保两端表结构一致数据可以正常写入;
  • “粗暴型”:一旦发现源端表结构发生了变更,则在目标端轮转一个新表(旧表重命名,然后按源表新的表结构在目标端重建表)以确保源和目标表结构统一。

“粗暴型”的解决方式可以很好的处理结构不一致问题,但如前边所述,一些线上的业务可能需要查询归档环境的冷数据,比如,用户想要查询半年前的订单数据,对于这类表简单粗暴的将其重命名会对业务查询归档数据造成困难;“温和型”的解决方式不仅可以处理表结构不一致的问题,而且也可以避免轮转表导致的数据查询问题,但是,对于 MySQL 来说,当归档表变得很大的时候,DDL 通常会非常耗时。

对于数据规模大、数据增长快这一情况,尽管选用了大磁盘容量的机型来存储归档数据,但因表多且数据量大往往在运行几个月后磁盘空间即被写满。这部分数据因为还没超出指定的有效期,所以还不能从归档目标环境直接清理,而 MySQL 又不能方便的进行容量扩展,所以只能考虑将现有的归档作业迁移至新的归档目标集群进行归档,而这一迁移也会对需要访问归档环境数据的应用造成影响。

另外,尽管启用了高可用机制,因为归档环境数据量大,一旦归档目标集群发生了 Master 节点切换,要想重新同步一份数据搭建一个 Slave 节点会非常耗时。

上述问题可简要概括为三个主要痛点:

  • 快速 Online DDL;
  • 水平扩展存储容量;
  • 故障恢复后的数据自动同步。

探索

弹性伸缩和快速 Online DDL 不是我们在归档环境遇到的个案问题,其实也是数据库业界普遍会遇到的两个问题,尤其是现如今各类海量数据的大背景下,也因此出现过诸多解决方案。比如,TokuDB 存储引擎就可以快速安全的处理 DDL,而且是作为 MySQL 插件的方式提供的,所以对于基于 MySQL 的应用几乎可以不用进行任何改造就可以在 TokuDB 引擎上运行。而且 TokuDB 引擎有很高的压缩比,这一点对很多资源敏感型的用户也很具吸引力。

美中不足的是,TokuDB 只是插件式的存储引擎,并不能解决弹性扩容问题。 MySQL Cluster、PXC 等具备弹性扩容能力,但它们只是扩展了计算能力,并不能扩展存储能力,且大表的 DDL 依然是个问题。Cassandra,Vertica 这类分布式列式存储可以对计算和存储方便的进行弹性伸缩,DDL 也可以快速安全的进行,但这类数据库是非关系型的,不支持分布式事务(对于归档应用不是什么问题),且对于基于 MySQL 的应用需要进行大量兼容性的改造才可能迁移至这些存储(对于 MySQL 的归档意味着我们要进行额外的大量的改造工作)。

基于 Google Spanner 和 F1 之类的数据库可以横向扩展、可以快速 Online DDL,且是关系型的,支持分布式事务,但却不支持 MySQL 协议,这对于新的应用不是大的问题,但对于基于 MySQL 的应用的迁移,对于我们目前的 MySQL 归档却是个问题。Hadoop 生态系统的 HBase 存在其他列式存储同样的优缺点,且对于我们归档应用显得有点重。

简单讲,我们的选型原则是:能在实现目标的前提下,越简单越好。

TiDB 分布式数据库集群在这一背景下进入我们的视野,它几乎支持前述提到的各个特性同时高度兼容 MySQL,这对于我们的业务是非常友好的。

TiDB

整个 2017 年,TiDB 在数据库行业发展的如火如荼,我们也对此予以了极大的关注。恰逢我们的归档环境存在前述问题,且生产环境也面临诸多大表扩容和高读写表的改造问题亟待解决,因此在归档环境率先尝试使用 TiDB 以解决当前面临的问题,并对其进行验证以便将来应用于生产环境变得势在必行。

1.TiDB 整体架构

TiDB 集群有三大组件构成:TiDB Server、PD Server、TiKV Server(图 1)。

图 1 TiDB 架构

其中各个组件的功能如下:

  • TiDB Server,可以理解为 SQL Layer,负责接收 SQL 请求,处理 SQL 解析、SQL 优化等相关逻辑,并通过 PD 与底层 TiKV 交互来获取或变更数据;
  • PD Server,可以视作集群的大脑,负责集群各类元信息的存储以及 TiKV 节点间的数据调度和负载均衡,另外还负责集群全局事务 ID 的分配;
  • TiKV Server,集群的存储层,数据最终以 Key-Value 的形式存储于其中,并通过 PD 在各个 KV 节点调度,以保证各节点负载均衡。TiKV Cluster 本身也可作为分布式的 Key-Value 存储独立使用。

2. TiDB 核心特性

  • 水平扩展计算与存储

TiDB Server 负责处理 SQL 请求,随着业务的增长,可以简单的添加 TiDB Server 节点,提高整体的计算处理能力。TiKV 负责存储数据,随着数据量的增长,可以部署更多的 TiKV Server 节点,提高集群整体的存储能力。PD 会在 TiKV 节点之间做调度,将部分数据迁移到新加的节点上,这个过程不需要人为的干预。

  • 故障自恢复的高可用

TiDB / TiKV / PD 三个组件都能容忍部分实例失效,不影响整个集群的可用性。TiDB Server 节点失效,只会影响该节点上的 session,应用连接失败重试后可通过前端负载均衡中间件将请求发送到其他正常的 TiDB Server 节点。PD 节点失效,若非 Raft leader 节点(PD Cluster 通过 Raft 协议保证自身数据一致性)则无影响,否则会重新选取 leader,期间该无法对外提供服务(约 3 秒钟)。TiKV 节点失效,会影响该节点上的所有 region,若 region 非 Raft leader(TiKV Cluster 也通过 Raft 协议保证节点间的数据一致性),则服务不受影响,否则服务会中断,待重新选举 leader 后恢复。如果 PD 确认了失效的 TiKV 节点已经不能恢复,则会自动将该节点的数据调度至其他正常的 TiKV 节点。

3. TiDB 其他特性及原理

  • 高度兼容 MySQL 语法和协议;
  • 分布式事务;
  • 跨数据中心数据强一致性保证;
  • 海量数据高并发实时写入与实时查询。

至于 TiDB 的内部原理,比如,数据在 TiKV 层是如何组织和管理的,又是如何保证数据一致性的;SQL 层是如何实现的,又是如何将关系型表结构、关系型数据、索引、SQL 映射为 K-V 模型的;PD 是如何保证自身元数据一致性的,又是如何协调 TiDB、调度 TiKV 实现负载均衡的超出了本文的范围。针对原理部分,TiDB 有三篇系列文章将这些问题描述的很清楚,详情可参考(以下文字可跳转至原文):

可弹性扩容,故障自恢复且可自动迁移数据,高度兼容 MySQL 协议,快速 Online DDL,这是我们计划选择 TiDB 作为归档目标集群所考虑的主要四点。

TiDB 在饿了么归档环境的实践

1.初试

整个实践过程并非一帆风顺,部署和测试期间遇到过不少问题。经历了从单机 12 实例部署 TiKV,到单机 4 实例部署,再到目前的单机 3 实例部署。也经历了 TiDB 版本由 RC 升级 PreGA、由 PreGA 升级 GA1.0.0、再由 1.0.0 升级到目前的 1.0.4。还有期间各种压缩、缓存、调度等参数的组合测试与调优和各类 bug、疑难问题的排查与解决。

起初,考虑到归档环境数据量庞大,且对于分布式的 TiDB 来说本身会存储多份数据副本无需对磁盘进行 RAID,而且机器内存和 CPU 充足,因此将每台物理机的 SATA 磁盘拆成了独立的 12 块,每块盘上部署一个实例,以增加集群总的可用空间。这样一共 3 台物理机共部署了 36 个 TiKV 实例来进行测试,TiDB 和 PD 各 3 个实例共享另外 3 台物理机。在使用 sysbench 压测过程中线程数开到 64 的时候(64 个表,单表 3000W 数据),持续的高并发写入导致数据在 TiKV 实例间分布极不均衡,写入的数据集中在其中 3 个 TiKV 实例,其他实例上的数据非常少,且没有看到预期的明显的数据从 region 较多的节点向 region 较少的节点调度的迹象。后台大量的 server busy 报错,监控页面大量的底层 RocksDB stall 信息。

后期分析发现是 SATA 盘的性能太弱,而 TiDB 的各类默认参数都只针对 SSD/NVMe 盘做了优化(这也是官方的安装程序检测磁盘性能的原因),且当时的 TiDB 版本比较老,还没有热点调度功能,持续的并发写入导致磁盘 IO 飙升,region 数量快速增长且热点集中,大量的 region 调度又引起大量的 snapshot 操作,从而又加剧了磁盘 IO 的消耗,最终导致了恶性循环,region 在最初写入的节点不断堆积,无法调度出去。后期,将 12 块盘以 RAID10 方式打成了 1 块盘以提升性能,单台物理机的 TiKV 实例数量也由 12 降至了 3,增加了 block-cache-size 的大小,同时,考虑到机器 CPU 性能较强悍,调高了 TiKV 默认的 compression-level 以尽可能的减少需要调度的数据量,也将版本升级后遇到的问题得以解决。后续对 TiDB 的扩缩容,online DDL,高可用进行了测试,也都符合预期。

尽管 TiDB 是兼容 MySQL 语法和协议的,但在测试过程中还是碰到一些小的问题。比如,暂不支持临时表,部分 SQL Hints 语法暂不支持,SELECT MIN()\MAX() 查询有索引的列也会比较慢等。对此,我们对归档应用进行了兼容性的改造。比如,针对 SELECT MIN()\MAX() 查询使用等价的 ORDER BY + LIMIT 1 的方式变通等(新版本已经直接支持了)。任何产品从最初开发到最终的成熟应用,迭代过程中必然会出现一些 bug,在使用 TiDB 的过程中也遇到过一些部署、授权、监控、PD-CTL 查看 store 状态信息等方面的问题。不过在反馈给 TiDB 官方团队后,都得到了快速的响应和解决。

在 TiDB 集群测试基本满足需求后,我们并没有直接将现有的所有归档作业迁移至 TiDB。毕竟模拟压测和真实的应用环境还有比较大的差距,我们计划让 TiDB 承载尽可能接近真实的归档工作来进一步考验其表现。TiDB 官方提供了一个很好的工具—— Syncer 来做这个事情。

Syncer 可以模拟成 MySQL Slave 节点从上游读取和解析 Binlog 并在匹配过滤规则后应用于下游的 TiDB(图 2)。Syncer 支持断点续传功能,可以同时起多个 Syncer 实例分别使用不同的过滤规则从一个数据源向到多个目标进行数据同步,也可以从多个数据源向同一个目标进行数据同步。我们选择了当前负载比较高的两套 MySQL 归档集群作为数据源,使用 Syncer 将每天夜里归档期间的增量数据(百 GB 数量级)实时同步至 TiDB 集群。同步持续运行了一周左右,未发现有明显异常。

图 2 Syncer 架构

2. 应用

即使利用 Syncer 同步数据的方案在 TiDB 集群模拟了归档期间的负载,但毕竟顺序应用 Binlog 时事务是单线程执行的,同直接在 TiDB 进行归档时的多线程并发执行的负载也还是有区别。所以,我们选择分批迁移归档作业至 TiDB 集群,每次一小批,运行一段时间无异常后迁移下一小批。

截至目前,已有 45% 左右的归档作业迁移至了 TiDB 集群。至此 TiDB 集群一直平稳运行。当前的归档方案(图 3)。

图 3 归档架构

TiDB 集群的部署情况和集群监控告警架构(如图 4、图 5)。

图 4 TiDB 集群部署情况

对于归档表我们进行了分类处理,对于需要业务访问的表,在源和目标表结构不一致时会同步变更目标环境的表结构,对不需要业务访问的表,在遇到表结构不一致时或者每月月初会自动轮转一个新表,以便于后续的归档顺利进行和后期的过期数据处理。

由于 DDL 在 TiDB 是串行执行的,且大部分表无需业务访问,所以在月初会有近五万张表的 rename、create 和 drop 操作在 TiDB 排队等待执行,造成较多的归档超时失败。对于这一部分的处理,我们计划将轮转表的逻辑从归档流程中解耦,在非归档时段提前将表慢慢轮转掉,到归档开始进行时便无需等待 DDL。

图 5 TiDB 集群监控告警方案

3.成果

截至目前整个归档平台已部署归档作业千余个,涉及数百套源集群数百个库数百张表。其中 TiDB 上部署的归档作业占总归档作业量的 45%,涉及近百套源集群几十个库上百张表。整个归档平台日均数据增量数亿行,大小数百 GB,其中 MySQL 占 75% 左右,TiDB 占 25% 左右。

目前 TiDB 集群的总容量几十 TB,已归档的数据量占 TiDB 集群总空间约 20%。后期,在现有 TiDB 集群容量不足时只需添加 TiKV 节点即可实现容量的扩展而无需迁移之上的归档作业,不影响业务的访问。对于源端发生了结构变更的表,也可以方便快速的在归档目标同步这个变更。如果集群中节点出现了故障也不会影响整个集群的访问,且在替换掉故障节点后数据会自动同步至新节点,无需人工干预。

当前 TiDB 尚存在的一些问题

  • TiDB 中 DDL 串行执行,当某种原因在同一时刻有大量 DDL 请求时,会排队等待,待所有的 DDL 都执行完成可能需要很长时间。希望能增加 DDL 并发执行的特性。
  • 当前需要手动对 TiDB 上的大表进行 Analyze 操作以收集准确的索引统计信息,以便于查询的高效执行。希望可支持自动采样分析来保证执行计划的准确性。
  • TiDB Binlog 依赖于 Kafka 集群,感觉架构有点重。

我们也就以上三点咨询了 TiDB 官方,下面是官方回复:

  • 并行 DDL 正在做,预计会在 2.1 版本初步提供,3.0 版本完善。
  • 目前发布 2.0 版本已经提供了统计信息的动态更新以及自动全量 analyze,不过还没有默认打开,测试稳定后,会默认打开。
  • 为保证 Binlog 高可用,依赖于分布式消息队列在所难免,Kafka 是其中最好的解决方案。

关于使用 TiDB 的一些建议

  • 多实例部署 TiKV 时, capacity、block-cache-size 等参数一定要设置好,否则可能导致磁盘空间溢出和内存溢出。每个 region 也会占用很少一部分的内存,随着集群 region 数量上升,TiKV 使用的内存量会缓慢上涨,需要留意。
  • 在磁盘使用量达到了设置的 capacity 的 80% 左右时,集群会控制不往这些节点调度数据,但正常的数据写入依然会进行。另外,扩容需要额外的临时空间,所以需要提前做好扩容准备。建议在容量 60% 的时候就开始准备扩容工作。
  • 多实例部署 TiKV 时需要设置好 label,以便于将数据尽可能的写到不同机房、机架和主机,避免几个副本落在同一台机器,保证各节点负载相对均衡。
  • TiDB 对单个事务大小有限制,主要原因是对大规模的数据做两阶段提交和多副本复制压力太大。在使用 mydumper 工具导出并使用 myloader 工具导入数据时常会碰到这个问题,可以通过加 -F 参数限制 mydumper 导出的单个 insert 的大小来避免。

计划

TiDB 在归档环境的成功应用是我们的一个良好开始,也为我们日后尝试在生产环境使用积累了不少经验,接下类我们计划再搭建 1~2 套 TiDB 集群,将剩余的未迁移至 TiDB 的归档作业全部迁移至 TiDB。

另外,目前生产环境也存在较多的大表治理问题和海量数据的推送问题。传统的 Sharding 方案对一些不好确定 sharding key 的大表无能为力, MySQL 主从方案对于海量数据的推送也力不从心。因此我们需要找到解决方案来应对这些挑战。

致谢

在 TiDB 部署与测试过程中,得到了 PingCAP 公司同事的大力支持。对他们的及时响应和耐心解答深表敬佩和谢意。

TiDB 背后有这么一支强悍的精于技术和服务的队伍,想必一定会走的很长很远!

作者:张延召,饿了么高级数据库工程师