一次可贵的分库分表实践

背景

前不久发过两篇关于分表的文章:算法

从标题能够看得出来,当时咱们只作了分表;仍是因为业务发展,截止到如今也作了分库,目前看来都还比较顺利,因此借着脑子还记得清楚来一次复盘。数据库

<!--more-->多线程

先来回顾下整个分库分表的流程以下:并发

整个过程也很好理解,基本符合大部分公司的一个发展方向。性能

不多会有业务一开始就会设计为分库分表,虽然说这样会减小后续的坑,但部分公司刚开始都是以业务为主。大数据

直到业务发展到单表没法支撑时,天然而然会考虑分表甚至分库的事情。spa

因而本篇会做一次总结,以前提过的内容可能会再重复一次。线程

分表

首先讨论下什么样的状况下适合分表?设计

根据个人经验来看,当某张表的数据量已经达到千万甚至上亿,同时日增数据量在 2% 以上。3d

固然这些数字并非绝对的,最重要的仍是对这张表的写入和查询都已经影响到正常业务执行,好比查询速度明显降低,数据库总体 IO 居高不下等。

而谈到分表时咱们着重讨论的仍是水平分表;

也就是将一张大表数据经过某种路由算法将数据尽量的均匀分配到 N 张小表中。

Range

而分表策略也有好几种,分别适用不一样的场景。

首先第一种是按照范围划分,好比咱们能够将某张表的建立时间按照日期划分存为月表;也能够将某张表的主键按照范围划分,好比 【1~10000】在一张表,【10001~20000】在一张表,以此类推。

这样的分表适合须要对数据作归档处理,好比系统默认只提供近三个月历史数据的查询功能,这样也方便操做;只须要把三月以前的数据单独移走备份保存便可)。

这个方案有好处也有弊端:

  • 好处是自带水平扩展,不须要过多干预。
  • 缺点是可能会出现数据不均匀的状况(好比某个月请求暴增)。

Hash

按照日期这样的范围分表当然简单,但适用范围仍是比较窄;毕竟咱们大部分的数据查询都不想带上时间。

好比某个用户想查询他产生的全部订单信息,这是很常见的需求。

因而咱们分表的维度就得改改,分表算法能够采用主流的 hash+mod 的组合。

这是一个经典的算法,大名鼎鼎的 HashMap 也是这样来存储数据。

假设咱们这里将原有的一张大表订单信息分为 64 张分表:

这里的 hash 即是将咱们须要分表的字段进行一次散列运算,使得通过散列的数据尽量的均匀而且不重复。

固然若是自己这个字段就是一个整形而且不重复也能够省略这个步骤,直接进行 Mod 获得分表下标便可。

分表数量选择

至于这里的分表数量(64)也是有讲究的,具体设为多少这个没有标准值,须要根据自身业务发展,数据增量进行预估。

根据我我的的经验来看,至少须要保证分好以后的小表在业务发展的几年以内都不会出现单表数据量过大(好比达到千万级)。

我更倾向于在数据库可接受的范围内尽量的增大这个分表数,毕竟若是后续小表也达到瓶颈须要再进行一次分表扩容,那是很是痛苦的。

目前笔者还没经历这一步,因此本文没有相关介绍。

可是这个数量又不是瞎选的,和 HashMap 同样,也建议得是 2^n,这样能够方便在扩容的时尽量的少迁移数据。

Range + Hash

固然还有一种思路,RangeHash 是否能够混用。

好比咱们一开始采用的是 Hash 分表,可是数据增加巨大,致使每张分表数据很快达到瓶颈,这样就不得再也不作扩容,好比由 64 张表扩容到 256 张。

但扩容时想要作到不停机迁移数据很是困难,即使是停机,那停多久呢?也很差说。

因此咱们是否能够在 Mod 分表的基础上再分为月表,借助于 Range 自身的扩展性就不用考虑后续数据迁移的事情了。

这种方式理论可行,但我没有实际用过,给你们的思路作个参考吧。

