亿级大表分库分表实战总结(万字干货,实战复盘)(一)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 亿级大表分库分表实战总结(万字干货,实战复盘)(一)

1.前言


为什么需要做分库分表。这个相信大家多少都有所了解。


海量数据的存储和访问成为了MySQL数据库的瓶颈问题,日益增长的业务数据,无疑对MySQL数据库造成了相当大的负载,同时对于系统的稳定性和扩展性提出很高的要求。


而且单台服务器的资源(CPU、磁盘、内存等)总是有限的,最终数据库所能承载的数据量、数据处理能力都将遭遇瓶颈。


目前来说一般有两种方案。


1)一种是更换存储,不使用MySQL,比如可以使用HBase、polarDB、TiDB等分布式存储。


2)如果出于各种原因考虑,还是想继续使用MySQL,一般会采用第二种方式,那就是分库分表。


文章开头就说了,网上分库分表文章很多,对知识点讲解比较多,因此,本文将不再过多赘述分库分表方案的范式处理。


而是专注于梳理分库分表从架构设计 到 发布上线的完整过程,同时总结其中的注意事项和最佳实践。包括五个部分:


  • 业务重构
  • 存储架构设计
  • 改造和上线
  • 稳定性保障
  • 项目管理


尤其是各个阶段的最佳实践,都是血与泪凝聚的经验教训。


2.第一阶段:业务重构(可选)


对于微服务划分比较合理的分库分表行为,一般只需要关注存储架构的变化,或者只需要在个别应用上进行业务改造即可,一般不需要着重考虑“业务重构” 这一阶段,因此,这一阶段属于“可选”。


本次项目的第一大难点,在于业务重构。


而本次拆分项目涉及到的两张大表A和B,单表将近八千万的数据,是从单体应用时代遗留下来的,从一开始就没有很好的领域驱动/MSA架构设计,逻辑发散非常严重,到现在已经涉及50+个在线服务和20+个离线业务的的直接读写。


因此,如何保证业务改造的彻底性、全面性是重中之重,不能出现有遗漏的情况。


另外,表A 和 表B 各自有二、三十个字段,两表的主键存在一一对应关系,因此,本次分库分表项目中,还需要将两个表进行重构融合,将多余/无用的字段剔除。


2.1 查询统计


在线业务通过分布式链路追踪系统进行查询,按照表名作为查询条件,然后按照服务维度进行聚合,找到所有相关服务,写一个文档记录相关团队和服务。


这里特别注意下,很多表不是只有在线应用在使用,很多离线算法和数据分析的业务也在使用,这里需要一并的梳理好,做好线下跨团队的沟通和调研工作,以免切换后影响正常的数据分析。


2.2 查询拆分与迁移


创建一个jar包,根据2.1的统计结果,与服务owner合作将服务中的相关查询都迁移到这个jar包中(本项目的jar包叫projected)。


此处为1.0.0-SNAPSHOT版本。


然后将原本服务内的xxxMapper.xxxMethod( ) 全部改成projectdb.xxxMethod( )进行调用。


这样做有两个好处:


  • 方便做后续的查询拆分分析。
  • 方便后续直接将jar包中的查询替换为改造后 中台服务 的rpc调用,业务方只需升级jar包版本,即可快速从sql调用改为rpc查询。


这一步花了几个月的实际,务必梳理各个服务做全面的迁移,不能遗漏,否则可能会导致拆分分析不全面,遗漏了相关字段。


查询的迁移主要由于本次拆分项目涉及到的服务太多,需要收拢到一个jar包,更方便后期的改造。如果实际分库分表项目中仅仅涉及一两个服务的,这一步是可以不做的。


2.3 联合查询的拆分分析


根据2.2收拢的jar包中的查询,结合实际情况将查询进行分类和判断,把一些历史遗留的问题,和已经废弃的字段做一些整理。


以下举一些思考点。


1)哪些查询是无法拆分的?例如分页(尽可能地改造,实在改不了只能以冗余列的形式)

2)哪些查询是可以业务上join拆分的?

3)哪些表/字段是可以融合的?

4)哪些字段需要冗余?

5)哪些字段可以直接废弃了?

6)根据业务具体场景和sql整体统计,识别关键的分表键。其余查询走搜索平台。


思考后得到一个查询改造总体思路和方案。


同时在本项目中需要将两张表融合为一张表,废弃冗余字段和无效字段。


