分库分表实战:可能是用户表最佳分库分表方案

再次抛出笔者的观点,在能满足业务场景的情况下,单表>分区>单库分表>分库分表,推荐优先级从左到右逐渐降低。

本篇文章主要讲用户表(或者类似这种业务属性的表)的分表方案,至于订单表,流水表等,本文的方案可能不是很合适,可以参考笔者另一篇文章《分库分表最佳实践:https://mp.weixin.qq.com/s/3ZxGq9ZpgdjQFeD2BIJ1MA》。

关注公众号:【阿飞的博客】,更多原创首发都在这里!!!

我们首先来看一下分表时主要需要做的事情:

  1. 选定分片键:既然是用户表那分片键非用户ID莫属;
  2. 修改代码:以sharding-jdbc这种client模式的中间件为例,主要是引入依赖,然后新增一些配置。业务代码并不怎么需要改动。
  3. 存量数据迁移;
  4. 业务发展超过容量评估后需要开发和运维介入扩容;

做过分库分表的都知道,第3步最麻烦,而且非常不好验证迁前后数据一致性(目前业界主流的迁移方案是存量数据迁移+利用binlog进行增量数据同步,待两边的数据持平后,将业务代码中的开关切到分表模式)。

第4步同样麻烦,业务增长完全超过当初分表设计的容量评估是很常见的事情,这也成为业务高速发展的一个隐患。而且互联网类型的业务都希望能做到7x24小时不停服务,这样就给扩容带来了更大的挑战。笔者看过比较好的方案就是58沈剑提出的成倍扩容方案。如下图所示,假设现在已经有2张表:tb_user_1,tb_user_2。且有两个库是主备关系,并且分表算法是hash(user_id)%2:

扩容-1

现在要扩容到4张表,做法是将两个库的主从关系切断。然后slave晋升为master,这样就有两个主库:master-1,master-2。新的分表算法是:

  • 库选择算法为:hash(user_id)%4的结果为1或者2,就选master-1库,hash(user_id)%4的结果为3或者0,就选master-2库;
  • 表的选择算法为:hash(user_id)%2的结果为1则选tb_user_1表,hash(user_id)%2的结果为0则选tb_user_2表。

如此以来,两个库中总计4张表,都冗余了1倍的数据:master-1中tb_user_1冗余了3、7、11...,master-1中tb_user_2冗余了4、8、12...,master-2中tb_user_1冗余了1、5、9...,master-2中tb_user_2冗余了2、6、10...。将这些冗余数据删掉后,库、表、数据示意图如下所示:


扩容-2

即使这样方案,还是避免不了分表时的存量数据迁移,以及分表后业务发展到一定时期后的繁琐扩容。那么有没有一种很好的方案,能够一劳永逸,分表时不需要存量数据迁移,用户量无论如何增长,扩容时都不需要迁移存量数据,只需要新增一个数据库示例,修改一下配置即可。软件开发行业,一个方案能撑过3~5年就是一个很优秀的方案,我们现在YY的是整个生命周期内都不用改动的完美的方案。没错,我们在寻找银弹

这个方案笔者在两个地方都接触到了:

  1. 某V厂面试时,部门老大提出的方案;
  2. 和美团大牛普架讨论了解到的CAT存储方案;

说明:CAT是美团点评开源的APM,目前在Github上的star已经破万(Github地址:https://github.com/dianping/cat),比skywalking和pinpoint还快,如果你正在选型APM,而且能接受代码侵入,那么CAT是一个不错的选择。

CAT存储方案是按照写入时间顺序存储,假设每小时写入量是千万级别,那么分表就按照小时维度。也就是说,2019年7月18号10点数据写入到表tb_catdata_2019071810中,2019年7月18号12点数据写入到表tb_catdata_2019071812中,2019年7月20号14点数据写入到表tb_catdata_2019072014中。这样做的优点如下:

  1. 历史数据不用迁移;
  2. 扩容非常简单;

缺点如下:

  1. 读写热点集中,所有写操作全部打在最新的表上。

有没有发现,这个方案的优点就是我们需要的。BINGO,要的就是这样的方案。那么对应到用户表上来具体的分表方案非常类似:按照range切分。需要说明的是,这个方案的前提是用户ID一定要趋势递增,最好严格递增。笔者给出3种用户ID递增的方案:

  • 自增ID

假设存量数据用户表的id最大值是960W,那么分表算法是这样的,表序号只需要根据user_id/10000000就能得到:

  1. 用户ID在范围[1, 10000000)中分到tb_user_0中(需要将tb_user重命名为tb_user_0);
  2. 用户ID在范围[10000000, 20000000)中分到tb_user_1中;
  3. 用户ID在范围[20000000, 30000000)中分到tb_user_2中;
  4. 用户ID在范围[30000000, 40000000)中分到tb_user_3中;
  5. 以此类推。

如果你的tb_user本来就有自增主键,那这种方案就比较好。但是需要注意几点,由于用户ID是自增的,所以这个ID不能通过HTTP暴露出去,否则可以通过新注册一个用户后,就能得到你的真实用户数,这是比较危险的。其次,存量数据在单表中可以通过自增ID生成,但是当切换分表后,用户ID如果还是用自增生成,需要注意在创建新表时设置AUTO_INCREMENT,例如创建表tb_user_2时,设置AUTO_INCREMENT=10000000,DDL如下:

CREATE TABLE if not exists `tb_user_2` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `username` varchar(16) NOT NULL COMMENT '用户名',
  `remark` varchar(16) NOT NULL COMMENT '备注'
) ENGINE=InnoDB AUTO_INCREMENT=10000000;

- 这样的话,当新增用户时,用户ID就会从10000000开始,而不会与之前的用户ID冲突
insert into tb_user_2 values(null, 'afei', 'afei');
  • Redis incr

第二种方案就是利用Redis的incr命令。将之前最大的ID保存到Redis中,接下来新增用户的ID值都通过incr命令得到。然后insert到表tb_user中。这种方案需要注意Redis主从切换后,晋升为主的Redis节点中的ID可能由于同步时间差不是最新ID的问题。这样的话,可能会导致插入记录到tb_user失败。需要对这种异常特殊处理一下即可。

  • 利用雪花算法生成

采用类雪花算法生成用户ID,这种方式不太好精确掌握切分表的时机。因为没有高效获取tb_user表数据量的办法,也就不知道什么时候表数据量达到1000w级别,也就不知道什么时候需要往新表中插入数据(select count(*) from tb_user无论怎么优化性能都不会很高,除非是MyISAM引擎)。而且如果利用雪花算法生成用户ID,那么还需要一张表保存用户ID和分表关系:

关系表

笔者推荐第一种方案,即利用表自增ID生成用户ID:方案越简单,可靠性越高。其他两种方案,或者其他方案或多或少需要引入一些中间件或者介质,从而增加方案的复杂度。新方案效果图如下:

新方案效果图

回顾总结

我们回头看一下这种用户表方案,满足了存量数据不需要做任何迁移(除非是存量数据远远超过单表承受能力)。而且,无论用户规模增长到多大量级,1亿,10亿,50亿,后面都不需要做数据迁移。而且也不再需要开发和运维介入。因为整个方案,会自己往新表中插入数据。我们唯一需要做的就是,根据硬件性能,约定一个库允许保存的用户表数量即可。假如一个库保存64张表,那么当扩容到第65张表时,程序会自动往第二个库的第一张表中写入。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容