一波反向操作, 我把几十个生产库给合了

src=http___e0.ifengimg.com_07_2018_1127_E721EB001CB3DDB52AFD64E57285F2F0F083A5D7_size88_w465_h642.jpeg&refer=http___e0.ifengimg.jpeg

前言

近三个月没更新文章, 可不是我偷懒, 而是工作太忙,实在没有时间.

时间回到三个月前, 领导突然找到我, 说要准备搞个“大工程”,一起讨论讨论.

什么大工程呢? 简而言之, 合库!

将几十个数据库合并成一个数据库!

image.png

我想很多技术人和我一样, 第一反应就是: 啥, 合库?

分库分表大多技术人或多或少都知道一些, 但是对于合库可能就没那么熟悉了.

不过, 仔细一想, 这样的需求还是存在的, 比如游戏常见的“合区服”, 其实技术层面可能就是通过合库合表实现的.

合库背景简介

和相关项目组的人员聊了一轮后, 得到了以下信息:

  • 共35个城市库, 表结构一致, 表的主键为Mysql自增ID

  • 共2个公共库,用来存放共有信息以及来自各个城市库产生的业务信息,以city_id区分.

  • 城市库和公共库均有触发器/存储过程

  • 各个城市发展业务进度不同, 所以城市库间的大小有了分化, 小城市的库特别小, 甚至部分表记录为空; 大城市的的库单表可达千万级别.

  • 无论城市库还是公共库中的表, 均存在json或字符串的形式来记录ID的字段: json里面有ID值, 字符串字段有以空格,逗号分隔的ID值.

  • 数据合并后, 应用程序也得调整数据库层面的代码.

看到这里, 你可能也和咸鱼会有一样的困惑:

大城市单表都上千万数据了, 单独一个库不是很正常的设计么?
为啥要冒着风险合并这么多库呢?
而且就算合并成功了, 接踵而至的性能问题等等, 最终不还是得通过分库分表来解决???

关于这些问题, 咸鱼当时也是想到了, 不过和项目人员以及业务人员沟通后, 方知“项目早期被过度设计的痛点”.

飞速发展的业务, 项目迭代速度很快, 而这种多个城市库的设计让迭代跟不上业务发展的脚步;

另外, 这种设计也对业务人员统计全国数据造成了一些困扰……

image.png

总之, 再三沟通确认后, 最终决定对项目城市库进行合并, 让设计回归最原始的样子.

合库方案设计

不难看出, 妨碍数据合并的问题如下:

  • 虽然各个城市库表结构一致, 但是表的主键ID自增, 合并势必冲突
  • 城市库进行分布式唯一改造后, 两个公共库也得对应进行调整
  • 城市库及公共库使用了触发器/存储过程, 需要考虑影响
  • 城市库和公共库中表均存在json和字符串类型的主/外键, 需要解析出来进行全局唯一性处理
  • 35个城市库及2个公共库, 不用想, 一晚上肯定搞不完, 毕竟业务也得继续跑, 为了合并停服几天不现实!
  • 涉及合库项目的所有应用程序也得相应调整数据层面的代码
  • 最重要的一点, 出现问题如何回滚?

经过了几轮讨论, 初步形成了以下实施大纲:

  • ID改造: 进行ID全局唯一性改造, 涉及城市库和公共库的所有主/外键字段(包含json及字符串)

  • 合库: 进行合库操作, 保留原库.

  • 切库: 将涉及到的应用程序调整, 读写新库.

大致方案敲定, 接下来就是进行实施细节的一些商讨, 力求能最大程度的降低风险.
比如:

  • 如何进行ID改造才能在保证全局唯一的基础上,最小的改动?
  • json、字符类型的存储的ID如何处理?
  • 触发器、存储过程如何处理?
  • ID改造期间出现问题如何回滚数据?
  • 合库操作如何进行? 保留原库的前提下, 如何原库的数据变动能持续同步至新库?
  • 这么多城市, 需要分批进行么?
  • 停服更新还是热更新?
  • …………

经历了将近半个月的脑暴, 最终在以上大纲的基础上完善了细节规则的制定:

考虑到城市较多,数据量大, 为了减少风险, 对35个城市按业务发展进行了批次划分, 大致如下:

试点城市(选中了两个数据小的城市): A、B
第一批次: 华东区域城市
第二批次: 华北区域城市
第三批次: 中西区域城市
第四批次: 华南区域城市

PS: 分批的精髓就在于, 让一部分小城市试水, 发现问题可以及时“止损”.

合库方案实施步骤

几轮探讨下来, 要完成最终的合库, 大致有以下几个步骤, 每个步骤均按照以上的城市规划, 分批进行, 尤其是试点的两个城市, 需要尽可能的保证试点时间, 排查各种错误,方便后续几批城市的复制.

分批停服更新

避开业务使用时间, 屏蔽当次试点城市的所有应用服务的访问.

PS: 只屏蔽当次试点城市的流量进入, 不影响其他城市的访问!