2.4 新表设计


这一步基于2.3对于查询的拆分分析,得出旧表融合、冗余、废弃字段的结果,设计新表的字段。


产出新表设计结构后,必须发给各个相关业务方进行review,并保证所有业务方都通过该表的设计。有必要的话可以进行一次线下review。


如果新表的过程中,对部分字段进行了废弃,必须通知所有业务方进行确认


对于新表的设计,除了字段的梳理,也需要根据具体查询,重新设计、优化索引。


2.5 第一次升级


新表设计完成后,先做一次jar包内sql查询的改造,将旧的字段全部更新为新表的字段。


此处为2.0.0-SNAPSHOT版本。


然后让所有服务升级jar包版本,以此来保证这些废弃字段确实是不使用了,新的表结构字段能够完全覆盖过去的业务场景。


特别注意的是,由于涉及服务众多,可以将服务按照 非核心 与 核心 区分,然后分批次上线,避免出现问题导致严重故障或者大范围回滚。


2.6 最佳实践


2.6.1 尽量不改变原表的字段名称


在做新表融合的时候,一开始只是简单归并表A 和 表B的表,因此很多字段名相同的字段做了重命名。


后来字段精简过程中,删除了很多重复字段,但是没有将重命名的字段改回来。


导致后期上线的过程中,不可避免地需要业务方进行重构字段名。


因此,新表设计的时候,除非必不得已,不要修改原表的字段名称!


2.6.2 新表的索引需要仔细斟酌


新表的索引不能简单照搬旧表,而是需要根据查询拆分分析后,重新设计。


尤其是一些字段的融合后,可能可以归并一些索引,或者设计一些更高性能的索引。


2.7 本章小结


至此,分库分表的第一阶段告一段落。这一阶段所需时间,完全取决于具体业务,如果是一个历史包袱沉重的业务,那可能需要花费几个月甚至半年的时间才能完成。


这一阶段的完成质量非常重要,否则可能导致项目后期需要重建表结构、重新全量数据。


这里再次说明,对于微服务划分比较合理的服务,分库分表行为一般只需要关注存储架构的变化,或者只需要在个别应用上进行业务改造即可,一般不需要着重考虑“业务重构” 这一阶段。


3.第二阶段:存储架构设计(核心)


对于任何分库分表的项目,存储架构的设计都是最核心的部分!


3.1 整体架构


根据第一阶段整理的查询梳理结果,我们总结了这样的查询规律。


  • 80%以上的查询都是通过或者带有字段pk1、字段pk2、字段pk3这三个维度进行查询的,其中pk1和pk2由于历史原因存在一一对应的关系
  • 20%的查询千奇百怪,包括模糊查询、其他字段查询等等


因此,我们设计了如下的整体架构,引入了数据库中间件、数据同步工具、搜索引擎(阿里云opensearch/ES)等。


下文的论述都是围绕这个架构来展开的。

11.png


3.1.1 mysql分表存储


Mysql分表的维度是根据查询拆分分析的结果确定的。


我们发现pk1\pk2\pk3可以覆盖80%以上的主要查询。让这些查询根据分表键直接走mysql数据库即可。


原则上一般最多维护一个分表的全量数据,因为过多的全量数据会造成存储的浪费、数据同步的额外开销、更多的不稳定性、不易扩展等问题。


但是由于本项目pk1和pk3的查询语句都对实时性有比较高的要求,因此,维护了pk1和pk3作为分表键的两份全量数据。


而pk2和pk1由于历史原因,存在一一对应关系,可以仅保留一份映射表即可,只存储pk1和pk2两个字段。


3.1.2 搜索平台索引存储


搜索平台索引,可以覆盖剩余20%的零散查询。


这些查询往往不是根据分表键进行的,或者是带有模糊查询的要求。


对于搜索平台来说,一般不存储全量数据(尤其是一些大varchar字段),只存储主键和

查询需要的索引字段,搜索得到结果后,根据主键去mysql存储中拿到需要的记录。


当然,从后期实践结果来看,这里还是需要做一些权衡的:


1)有些非索引字段,如果不是很大,也可以冗余进来,类似覆盖索引,避免多一次sql查询;


2)如果表结构比较简单,字段不大,甚至可以考虑全量存储,提高查询性能,降低mysql数据库的压力。


这里特别提示,搜索引擎和数据库之间同步是必然存在延迟的。所以对于根据分表id查询的语句,尽量保证直接查询数据库,这样不会带来一致性问题的隐患。


