乐趣区

关于分库分表:手把手教学分库分表平滑切换

前言

本文假如业务零碎面临了单表数据量存储的挑战,须要对表进行分库分表,本文不在此形容分库分表的具体计划,假如分库分表计划曾经确定,介绍如何施行单表到分库分表计划的平滑切换。

场景假如

假如业务零碎须要对订单表 order_tab 分库分表,分库分表键为 order_id,类型为 varchar(64),分库分表的最终计划为:8 个库,1024 张表,依据 order_id 计算 CRC32Hash % 1024 失去表名索引,分库索引依据表名索引对分库数取模。

tableCount = 1024
dbCount = 8
tableIndex = CRC32HashInt(order_id) % tableCount
dbIndex = tableIndex / (tableCount / dbCount)
分库分表前 分库分表后
order_tab order_db_0000.order_tab_00000000 ~ order_db_0000.order_tab_00000127 ~
order_tab order_db_0001.order_tab_00000128 ~ order_db_0001.order_tab_00000255 ~
order_tab
order_tab order_db_0007.order_tab_00000898 ~ order_db_0007.order_tab_00001023 ~

剖析

单表到分库分表的切换,对于业务零碎而言,面临的技术实现的变更蕴含但不限于以下几点:

  • 单行数据读写
  • 批量数据读写
  • 关联查问
  • 分布式事务

单行数据读写

分库分表计划具备的一个特点是,须要指定分表键能力确定具体数据存储在哪一个库哪一张表,因此对于单条数据的查问或者变更,在确定了分表键条件后,读写须要能够路由到具体的表,实现较为简单,仅需对表名的获取上做一次计算即可实现。

批量数据读写

批量查问的场景分为带分表键的查问和不带分表键的查问,对于带分表键的读写而言,指标数据表能够确定为单表;而对于不带分表键的读写,无奈确定指标表,如果分表数目较多,遍历所有数据表进行读写再聚合,显然在性能上是不可承受的,须要设计对该读写场景下的额定反对。

关联查问

在没有额定的中间件反对的状况下,关联查问的 A,B 表须要在同一个数据库,因此大多数场景下,抉择了分库分表,也即放弃了关联查问,实际上,大部分关联查问的场景,也是能够应用更多的简略查问来代替;如果切实无奈替换,那么能够保障关联的 A,B 表在设计分库分表计划时,始终让 A,B 分到一个库当中。

分布式事务

对单库的写事务,数据库本地事务能够完满保障,而切换到分库分表计划时,一个写流程可能变更了不同库的表,那么本地事务曾经无奈保障同时胜利或同时失败。因此在这类场景下,分布式事务问题也即成了必须要解决的问题。
至于如何实现分布式事务的解决,是一个十分宏大的问题,目前市面上也有一些中间件能够实现分布式事务,集成难度较大,大略也有因为不成熟的因素,大部分业务场景都会放弃强一致性的保障,而抉择依据具体业务实现肯定的弥补来实现最终一致性。

切换施行

理论的切换计划可能因为业务复杂度差别,计划上会有比拟大的差异,接下来将介绍较为通用的实现计划:

切换前筹备

接口重构

基于前文剖析,切换到分库分表计划后,须要对本来对 order_tab 表的读写接口进行改写,为了避免迁徙时存在代码脱漏,能够将对于 order_tab 表的读写接口全副聚合在 OrderManager 模块代码内,那么在实现过程,须要齐全重构 OrderManager 内所有对 order_tab 的读写接口,假如重构后的代码聚合在 OrderSharedManager。
在重构过程,须要思考重构分库分表后无奈反对的关联查问的代码。

数据聚合

