随着系统业务的发展,数据量的增长,海量数据的存储和读取成为系统性能提升的最大瓶颈。通过数据切分水平扩展数据库成为首要方案。
Mysql分库分表技术层面的文章已经很多,但涉及的如何实施的文章并不多,本人结合刚刚结束的迁移项目经历整理此文,由于本文为纯文字性文章,内容可能比较枯燥,但仍希望能够为即将执行迁移的人提供些许帮助。
迁库前的梳理与准备。
架构的三思,通常情况下如果系统已经发展到需要分库分表来提高性能,那么应该可以考虑将系统拆分成多个垂直应用和分布式服务框架来重构系统。另外有些系统或公司可能希望在迁移的过程中实现双写,双写的多数目的是为了当新系统存在问题时能够切换回到旧系统和方便后期验证工作。结合本人经验,此时一定不要盲目准备双写,而是要重点考虑一但切换带来的影响是否是可以承受的,迁库引发的错误是可以通过测试发现的,但是切换引起的损失可能是不好估量的。所以如果选择双写,除了双写方案还要考虑因切换导致的不一致数据如何修复以及数据验证的相关工具。
工作该如何开展?梳理现有业务,并绘制一张数据库ER图,然后分析统计哪些表需要拆分再结合业务特点进行分类。假设数据库中有20张表那么很可能只有10几张表需要分库分表,而且在这10几张表中有些业务是相互独立的,那么就可以分批次进行迁移了。在分析时可结合业务特点、历史数据量、年增长率等信息进行综合评判。而且像系统中的数据字典表、申请、审批等类型的表一般是不需要进行分库分表的,针对此类型的表可考虑放在一个单独的数据库实例中。
定义分库分表的路由规则。结合数据ER图,通盘考虑需要事务保障一致性的操作,定义数据路由规则使其能够将同一事务下的所有表路由到同一数据库中。
检查现有业务表是否包含自增主键(全量迁移时会比较方便),createTime和updateTime字段,如果没用应该考虑进行扩展(修改应用代码或添加触发器维护这两个字段)方便数据的全量和增量迁移。
迁移工作的开展
经历过前期的梳理,下面重点工作就是实施的过程了,这个过程中的重点工作除了开发与测试工作之外还有就是脚本及工具的准备。
工欲善其事必先利其器。如果涉及新的dao层代码开发,那么开发一个代码生成工具无疑会带来事半功倍的效果。同时还需要准备数据迁移的工具,这时之前提及到的添加createTime和updateTime及自增字段就派上用场了。由于数据同步功能一般对内存消耗较大,所以可以多机部署然后创建不同的同步任务,对于数据量较大的表可以根据时间段或id范围拆分成多个同步任务。另外自增主键连续性较强所以单次加载数据量比较均匀比较适合做全量数据同步,而updateTime则适用于增量数据同步。另外分库分表后,由于需要创建整百上千个表,所以编写一个简单的建表sql生成工具也是必要的。对于数据迁移之后数据核查工作也是比较复杂和耗时的所以编写一个数据核查工具也能减少不少的工作量。
代码开发与测试,不能松懈的任务。架构和方案虽然定义完成,但是代码开发工作仍然是重中之重。如果数据库操作被封装成分布式服务,那么此时需要注意在数据操作层代码中不能有过重的业务逻辑,需要保障方法的性能。对于一些单表操作的数据库层代码一般可能比较简单通常情况下交由一个人开发即可,但是对于公共数据表操作的代码开发很可能由多人同时进行,那么就需要注意避免出现大量臃肿和重复性代码或方法,最好的方式是结合业务对公共方法进行统一定义然后分工实现。代码开发过程中和完成后的走查也尤为重要。
开发测试完成之后,上线之前需要梳理相关应用系统的上线顺序,并准备建表脚本。在上线前可以先迁移全量数据到新数据库并记录时间点,在正式上线前执行增量数据同步。数据迁移完成后要对数据的正确性、完整性进行验证,应用上线之后要对系统功能逐一验证。
架构设计与代码开发的一些技巧
复杂业务拆分为批量作业。数据库层操作方法如果迁移为分布式服务之后,为了避免业务处理时间过长造成超时,一般可以将复杂业务封装成批量作业,然后使用定时任务或多线程逐一处理,处理完成后记录处理结果。对于数据量较大的业务还可以在服务调用方对数据进行分页处理,以降低单次操作的时长。
定时任务的高可用改造功能。对于定时任务,由于数据可能分布在不同的数据库中,不能使用事务提供一致性保障,这时可以将任务拆分成多个步骤,逐步执行并记录下当前任务的处理步骤,如果因为服务重启或宕机导致业务处理只完成一半当服务恢复后可以继续执行,但是最好同时具备完善的监控或告警机制以避免数据处于长期处理失败和重试状态。
针对复杂查询的处理。对于需要事务保障一致性的操作数据记录后可能并不方便其他业务对其进行查询,这时可以通过保存多个维度的数据来解决此问题。例如我们对银行卡和其消费信息进行分库分表改造。为保障数据一致性,一张银行卡的消费数据应该和银行卡信息存储在同一数据库中,但是如果通过用户名维度查询消息信息时就没有那么方便了。这时可以将银行卡数据按照用户名维度冗余一份,在以用户名查询消费数据时可先通过用户表查询到该用户下的银行卡信息,然后在查询消费信息。或者冗余一份消费信息,但是这样消耗的存储空间显然是比前者大的。除此之外我们也可用通过搜索引擎解决查询问题,canal+es就是不错的组合。