3.1.3 数据同步


一般新表和旧表直接可以采用 数据同步 或者 双写的方式进行处理,两种方式有各自的优缺点。

12.jpg


一般根据具体情况选择一种方式就行。


本次项目的具体同步关系见整体存储架构,包括了四个部分:


1)旧表到新表全量主表的同步


一开始为了减少代码入侵、方便扩展,采用了数据同步的方式。而且由于业务过多,担心有未统计到的服务没有及时改造,所以数据同步能避免这些情况导致数据丢失。


但是在上线过程中发现,当延迟存在时,很多新写入的记录无法读到,对具体业务场景造成了比较严重的影响。(具体原因参考4.5.1的说明)


因此,为了满足应用对于实时性的要求,我们在数据同步的基础上,重新在3.0.0-SNAPSHOT版本中改造成了双写的形式。


2)新表全量主表到全量副表的同步


3)新表全量主表到映射表到同步


4)新表全量主表到搜索引擎数据源的同步


2)、3)、4)都是从新表全量主表到其他数据源的数据同步,因为没有强实时性的要求,因此,为了方便扩展,全部采用了数据同步的方式,没有进行更多的多写操作。


3.2 容量评估


在申请mysql存储和搜索平台索引资源前,需要进行容量评估,包括存储容量和性能指标。


具体线上流量评估可以通过监控系统查看qps,存储容量可以简单认为是线上各个表存储容量的和。


但是在全量同步过程中,我们发现需要的实际容量的需求会大于预估,具体可以看3.4.6的说明。


具体性能压测过程就不再赘述。


3.3 数据校验


从上文可以看到,在本次项目中,存在大量的业务改造,属于异构迁移。


从过去的一些分库分表项目来说,大多是同构/对等拆分,因此不会存在很多复杂逻辑,所以对于数据迁移的校验往往比较忽视。


在完全对等迁移的情况下,一般确实比较少出现问题。


但是,类似这样有比较多改造的异构迁移,校验绝对是重中之重!!


因此,必须对数据同步的结果做校验,保证业务逻辑改造正确数据同步一致性正确。这一点非常非常重要。


在本次项目中,存在大量业务逻辑优化以及字段变动,所以我们单独做了一个校验服务,对数据的全量、增量进行校验。


过程中提前发现了许多数据同步、业务逻辑的不一致问题,给我们本次项目平稳上线提供了最重要的前提保障!!


3.4 最佳实践


3.4.1 分库分表引起的流量放大问题


在做容量评估的时候,需要关注一个重要问题。就是分表带来的查询流量放大。

这个流量放大有两方面的原因:


  • 索引表的二次查询。比如根据pk2查询的,需要先通过pk2查询pk1,然后根据pk1查询返回结果。
  • in的分批查询。如果一个select...in...的查询,数据库中间件会根据分表键,将查询拆分落到对应的物理分表上,相当于原本的一次查询,放大为多次查询。(当然,数据库会将落在同一个分表的id作为一次批量查询,而这是不稳定的合并)


因此,我们需要注意:


  • 业务层面尽量限制in查询数量,避免流量过于放大;
  • 容量评估时,需要考虑这部分放大因素,做适当冗余,另外,后续会提到业务改造上线分批进行,保证可以及时扩容;
  • 分64、128还是256张表有个合理预估,拆得越多,理论上会放大越多,因此不要无谓地分过多的表,根据业务规模做适当估计;
  • 对于映射表的查询,由于存在明显的冷热数据,所以我们又在中间加了一层缓存,减少数据库的压力


3.4.2 分表键的变更方案


本项目中,存在一种业务情况会变更字段pk3,但是pk3作为分表键,在数据库中间件中是不能修改的,因此,只能在中台中修改对pk3的更新逻辑,采用先删除、后添加的方式。


这里需要注意,删除和添加操作的事务原子性。当然,简单处理也可以通过日志的方式,进行告警和校准。


3.4.3 数据同步一致性问题


我们都知道,数据同步中一个关键点就是(消息)数据的顺序性,如果不能保证接受的数据和产生的数据的顺序严格一致,就有可能因为(消息)数据乱序带来数据覆盖,最终带来不一致问题。