实施步骤:

  • 提前部署可以屏蔽相关城市访问的代码逻辑
  • 提前给受影响城市的一线发停服通知
  • 屏蔽相关应用服务 (防止ID改造时, 导致有流量进来)
  • 关闭相关定时任务 (防止ID改造时, 定时任务导致的数据变动)

分批数据备份

每批次城市处理时, 均需要备份数据, 用于紧急回退.

实施步骤:

  • 备份城市库: 全库备份本次受影响的城市库
  • 备份公共库: 备份公共库中本次城市的所有记录

这里采用的是备份城市记录的方式, 通过新建一张表, 然后复制相应的记录进新表即可.
意外恢复时, 只需根据ID更新恢复即可.

分批ID改造:

ID改造, 最重要的就是新ID的规则, 既要保证全局唯一, 又要保证最小影响.
最终我们商讨后采用了以下方式生成

image.png

新ID = 城市库独立基数 + 原ID

新ID生成的规则说明

其实, 就ID的生成规则采用哪种形式, 我们内部也讨论了一波.
最后基于“对当前应用影响最小”的思想下, 团队摒弃了类似雪花算法这类的规则.
而最终采用了新ID = 城市库独立基数 + 原ID这种规则

PS: 要是采用雪花算法规则生成新ID, 那么应用代码上就得调整有关新增的逻辑,同时还得修数据.

城市库独立基数的生成规则

基数的规则需要基于生产数据的分析, 比如35个城市库中最大的库是A, 最小的是B库:

A中最大的表数据已达100 00000
B中最大表数据只有10 0000

此时, 基数应该在100 0000之上考虑, 考虑到我们分批进行ID改造工作, 还得考虑城市的增量以及分批合库、切库的影响, 所以在此基础上预留足够大的增量空间.

为什么要考虑城市的增量?

若如下设计:
A库ID规则 = 1000 0000 + 原ID
B库新ID = 101 0000 + 原ID

因为我们是分批合库的, 是有时差的, 那么按照A库的增长速度, 很快就会出现101 0000以后的ID, 这必将导致合库时数据冲突.

为什么要考虑切库的增量?

若我们为每个城市预留100w的城市数据增量, 那么基数设置如下:
A库新ID = 1000 0000 + 原ID
B库新ID = 1100 0000 + 原ID
C库新ID = 1200 0000 + 原ID
K库为新库,用来合并所有城市库的数据.

如果仅仅是ID的处理, 显然什么问题, 毕竟最大的A库最多的数据也才100W, 业务再这么发展, 也无法短时间内翻倍.

可一旦考虑到合库和切库, 那么预留的100W数据增量可能就不够用了.
A,B库分批先合进K,然后应用程序把来自A、B城市的数据切到K库, 那么来自A、B城市的数据都将再B的基数上继续增加.

举例:
A: 1000 0010
B: 1100 0010
合进K库的某表后,对应表记录为
1000 0010
1100 0010
再进行切库, 把A,B城市的数据接入K库,若此时A有新数据进来,那么数据为1100 0011, 而不是1000 0011;

这就表示, A、B城市共享了100W的数据增量, 那么就有可能数据迅速增加至1200 0000 , 那么就与C库冲突.

所以, 我们必须考虑切库的时间影响, 设计好数据增量来防止这种情况.

新ID的生成规则制定完后,下一步就是具体的实施了.
通过对生成库数据的排查, 我们大致对需要改造的ID字段做了如下区分:

普通数值ID字段: int型字段, 存储单个ID

特殊型ID字段: varchar或text类型字段, 存储多个以符号分隔的ID或直接存储json字符串

不难看出,普通数值ID字段最好处理, 可能一个update语句即可
update table set uid = uid + 基数

特殊型ID字段就不行了, 必须要程序去解析然后处理.

最后我们决定针对ID改造这件事专门写个服务来进行处理.

那么, 首先需要做的事就是必须要有一个明确的处理规则.

我们细致的排查城市库和公共库的表结构, 列出每张表包含主外键的字段, 区分数字类型 和 特殊类型(字符串和json), 同时特殊类型需要注明解析规则.

数字类型: 某个字段就用来存放某个主外键ID
特殊类型: 某个字段存放的是逗号分隔的ID串, 或包含一堆ID的JSON串

表格大致如下:

表名 字段名 字段类型(1主键/2外键/3/json对象/4json数组/5字符串) 字段包含的外键 特殊类型解析说明 库类型(1城市库/2公库一/3公库二)
student id 1 1
student class_id 2 1
student schoolObject 3 school_id schoolObject.school_id需要改造 1
student schoolObject 3 city_id schoolObject.city_id需要改造 1
student teacherIds 5 逗号分隔的id外键 1

有了规则, 接下来无非是按部就班的coding工作.

image.png

实施步骤:

  • 对城市库所有表加上city_id字段并赋值, 后续合库用来数据隔离
  • 修改原表中存放ID的类型

确认原表的ID字段类型足够放下新ID! (重要)

关于这点, 我们就踩了坑, 因为前期忽略了这个事.

原ID字段为数字型(int)
原库的表所有存放ID主外键的字段均采用了“int”型, 而我们生成的新ID是放不下的, 所以, 得先将表的字段类型调整置“bigint”.