分库分表后,对于一部分的查问场景无奈做到完满反对,如不带分表键的查问,在理论实现过程,通过查问所有数据表的后果再进行聚合是极不合理的形式。而不带分表键的查问在业务侧无奈防止,比方报表查问,数据导出等场景。
那么数据聚合便成了必须,市面上的中间件比方 ElasticSearch,TiDB 均能够用作数据聚合的存储介质,为了反对无分表键的查问,能够通过将分库分表的数据同步至 ElasticSearch 或者 TiDB 来反对无分表键或者批量查问的场景,在接口重构时也须要将该类接口重构为查问对应的存储引擎。

Adaptor 适配

平滑切换的特点,在切换全量流量到新表前,始终存在对于旧表的读写,那么在实现侧须要做到新旧表的读写兼容,设计 datasource 开关 (配置核心进行配置) 来指定具体须要将读写申请打入新表还是旧表,实现 OrderAdaptorManager,并实现全量的对 order_tab 读写接口。对应的,须要依据 datasource 开关判断来调用 OrderManager 或者 OrderSharedManager。
datasource 开关定义如下:

配置值 含意
0 切换前,读写旧表
1 切换中,优先读新表,新表不存在则读写旧表
2 切换后,全量读写新表

兼容逻辑如下图所示:

实现了以上筹备工作,能够开始设计具体的切流实施方案。

切换计划一:实时同步增量数据,全量同步旧数据,一键切换

切换前维持 datasource=0,对 order_tab 表的所有读写维持调用旧代码,后续能够通过开关管制切流节奏:

步骤一:订阅旧表数据 binlog,回写至新表

此阶段服务层接口全量读写旧表,设置切流开关 datasource=0,开发订阅 binlog 程序,将对 order_tab 所有的变更流量回放到新表,并依据分库分表策略,将对应的数据回写到新表当中,因为局部更新或者删除在新表中不存在,能够疏忽该类写失败的状况。

步骤二:脚本全量迁徙旧数据

第一步实现后,将实时的更新增量数据至新表,无异样后开发批量迁徙脚本,将旧表数据一次性迁徙至新表中,旧表数据到新表数据须要依据分库分表策略,将对应的数据写入到不同的库或表。此时数据流放弃与步骤一统一。
因为在迁徙过程中的增量数据曾经通过 binlog 实时同步至了新表,因此对这部分数据的迁徙将间接跳过。

步骤三:开启开关切流至新表

数据全量迁徙结束后,新旧表当中具备了全量的数据,如有必要,能够进行新旧数据的对账,确保数据迁徙不存在脱漏或者失败。如无异样时,将读写流量开关设置 datasource=2,此时的读写流量如下:

步骤四:敞开 binlog 订阅

实现了流量的切换后,旧表 DB 将不再存在流量间接进行读写,同时能够依据订阅服务判断是否再无新流量写入旧表,确保曾经切换结束后,关停订阅 binlog 服务,实现全副的迁徙步骤。

切换计划二:双写同步新数据,全量同步旧数据,安稳切换

思考到订阅 binlog 的实现是门槛稍高的形式,如不具备订阅 binlog 的能力,能够通过在读写接口进行兼容的形式实现,步骤如下:

步骤一:开启迁徙标识,双写

将切流开关 datasource 设置为 1,对于对 order_tab 所有的写操作,在写旧表的同时,维持双写到新表,维持读流量到旧表中。

步骤二:脚本全量迁徙旧数据

与计划一统一,通过执行迁徙脚本,将历史数据全量迁徙至新表,依据分库分表策略路由到指标表中。

步骤三:开启开关切流至新表

数据全量迁徙结束后,新旧表当中具备了全量的数据,在进行数据对账确保无脱漏后,将读写流量开关设置 datasource=2,读写流量全副间接写入新表中,此时的指标状态如下:

至此,旧表数据的全副读写流量切入到新表中。

切换过程中须要比拟关注的点如下:

  • 全量迁徙历史数据后须要进行数据比照
  • 须要做必要的监控判断是否存在流量残余
  • 思考必要的回滚措施
  • 双写失败时须要做必要的被动发现及修复措施
退出移动版