共计 4770 个字符,预计需要花费 12 分钟才能阅读完成。
背景
在星爷的《大话西游》中有一句十分闻名的台词:“已经有一份真挚的感情摆在我的背后我没有珍惜,等我失去的时候才追悔莫及,世间最苦楚的事莫过于此,如果入地能给我一次再来一次的机会,我会对哪个女孩说三个字:我爱你,如果非要在这份爱上加一个期限,我心愿是一万年!”在咱们开发人员的眼中,这个感情就和咱们数据库中的数据一样,咱们多心愿他一万年都不扭转,然而往往大失所望,随着公司的一直倒退,业务的一直变更,咱们对数据的要求也在一直的变动,大略有上面的几种状况:
分库分表:业务倒退越来越快,导致单机数据库接受的压力越来越大,数据量也越来越多,这个时候通常会应用分库的办法去解决这个问题,将数据库的流量均分到不同的机器上。从单机数据库到分库这个过程,咱们就须要残缺的迁徙咱们的数据,咱们能力胜利的分库的形式上应用咱们的数据。
更换存储介质:下面介绍的分库,一般来说咱们迁徙完之后,存储介质仍然是同样的,比如说之前应用的是单机 Mysql,分库之后就变成了多台机器的 Mysql,咱们的数据库表的字段都没有发生变化, 迁徙来说绝对比较简单。有时候咱们分库分表并不能解决所有的问题,如果咱们须要很多简单的查问,这个时候应用 Mysql 可能就不是一个靠谱的计划,那么咱们就须要替换查问的存储介质,比方应用 elasticsearch,这种的迁徙就会略微要简单一些,波及到不同存储介质的数据转换。
切换新零碎:个别公司在高速倒退中,肯定会呈现很多为了速度快而后反复建设的我的项目,当公司再肯定时间段的时候,往往这部分我的项目会被合并,变成一个平台或者中台,比方咱们一些会员零碎,电商零碎等等。这个时候往往就会面临一个问题,将老的零碎中的数据须要迁徙到新的零碎中,这个时候就更加简单了,有可能不仅是存储介质有变动,有可能我的项目语言也不同,从更下层的角度来看,部门有可能也不同,所以这种数据迁徙的难度是比拟高,危险也更加的大。
在理论业务开发中,咱们会依据不同的状况来做出不同的迁徙计划,接下来咱们来讨论一下到底应该怎么迁徙数据。
数据迁徙
数据迁徙其实不是欲速不达的,每一次数据迁徙都须要一段漫长的工夫,有可能是一周,有可能是几个月,通常来说咱们迁徙数据的过程根本都和下图差不多:
首先咱们须要将咱们数据库曾经存在的数据进行批量的迁徙,而后须要解决新增的这部分数据,须要实时的把这部分数据在写完本来的数据库之后而后写到咱们的新的存储,在这一过程中咱们须要一直的进行数据校验。当咱们校验根本问题不大的时候,而后进行切流操作,直到齐全切流之后,咱们就能够不必再进行数据校验和增量数据迁徙。
存量数据迁徙
首先咱们来说一下存量数据迁徙应该怎么做,存量数据迁徙在开源社区中搜寻了一圈发现没有太好用的工具,目前来说阿里云的 DTS 提供了存量数据迁徙,DTS 反对同构和异构不同数据源之间的迁徙,根本反对业界常见的数据库比方 Mysql,Orcale,SQL Server 等等。DTS 比拟适宜咱们之前说的前两个场景,一个是分库的场景,如果应用的是阿里云的 DRDS 那么就能够间接将数据通过 DTS 迁徙到 DRDS, 另外一个是数据异构的场景,无论是 Redis 还是 ES,DTS 都反对间接进行迁徙。
那么 DTS 的存量迁徙怎么做的呢?其实比较简单大略就是上面几个步骤:
当存量迁徙工作启动的时候,咱们获取以后须要迁徙的最大的 id 和最小 id
设置一个分段,比方 1 万,从最小 id 开始每次查问 1 万的数据给 DTS 服务器,交给 DTS 解决。sql 如下:
`select * from table_name where id > curId and id < curId + 10000;`
* 1
3. 当 id 大于 maxId 之后,存量数据迁徙工作完结
当然咱们在理论的迁徙过程中可能不会去应用阿里云,或者说在咱们的第三个场景下,咱们的数据库字段之间须要做很多转换,DTS 不反对,那么咱们就能够模拟 DTS 的做法,通过分段批量读取数据的形式来迁徙数据,这里须要留神的是咱们批量迁徙数据的时候须要管制分段的大小,以及频率,避免影响咱们线上的失常运行。
增量数据迁徙
存量数据的迁徙计划比拟无限,然而增量的数据迁徙办法就是百花齐放了,一般来说咱们有上面的几种办法:
DTS: 阿里云的 DTS 算是一条龙服务了,在提供存量数据迁徙的同时也提供了增量数据迁徙,只不过须要按量免费。
服务双写:比拟适宜于零碎没有切换的迁徙,也就是只换了存储然而零碎还是同一个,比如说分库分表,redis 数据同步等,这个的做法比较简单间接在代码外面同步的去写入须要迁徙的数据,然而因为不是同一个数据库就不能保障事务,有可能导致迁徙数据的时候会呈现数据失落,这个过程通过后续的数据校验会进行解决。
MQ 异步写入:这个能够实用于所有的场景,当有数据批改的时候发送一个 MQ 音讯,消费者收到这个音讯之后再进行数据更新。这个和下面的双写有点相似,然而他把数据库的操作变成了 MQ 异步了出问题的概率就会小很多
监听 binlog: 咱们能够应用之前说过的 canal 或者其余的一些开源的如 databus 去进行 binlog 监听,监听 binlog 的形式 就和下面的音讯 MQ 形式一样,只是发送音讯的这一步被咱们省略了。这个形式的一个开发量来说根本是最小的。
这么多种形式咱们应该应用哪种呢?我集体来说是比拟举荐监听 binlog 的做法的,监听 binlog 缩小开发成本,咱们只须要实现 consumer 逻辑即可,数据能保障一致性,因为是监听的 binlog 这里不须要放心之前双写的时候不是一个事务的问题。
数据校验
后面所说的所有计划,尽管有很多是成熟的云服务 (dts) 或者中间件(canal),然而他们都有可能呈现一些数据失落,呈现数据失落的状况整体来说还是比拟少,然而十分难排查,有可能是 dts 或者 canal 不小心抖了一下,又或者是接收数据的时候不小心导致的失落。既然咱们没有方法防止咱们的数据在迁徙的过程中失落,那么咱们应该通过其余伎俩来进行校对。
通常来说咱们迁徙数据的时候都会有数据校验这一个步骤,然而在不同团队可能会选取不同的数据校验计划:
之前在美团的时候,咱们会做一个双读,也就是咱们所有的读取都会从新的外面读取一份,然而返回的还是老的,这个时候咱们须要做这部分数据的校验,如果有问题能够发出报警人工修复或者主动修复。通过这种形式,咱们罕用的数据就能很快的进行一个修复,当然也会不定时的去跑一个全量的数据 check,只是这种 check 进去修复数据的工夫就比拟滞后。
当初在猿辅导之后,咱们没有采纳之前的那种形式,因为双读 check 尽管能很快发现数据的不对,然而咱们并没有对这部分数据有那么高的一个实时性校验并且双读的一个代码开发量还是略微比拟大的,然而又不能依附不定时全量 check 去保障,这样就会导致咱们的数据校验工夫会十分的缩短。咱们采取了一个折中的办法,咱们借鉴了对账外面的 T + 1 的一个思路,咱们每天凌晨获取老数据库中昨天更新的数据,而后和咱们新数据库中的数据做一一比对,如果有数据不一样或者数据缺失,咱们都能够立马进行一个修复。
当然在理论开发过程中咱们也须要留神上面几点:
数据校验工作的一个正确性如何保障,校验工作原本就是去校对其余数据的,然而如果他本身呈现了问题,就失去了校验的意义,这里目前来说只能靠 review 代码这种形式去保障校验工作的正确性。
校验工作的时候须要留神日志的打印,有时候呈现问题可能是间接所有数据呈现问题,那么校验工作就有可能会打出大量的谬误日志,而后进行报警,有可能会将零碎打挂,或者说影响其他人的服务。这里如果要简略一点搞,能够将一些非人工解决的报警搞成 warn,简单一点搞得话,能够封装一个工具,某个 error 打印再某个时间段超过一定量而后就不必再打印了。
校验工作留神不要影响线上运行的服务,通常校验工作会写很多批查问的语句,会呈现批量扫表的状况,如果代码没有写好很容易导致数据库挂掉。
切流
当咱们数据校验根本没有报错了之后,阐明咱们的迁徙程序是比较稳定的了,那么咱们就能够间接应用咱们新的数据了吗?当然是不能够的,如果咱们一把切换了,顺利的话当然是很好的,如果呈现问题了,那么就会影响所有的用户。
所以咱们接下来就须要进行灰度,也就是切流。对于不同的业务切流的的维度会不一样,对于用户维度的切流,咱们通常会以 userId 的取模的形式去进行切流,对于租户或者商家维度的业务,就须要依照租户 id 取模的形式去切流。这个切流须要制订好一个切流打算,在什么时间段,放出多少的流量,并且切流的时候肯定要抉择流量比拟少的时候进行切流,每一次切流都须要对日志做具体的察看,呈现问题尽早修复,流量的一个放出过程是一个由慢到快的过程,比方最开始是以 1% 的量去一直叠加的,到前面的时候咱们间接以 10%,20% 的量去疾速放量。因为如果呈现问题的话往往在小流量的时候就会发现,如果小流量没有问题那么后续就能够疾速放量。
留神主键 ID
在迁徙数据的过程中特地要留神的是主键 ID,在下面双写的计划中也提到过主键 ID 须要双写的时候手动的去指定,避免 ID 生成程序谬误。
如果咱们是因为分库分表而进行迁徙,就须要思考咱们当前的主键 Id 就不能是自增 id, 须要应用分布式 id,这里比拟举荐的是美团开源的 leaf,他反对两种模式一种是雪花算法趋势递增,然而所有的 id 都是 Long 型,适宜于一些反对 Long 为 id 的利用。还有一种是号段模式,这种会依据你设置的一个根底 id,从这个下面一直的减少。并且根本都走的是内存生成,性能也是十分的快。
当然咱们还有种状况是咱们须要迁徙零碎,之前零碎的主键 id 在新零碎中曾经有了,那么咱们的 id 就须要做一些映射。如果咱们在迁徙零碎的时候曾经晓得将来大略有哪些零碎会迁徙进来,咱们就能够采纳预留的形式,比方 A 零碎当初的数据是 1 到 1 亿,B 零碎的数据也是 1 到 1 亿,咱们当初须要将 A,B 两个零碎合并成新零碎,那么咱们能够略微预估一些 Buffer, 比方给 A 零碎留 1 到 1.5 亿,这样 A 就不须要进行映射,B 零碎是 1.5 亿到 3 亿,那么咱们转换成老零碎 Id 的时候就须要减去 1.5 亿,最初咱们新零碎的新的 Id 就从 3 亿开始递增。
然而如果零碎中没有做布局的预留段怎么办呢?能够通过上面两种形式:
须要新增一个表,将老零碎的 id 和新零碎的 id 做一个映射记录,这个工作量还是比拟大的,因为咱们个别迁徙都会波及几十上百张表,记录的老本还是十分的高。
如果 id 是 Long 型的话,咱们能够好好利用 long 是 64 位这个因素,咱们能够制订一个规定,咱们新零碎的 id 都是从一个比拟大的数开始,比方从大于 Int 的数开始,将小 Int 的那局部数都能够留给咱们的老零碎做 Id 迁徙,比方咱们下面的 1.5 亿的数据量,其实只用了 28 位,咱们的 Int 是 32 位,那么还有 4 位能够应用,这个 4 位能够代表 16 个零碎做迁徙,当然如果布局中有更多的零碎做迁徙,能够将新零碎的 id 起始点设置得更大一点。如下图所示:
总结
最初简略来总结下这个套路,其实就是四个步骤,一个留神:存量,增量,校验,切流,最初再留神一下 id。不论是多大量级的数据,基本上依照这个套路来迁徙就不会呈现大的问题。心愿能在大家的后续迁徙数据工作中,这篇文章能帮忙到你。
以下文章来源于咖啡拿铁,作者咖啡拿铁
总结了一些 2020 年的面试题,这份面试题的蕴含的模块分为 19 个模块,别离是:Java 根底、容器、多线程、反射、对象拷贝、Java Web、异样、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、MyBatis、RabbitMQ、Kafka、Zookeeper、MySQL、Redis、JVM。
获取材料以上材料:关注公众号:有故事的程序员,获取学习材料。
记得点个关注 + 评论哦~