原ID字段为特殊型(varchar、text)
text可能无需处理, 不过varchar这类的字段就需要加长了! 在我们处理的表中有varchar(500)的字段存放逗号分隔的ID串, ID改造后, 直接放不下了.

  • 屏蔽受影响的触发器

排查生产库时发现了城市库和公共库均有insert和update触发器, 会影响我们的ID改造工作.
针对城市库和公共库的触发器,我们必须要分开处理,要是一刀切的全部删除, 那么就会对非试点城市造成影响.

ID改造前删除城市库触发器, 改造完成后恢复.
ID改造前修改公共库的触发器(增加除法条件,屏蔽当次试点城市), 改造完成后恢复.

  • 城市库ID改造
  • 公共库ID改造
  • 恢复受影响的触发器
踩过的一些坑
  • 所有表存放ID的字段是int型, 放不下新ID,需改为bigint
  • 部分表存放ID的字段是varchar类型, ID修改后, 因长度不够放不下, 需要进行加长
  • 部分表编码类型为utf8, 但是json串数据中居然有emoj表情包数据,
    导致解析出来后无法更新回去, 需要修改表的编码模式为utf8mb4
  • 当对表的主键ID进行ID改造时, AUTO_INCREMENT值居然失效了.

举例:
test表, 主键为id, int型, 自增值已经到10了.
首先修改类型:
alter table test CHANGE id id BIGINT (20) auto_increment;
然后修改ID
update test set id = id+1000
如果此时你新增记录, 你会发现, auto_increment并没有在1010后继续新增, 而是在10的基础上新增, 也就是新纪录的id为11;

所以我们对auto_increment需要特殊处理下:
首先修改类型:
alter table test CHANGE id id BIGINT (20);
然后修改ID
update test set id = id+1000
组后加上auto_increment
alter table test CHANGE id id BIGINT (20) auto_increment;

  • 空表的处理

虽说所有城市库的表结构都一致, 不过每个城市因业务发展程度不一样, 导致部分城市库的部分表记录为空. 这种空表如果不处理也将对后续的合库造成影响,所以必须将这些空表的AUTO_INCREMENT改造为城市基数.

步骤3. 合库: 进行合库操作, 保留原库

关于合库, 同样也是基于我们的大前提: 分批进行.

这里我们使用了阿里的DTS同步工具,关于这个工具简单说明下它的作用

  1. 将多个库实时同步到一个库, 前提是库中的表结构必须完全一样,且ID不能冲突

所以合库的前提便是城市库表结构一致,且ID全局唯一

  1. 多对一实时同步时, 只能单向同步,不能反向

DTS多对一同步时, 只支持单向的同步, 也就是只能是A、B、C的数据同步到SUM, 而不能反向.
这一点比较重要.
当你对几个试点城市切库时, 数据读写全在新库中, 这些数据不支持反向同步到老的城市库中的, 万一发现重大问题需要回滚时, 则需单独制定一个靠谱的数据回退方案.

  1. 多对一实时同步时, 新库中AUTO_INCREMENT最终以最大的自动设置

起初我们调研时对此有疑惑:
A的AUTO_INCREMENT = 1000;
B的AUTO_INCREMENT = 2000;
C的AUTO_INCREMENT = 3000;
那么, DTS同步A、B、C到SUM时, 最终SUM的AUTO_INCREMENT和同步顺序是否有关系???

通过询问以及实验, 最终发现, 并无关系,
通过DTS多对一同步, SUM库总是以AUTO_INCREMENT最大的C为准.

  1. 同步是一种关联关系, 可以手动断开

使用DTS进行同步时, 建立这种同步关系也就是点个按钮的事.
在我们合库时, 期间经历了好几次业务迭代, 其中有涉及到表的修改变动,起先我们以为涉及表结构的调整需要停止DTS, 之后发现, DTS同步只受新库表结构的影响.
也就是A、B、C的表结构变动可以同步到SUM, 无需停止DTS; 若直接对SUM调整表结构, 那么DTS会报错.

注意点:
这里注意, 虽然城市库业务表结构是一致的, 不过因为一些原因, 各个城市库中均有一些备份表存在, 这导致了数量上差异, 我们在处理时, 评估了这批差异表, 发现没什么用后直接删除来保证城市库间的一致性.

步骤4. 切库: 将涉及到的应用程序调整, 读写新库.

关于切库, 这块, 一样是按批次试点切, 到一步时其实没什么大的风险了,无非是代码上的调整, 读写全在新库中.

唯一的风险点也就是前面提过的发现了重大问题, 需要回退增量数据到城市库.

小结

从合库计划的讨论,到实施, 再到完成, 经历了三个多月忙碌的时光, 不过直到最后, 还是感觉挺充实的, 毕竟, 这算一份难得的经历.

回顾下整个合库的经历, 其实最刺激的点还是在ID改造那一块, 熬个夜, 脚本一点, 然后就是紧张的等待上几个小时....

关于ID改造的脚本, 后续有时间再整理分享.

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

推荐阅读更多精彩内容