我们自研的数据同步工具底层使用的消息队列是kakfa,,kafka对于消息的存储,只能做到局部有序性(具体来说是每一个partition的有序)。我们可以把同一主键的消息路由至同一分区,这样一致性一般可以保证。但是,如果存在一对多的关系,就无法保证每一行变更有序,见如下例子。

13.png



那么需要通过反查数据源获取最新数据保证一致性。


但是,反查也不是“银弹“,需要考虑两个问题。


1)如果消息变更来源于读写实例,而反查 数据库是查只读实例,那就会存在读写实例延迟导致的数据不一致问题。因此,需要保证 消息变更来源 和 反查数据库 的实例是同一个。


2)反查对数据库会带来额外性能开销,需要仔细评估全量时候的影响。


3.4.4 数据实时性问题


延迟主要需要注意几方面的问题,并根据业务实际情况做评估和衡量。


1)数据同步平台的秒级延迟


2)如果消息订阅和反查数据库都是落在只读实例上,那么除了上述数据同步平台的秒级延迟,还会有数据库主从同步的延迟


3)宽表到搜索平台的秒级延迟


只有能够满足业务场景的方案,才是合适的方案。


3.4.5 分表后存储容量优化


由于数据同步过程中,对于单表而言,不是严格按照递增插入的,因此会产生很多”存储空洞“,使得同步完后的存储总量远大于预估的容量。


因此,在新库申请的时候,存储容量多申请50%。


具体原因可以参考我的这篇文章 为什么MySQL分库分表后总存储大小变大了?


3.5 本章小结


至此,分库分表的第二阶段告一段落。


这一阶段踩了非常多的坑。


一方面是设计高可用、易扩展的存储架构。在项目进展过程中,也做了多次的修改与讨论,包括mysql数据冗余数量、搜索平台的索引设计、流量放大、分表键修改等问题。


另一方面是“数据同步”本身是一个非常复杂的操作,正如本章最佳实践中提及的实时性、一致性、一对多等问题,需要引起高度重视。


因此,更加依赖于数据校验对最终业务逻辑正确、数据同步正确的检验!


在完成这一阶段后,可以正式进入业务切换的阶段。需要注意的是,数据校验仍然会在下一阶段发挥关键性作用。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
8月前
|
SQL 关系型数据库 MySQL
MySQL优化方案
MySQL优化方案
305 9
MySQL优化方案
|
7月前
|
SQL 关系型数据库 MySQL
106分布式电商项目 - MySQL优化(查询优化)
106分布式电商项目 - MySQL优化(查询优化)
49 0
|
7月前
|
存储 关系型数据库 MySQL
108分布式电商项目 - MySQL优化(插入数据优化)
108分布式电商项目 - MySQL优化(插入数据优化)
32 0
|
7月前
|
关系型数据库 MySQL 数据库
107分布式电商项目 - MySQL优化(数据库结构优化)
107分布式电商项目 - MySQL优化(数据库结构优化)
45 0
|
7月前
|
算法 安全 Java
架构设计第十一讲:架构之高并发:限流
架构设计第十一讲:架构之高并发:限流
|
SQL 关系型数据库
【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式事务特别及问题分析(上)
【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式事务特别及问题分析(上)
156 0
【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式事务特别及问题分析(上)
|
消息中间件 存储 中间件
【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式事务特别及问题分析(中)
【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式事务特别及问题分析(中)
128 0
【分布式技术专题】「架构实践于案例分析」总结和盘点目前常用分布式事务特别及问题分析(中)
|
存储 监控 算法
【高并发项目实战】自适应高并发复杂场景的订单拆分算法工具
​本篇文章带你打造一个自适应场景的交易订单合单拆分通用算法方案,根据现有技术的痛点,我们支付的时候设计一种自适应场景的交易下单合单拆分通用算法的方案,可插拔的场景组件提升扩展性和通用性就很重要。
|
存储 关系型数据库 MySQL
MySQL索引的测试 (千万级数据) 以及特点总结|周末学习
创建表 可以看到这里创建的索引类型都是 BTREE -- ---------------------------- -- Table structure for mall -- ---------------------------- DROP TABLE IF EXISTS `mall`; CREATE TABLE `mall` ( `id` int(11) NOT NULL AUTO_INCREMENT, `categoryId` int(11) NOT NULL, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_gen
241 0
|
SQL 存储 监控
亿级大表分库分表实战总结(万字干货,实战复盘)(二)
亿级大表分库分表实战总结(万字干货,实战复盘)(二)
353 0