导读

本文由知乎数据库负责人代晓磊老师老师撰写,全面介绍了知乎几十套 TiDB、数据总量达 PB 级别的数据库在线迁移经验,详细分享了三种场景和方案,为同城机房迁移提供了详尽的指导。

文章首先概述了在线机房迁移的基本条件,包括对专线和资源的要求,确保迁移过程中的稳定性和性能。接着,详细阐述了两种主要的迁移方案:使用 TiDB 的 Placement Rules 进行跨云跨 Kubernetes 集群的副本投放迁移,以及通过 TiCDC 链接的主备集群迁移。此外,还提供了其他场景的迁移建议,如业务双写和凌晨写入的特殊情况。

要想搞定在线机房迁移之 TiDB 数据库迁移,看完本文基本上所有的迁移方案你都可以搞定了(数据库迁移方案和流程大同小异)。本文中 3 种 TiDB 在线迁移的场景和方案,大家可以根据各自的业务场景各取所需了。

在线机房 prepare 阶段

一般在线机房迁移都是同城,且机房距离在 150 km,需要具备条件:

1. 专线要求

  • 数据中心距离在 150 km 以内,通常位于同一个城市或两个相邻城市;
  • 数据中心间的网络至少采用2条光纤专线连接,并且要求延迟 3 ms 左右,并且长期稳定运行;
  • 双专线且带宽大于 200 Gbps。

2. 资源要求

  • 物理机:比之前的配置要好,尤其当前的高密机型,考虑好硬盘 pv、cpu、内存资源规划;
  • K8s :版本选择,以及环境搭建完毕,到达可用程度,并且可以绑定物理机nodes 资源;
  • Operator:TiDB-operator 尽量选择高版本,做好调研和测试验证,到达可用状态。

在线 TiDB 集群迁移切换方案

在之前我写过的多云多活文章中有提及切换方案,并且基于线上的核心集群实施成功,相当于帮我们在线数据库迁移打好了坚实的基础。

因为知乎的 TiDB 是 all in K8s,我就先按照 K8s 给大家展示方案。

打开网易新闻 查看精彩图片

整体的迁移架构如上图所示,可以看到:

  • 从结构上看,上方 K8s 集群 online 代表当前老机房的 TiDB 集群部署,里面包含了 TiDB 集群的各个组件。下方是新建立一套 k8s 集群,里面也部署了 TiDB 集群;
  • 中间 2 个红框代表的同步链路,一个是基于 TiDB placement-rule 副本投放,从视图上还是同一套 TiDB 集群的迁移链路。另外一个是由 TiCDC 同步的上下游 2 套不同的 TiDB 集群(上下游的版本可以不一样)。

下面我们分别来聊下这两种迁移方案:

一、跨云跨 k8s 的 TiDB placement-rule 副本投放迁移(60% tidb 集群都由此方案迁移)

迁移架构说明

打开网易新闻 查看精彩图片

Placement Rules 是 PD 在 4.0 版本引入的一套副本规则系统,用于指导 PD 针对不同类型的数据生成对应的调度。通过组合不同的调度规则,用户可以精细地控制任何一段连续数据的副本数量、存放位置、主机类型、是否参与 Raft 投票、是否可以担任 Raft leader 等属性。Placement Rules 特性在 TiDB v5.0 及以上的版本中默认开启( 5.0 之前开启需要通过 pd-ctl 命令:config set enable-placement-rules true 开启)。默认开启 placement-rule 后的情况如下:

# ./pd-ctl -i
» config placement-rules show
[
{
"group_id": "pd",
"id": "default",
"start_key": "",
"end_key": "",
"role": "voter",
"is_witness": false,
"count": 5
}

注:"count": 5 副本是因为之前我 config set max-replicas 5 修改过集群副本量

如果我想给这个 TiDB 集群实现同城双中心的副本放置 3 个 voter 副本在老集群,2 个 follower 副本在新集群,并且在每个规则内通过 label_constraints 将副本限定在对应的数据中心内。配置如下:

[
{
"group_id": "pd",
"id": "default",
"start_key": "",
"end_key": "",
"role": "voter",
"count": 3,
"label_constraints": [
{"key": "zone", "op": "in", "values": ["zone1"]}
],
"location_labels": ["rack", "host"]
},
{
"group_id": "pd",
"id": "online2",
"start_key": "",
"end_key": "",
"role": "follower",
"count": 2,
"label_constraints": [
{"key": "zone", "op": "in", "values": ["zone2"]}
],
"location_labels": ["rack", "host"]
}
]

通过以上配置就搞定了新机房的副本投放,相当于我将之前 5 voter 副本都在 id=default 的老机房,拆分成了 3 个 voter 在老机房,2 个 follower 在新机房的同构集群。在 2 个 follower 副本数据同步完成后,修改 placement-rule 配置,将 id=online2 的 region role 由 follower 调整 voter 即可完成 region leader 的跨机房分布,通过观察监控等 leader 均衡后,调整老机房的 count=0,新机房的 count=5 来完成新机房的副本补充,一定时间后老机房 region 掉 0 就完全完成数据的迁移了。调整方式如下:

[
{
"group_id": "pd",
"id": "default",
"start_key": "",
"end_key": "",
"role": "voter",
"count": 0, ####这里的0就代表把老机房的副本删除了
"label_constraints": [
{"key": "zone", "op": "in", "values": ["zone1"]}
],
"location_labels": ["rack", "host"]
},
{
"group_id": "pd",
"id": "online2",
"start_key": "",
"end_key": "",
"role": "voter", ####这里将之前的follower调整到voter
"count": 5, ####这里的5就代表把新online2的副本扩容到5
"label_constraints": [
{"key": "zone", "op": "in", "values": ["zone2"]}
],
"location_labels": ["rack", "host"]
}
]

优点:TiKV 数据根据 placement-rule 规则自动投放,业务读写访问都是同一个集群,只是 tikv 存储节点投放到了不同 k8s 里的不同 TiDB 集群中,整体的迁移对于业务透明,刚才的 3voter : 2follower 配置,好处就在于 3 个 voter 都在老数据中心,TiDB 的写入也基本都是本中心多数派提交,写入性能基本没有什么变化,TiDB 读取也是老数据中心的 leader 读取。

缺点:副本投放方式不能够跨版本,必须保证和当前集群版本一致,另外一旦有 leader 在新机房产生,业务存在跨机房读写 region leader 的延迟增加问题(增加的延迟对于大部分业务都可控)。

多云同构集群迁移操作步骤

(1)在 online2 创建同版本 TiDB 集群,拷贝老集群的 tc.yaml 的配置,修改里面的配置,只有配置了 clusterDomain 的集群,新集群的 PD 才能在跨云跨 k8s 跟老集群的 PD 通信,另外还需要注意修改 PD 的权重配置,目的在迁移期间将PD leader圈在老机房来保证性能(别忘了 TiDB server 经常跟 pd 获取 TSO 和全局 ID 等请求):

pd-ctl member leader_priority

(2)创建同配置同数量的 TiKV 节点 + 配置 placement-rule,这时需要考虑副本的 leader 放置策略(角色设定:voter/follower/learner),参考刚才的 3:2 的 placement-rule 配置。

注:为啥同数量?单副本的 size ,新集群需要能容纳2个副本的 size,避免因为新机房的 TiKV 节点过少而打满资源。为啥同配置?tikv 集群的 cpu 负载和 mem 使用保证迁移后能扛住。

(3)调优region的创建速度:store limit 配置,这里可以使用下面的命令专门给新的 online2 的 TiKV store 配置较高的 region 创建速度。相关的操作如下:

kubectl exec tidb-test-0 -n tidb-test -- ./pd-ctl store |grep -B 2 'tidb-test.online2.com'|grep 'id":'|awk -F':' '{print $2}'|awk -F',' '{print "store limit "$1" 40 add-peer"}

如何观察?查看监控 region health(注意 miss-peer/extra-peer 的变化),另外还可以通过计算 leader region 数量 X 2 ,或者 store region数量 X store 量看估算。

注: Store Limit 与 PD 其他 xxxx-schedule-limit 相关的参数不同的是,Store Limit 限制的主要是 operator 的消费速度,而其他的 limit 主要是限制 operator 的产生速度。

(4)config set schedule 来加速调度,先 config show 查看当前调度配置默认值,执行下面的命令调整 region/leader 调度的并发(注意观察 PD leader 的负载):

config set leader-schedule-limit 16 ### 控制 Transfer Leader 调度的并发数
config set region-schedule-limit 2048 ### 控制增删 Peer 调度的并发数
config set replica-schedule-limit 64 ### 同时进行 replica 调度的任务个数

(5)均衡 region leader 到新机房集群(follower->voter)

上面讲方案时提到了 online2 机房的 tikv 如何通过 placement-rule 提升为 leader ,并且将老集群 region 缩 0 的方法。

需要考虑业务是否能接受跨机房读写 region leader 的延迟增加(从整个迁移周期来看,大部分都是接受的,影响可控),如果读写敏感业务,就需要定一个凌晨切全部的 leader 到 online2,以及切 PD leader 和业务域名到新机房。

(6)多云多活状态,缩容老机房所有 TiKV,delete store,对于“顽固 region ”进行 delete region

这种状态会持续一段时间,因为均衡 region leader,以及下线老机房的 region 都需要天级别的时间,对于下线老机房 store 时,会出现只剩下部分 region 无法迁移走的情况,这是需要用到强制删除的策略,下面的命令就是给 store id=10 的这个 tikv 强制增加 remove-peer 来删除老机房的 region:

kubectl exec tidb-test-pd-0 -n tidb-test -- ./pd-ctl region store 10| jq '.regions | .[]| "\(.id)"'|awk -F'"' '{print "kubectl exec tidb-test-pd-0 -n tidb-test -- ./pd-ctl operator add remove-peer ",$2," 10"}' > /home/daixiaolei/remove_peer.sh

(7)切 PD leader 到新机房

切 PD leader 时需要注意集群抖动和低峰切(我们曾经出现因百万region 切 PD leader 卡集群 1h,通过 pd-recover 修复的故障),因为之前设定了 leader_priority,需要进行调整,可以有两个方式切 PD leader:

  • 直接设定 online2 的 pd leader_priority 比老机房高,这时PD集群就会因权重调整自动切PD。
  • 设定online2的 pd leader_priority 跟老机房一致,手动并且选低峰执行 member leader transfer
  • 切主到 online2 的集群。

(8) 业务切读写到新机房

因为副本投放的集群两边机房访问的是同一个集群,所以只需要把 dns 从老机房迁到新机房即可,不用 kill 连接,待容器自动销毁后连接自动释放。

(9)缩容老机房 TiDB Server

缩容前查看 cluster-processlist 确认是否还有业务链接,如果有通过 pods IP 找到对应的业务程序,让业务及时切主。

查看监控是否还有业务流量,确认没有流量后,老机房 TiDB Server 缩 0。

(10) 老机房缩容所有 pd 节点

确认新 online2 机房有至少 3 个 pd 节点,就可以缩容老机房的所有 pd 节点。

(11) 老机房 delete pvc

默认缩容 TiKV 的 pods 的 PVC 不会删除,需要手动删除 PVC,这样 pv 也自然释放。

(12)老机房 delete tc

老机房 delete tc 的操作风险非常的巨大,操作错新 online2 机房的集群会有毁灭性的影响,操作时一定要 kubectl get pods -n tidb-test 看下是否是已经空的老集群,同时要找找 DBA double check 。

(13)回收物理机资源(delete nodes ,关机回收)

回收资源后,机房迁移完毕。

同构集群能够按照上述流程进行升级迁移,当你遇到无法副本投放的集群,就涉及到集群 tc.yaml 中的一个重要的配置 clusterDomain ,如果没有这个配置,就要创建异构集群来添加(所以对于 TiDB on K8s ,如果要跨 K8s 建立集群,必须要配置 clusterDomain )。

二、TiCDC 链接的主备集群( 30% TiDB 集群由此方案迁移)

打开网易新闻 查看精彩图片

基于 TiCDC 迁移需要先通过 BR 恢复老集群的全量数据,再通过 TiCDC 同步增量数据即可,在数据同步后需要和业务配合进行迁移,A/S 级别业务必要时需要在低峰时进行迁移,对数据一致性有严格要求的需要配合停写迁移。

优点

  • 上下游都是独立集群,下游新集群的版本可以高于当前集群,对于需要替换的老 TiDB 版本或者希望用到新版本特性的业务可以迁移升级一并做了。但是一定要注意 TiDB 升级版本后的 SQL 兼容性。
  • 对于之前多个核心数据库共用一套 TiDB 集群的拆分到多套集群,增加隔离和稳定性。
  • TiCDC 的迁移可以随时回滚,在业务停写切读写到下游集群时创建新集群到旧集群的反向同步任务,将新集群生产的数据同步回老集群,在业务确定迁移成功一周后再停掉同步任务,并删除老集群。

缺点

  • TiCDC 同步的延迟较高,对于读敏感业务,无法切读流量验证。
  • TiCDC 的同步能力有限制(单 worker 3w/s),对于线上写入 5w/s 的增量同步链路可能无法搭建
  • BR 备份/恢复大集群可能时间较长,需要调整 gc-life-time 来保证增量同步, gc-life-time 配置过长会保留较多 MVCC,可能会对 TiDB 读性能造成部分影响。

TiCDC 迁移操作步骤

(1)在 online2 创建新 TiDB 集群(根据业务要求,版本可以一样也可以升级到高版本);

(2)调整老集群的 gc_life_time ,比如设定为 3 天(72h,按需调整);

mysql> select * from mysql.tidb where VARIABLE_NAME like '%gc_life_time%';
+-------------------+----------------+----------------------------------------------------------------------------------------+
| VARIABLE_NAME | VARIABLE_VALUE | COMMENT |
+-------------------+----------------+----------------------------------------------------------------------------------------+
| tikv_gc_life_time | 10m | All versions within life time will not be collected by GC, at least 10m, in Go format. |
+-------------------+----------------+----------------------------------------------------------------------------------------+
1 row in set (0.02 sec)
mysql> update mysql.tidb set VARIABLE_VALUE='72h' where VARIABLE_NAME='tikv_gc_life_time';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from mysql.tidb where VARIABLE_NAME like '%gc_life_time%';
+-------------------+----------------+----------------------------------------------------------------------------------------+
| VARIABLE_NAME | VARIABLE_VALUE | COMMENT |
+-------------------+----------------+----------------------------------------------------------------------------------------+
| tikv_gc_life_time | 72h | All versions within life time will not be collected by GC, at least 10m, in Go format. |
+-------------------+----------------+----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

(3)使用 BR 工具备份老集群 databases 数据到 S3,BR 的版本要跟 TiDB 集群版本一致;

br backup db \
--pd "${PDIP}:2379" \
--db test \
--storage "s3://backup-data/db-test/2024-06-30/" \
--ratelimit 128 \
--log-file backuptable.log

(4)使用 BR 将 S3 的 TiDB 备份恢复到 online2 新集群;

br restore db \
--pd "${PDIP}:2379" \
--db "test" \
--ratelimit 128 \
--storage "s3://backup-data/db-test/2024-06-30/" \
--log-file restore_db.log

(5)创建老集群到新集群的 CDC 任务(老集群需要部署 CDC 组件),注意从备份日志中获取 TSO,这个 TSO 是 TiCDC 创建同步任务的依赖;

kubectl logs -f backup-tidb-test-backup-06301455-nv4pw -n tidb-test
./cdc cli changefeed create --pd=http://tidb-test-pd:2379 --sink-uri="tidb://test_wr:xxxx@tidb-test-peer.online2.com:4000/" --start-ts=434373684418314309 --config service_tree.toml --changefeed-id=tidb-test-migration

(6)同步验证:观察 grafana cdc 监控或者 cdc query changefeed-id 命令查看已经没有延迟,从数据量和 sample 抽样验证数据一致性;

(7)数据验证没问题,可以让读取不敏感的业务灰度读流量到新集群,验证业务 SQL 的执行情况;

(8)业务方读流量灰度 100% 到新集群就可以切写了,或者读写敏感型的选择凌晨低峰期切读写。业务切写的方式有两种选择:

a. 业务自己控制停写,然后切到下游;

b. 数据库回收老集群用户的写入权限来强制停写,回收权限对已建链接不生效,需要 kill 一遍所有的链接或者重启一遍 TiDB Server;

(9)停写后,需要观察下同步链路,确认没有数据同步,然后停止老机房到新机房的 TiCDC 同步链路,并且建立新机房 TiDB 集群反向同步老机房的链路,目的是一旦新集群有任何问题,可以随时切回老集群,数据也不会丢失;

(10)业务切到下游新集群完成 TiDB 切换;

(11)观察一周后,下线 TiCDC 反向同步链路以及删除老集群;

(12)回滚方案:如果在一周内出现读写问题,可以随时切换回老集群。

三、其它场景(业务双写、业务只凌晨写,10%)

空集群业务双写

对于日志型业务,可以在新机房建立集群,业务双写7~30天就完成切换。

BR/dumping 备份,业务切读切写

有些 TiDB 集群只有凌晨写入,白天可以 BR/dumping+lightning 备份恢复后切主。

总结

通过三个月的迁移,我们将几十套 TiDB 集群,总量 PB 级数据,通过以上各种方式,安全稳定的迁移到了新机房,在此期间,我们根据迁移方案,也开发了平台化的 DTS ,以及机房迁移模块跟进迁移进度。最后,感谢业务团队配合以及团队兄弟们的辛苦努力,共同达成此次在线机房迁移项目。