烦人的数据迁移

分表规则弄好后其实只是完成了分表的第一步,真正麻烦的是数据迁移,或者说是如何作到对业务影响最小的数据迁移。

除非是一开始就作了分表,因此数据迁移这一步骤确定是跑不掉的。

下面整理下目前咱们的作法供你们参考:

  1. 一旦分表上线后全部的数据写入、查询都是针对于分表的,因此原有大表内的数据必须得迁移到分表里,否则对业务的影响极大。
  2. 咱们估算了对一张 2 亿左右的表进行迁移,本身写的迁移程序,大概须要花 4~5 天的时间才能完成迁移。
  3. 意味着这段时间内,之前的数据对用户是不可见的,显然这样业务不能接受。
  4. 因而咱们作了一个兼容处理:分表改造上线后,全部新产生的数据写入分表,但对历史数据的操做还走老表,这样就少了数据迁移这一步骤。
  5. 只是须要在操做数据以前作一次路由判断,当新数据产生的足够多时(咱们是两个月时间),几乎全部的操做都是针对于分表,再从库启动数据迁移,数据迁移完毕后将原有的路由判断去掉。
  6. 最后全部的数据都从分表产生和写入。

至此整个分表操做完成。

业务兼容

同时分表以后还须要兼容其余业务;好比原有的报表业务、分页查询等,如今来看看咱们是如何处理的。

报表

首先是报表,没分表以前之间查询一张表就搞定了,如今不一样,由一张表变为 N 张表。

因此原有的查询要改成遍历全部的分表,考虑到性能能够利用多线程并发查询分表数据而后汇总。

不过只依靠 Java 来对这么大量的数据作统计分析仍是不现实,刚开始能够应付过去,后续还得用上大数据平台来处理。

查询

再一个是查询,原有的分页查询确定是不能用了,毕竟对上亿的数据分页其实没什么意义。

只能提供经过分表字段的查询,好比是按照订单 ID 分表,那查询条件就得带上这个字段,否则就会涉及到遍历全部表。

这也是全部分表以后都会遇到的一个问题,除非不用 MySQL 这类关系型数据库。

分库

分表完成后能够解决单表的压力,但数据库自己的压力却没有降低。

咱们在完成分表以后的一个月内又因为数据库里“其余表”的写入致使整个数据库 IO 增长,并且这些“其余表”还和业务关系不大。

也就是说一些无关紧要的数据致使了总体业务受影响,这是很是不划算的事情。

因而咱们便把这几张表单独移到一个新的数据库中,彻底和现有的业务隔离开来。

这样就会涉及到几个改造:

  1. 应用自身对这些数据的查询、写入都要改成调用一个独立的 Dubbo 服务,由这个服务对迁移的表进行操做。
  2. 暂时不作数据迁移,因此查询时也得按照分表那样作一个兼容,若是查询老数据就要在当前库查询,新数据就要调用 Dubbo 接口进行查询。
  3. 对这些表的一些关联查询也得改造为查询 Dubbo 接口,在内存中进行拼接便可。
  4. 若是数据量确实很大,也可将同步的 Dubbo 接口换为写入消息队列来提升吞吐量。

目前咱们将这类数据量巨大但对业务不太影响的表单独迁到一个库后,数据库的总体 IO 降低明显,业务也恢复正常。

总结

最后咱们还须要作一步历史数据归档的操做,将 N 个月以前的数据要按期迁移到 HBASE 之类存储,保证 MySQL 中的数据一直保持在一个可接受的范围。

而归档数据的查询便依赖于大数据提供服务。

本次分库分表是一次很是可贵的实践操做,网上大部分的资料都是在汽车出厂前就换好了轮胎。

而咱们大部分碰到的场景都是要对高速路上跑着的车子换胎,一不当心就“车毁人亡”。

有更好的方式方法欢迎你们评论区留言讨论。

你的点赞与分享是对我最大的支持

相关文章
相关标签/搜索