数据迁移与扩缩容方案

数据迁移与扩缩容方案

数据迁移的难点

分库分表实施过程中,数据迁移是最容易出问题的环节。核心难点在于:

  1. 数据一致性:迁移过程中的增量数据不能丢失
  2. 停机窗口:业务对迁移时间敏感
  3. 回滚方案:迁移失败后快速切回
  4. 数据校验:迁移完成后验证数据完整性

停机迁移方案

适用场景

业务允许短时间停机(一般在凌晨低峰期),数据量不大(百 GB 级别内)。

实施步骤

第 1 步:通知业务方,确认停机窗口(如凌晨 2:00-5:00)
第 2 步:停止应用写入(或切换为只读模式)
第 3 步:全量数据导出 + 导入到新表
第 4 步:数据校验(数量 + 关键字段 checksum)
第 5 步:切换数据源指向新库
第 6 步:应用启动,验证功能
第 7 步:确认无误后清理旧库

数据导出导入

# 从旧库导出
mysqldump -h old_host -u root -p db_name \
  --where="user_id % 4 = 0" \
  --no-create-info \
  --complete-insert \
  user_order > user_order_0.sql

# 导入到新分片
mysql -h new_host_0 -u root -p db_0 < user_order_0.sql

优缺点

优点:实现简单,数据一致性容易保证
缺点:需要停机,数据量大时迁移时间长

在线平滑迁移(双写方案)

适用场景

业务不能长时间停机,需要在线迁移大表数据。

实施步骤

第 1 阶段:双写搭建

// 应用层修改:写入时同时写入旧库和新库
public void saveOrder(Order order) {
    oldOrderDao.save(order);    // 旧库
    newOrderDao.save(order);   // 新分片(根据分片规则路由)
}

查询仍然使用旧库,避免查询性能受影响。

第 2 阶段:历史数据迁移

-- 分批迁移旧数据
-- 使用 LIMIT OFFSET 分批迁移,每批 10000 行
INSERT INTO new_db_0.order SELECT * FROM old_db.order 
WHERE id BETWEEN 1 AND 10000;
// 每批迁移完成后校验
long oldCount = oldDao.countByIdRange(start, end);
long newCount = newDao.countByOldIdRange(start, end);
if (oldCount != newCount) {
    // 重新迁移本批
}

第 3 阶段:增量追平与切换

-- 再次迁移双写期间的增量数据
-- 使用时间戳或自增 ID 定位
INSERT INTO new_db_0.order SELECT * FROM old_db.order 
WHERE update_time > lastSyncTime;

当新旧库数据差距缩小到可以接受的停机窗口内:

  1. 停止双写(停写旧库输出缓冲区或短暂暂停写入)
  2. 最后一次增量同步
  3. 全量校验
  4. 切换查询到新库

第 4 阶段:旧库下线

确认新库运行稳定后,关闭旧库的双写逻辑,彻底移除旧库。

扩容方案

场景

从 4 个分片扩容到 8 个分片。

方案一:取模变化 + 全量迁移(简单但代价大)

// 旧规则:order_id % 4
// 新规则:order_id % 8
// 需要重新计算所有数据的分片归属并迁移
// 约 50% 的数据需要搬迁

方案二:虚拟节点 + 一致性哈希

通过一致性哈希算法,扩容时只迁移部分数据:

// 旧分片节点:A, B, C, D
// 新分片节点:A, B, C, D, E, F, G, H
// 一致性哈希保证只有这部分数据需要迁移:
//   某些属于 A 的数据需要迁移到 E
//   某些属于 B 的数据需要迁移到 F
//   ...
// 迁移量约 50%,但实现复杂

方案三:级联扩容(大数据量常用)

不直接扩容,而是增加一层路由:

// 级联路由:先取模旧规则,再取模新规则
class CascadeRouter {
    int getShard(long id) {
        int oldShard = id % 4;
        if (oldShard == 0) return id % 2;     // old0 拆为 new0,new1
        if (oldShard == 1) return id % 2 + 2;  // old1 拆为 new2,new3
        // ...
    }
}

这样每个原有分片只需要拆分为 2 个新分片,每次只迁移 50% 的数据。

方案四:提前预留(推荐)

设计时采用”二分法”预留分片空间:

// 初始分配 256 个分片(但在 only 4 台机器上运行)
// 将来扩展时,将 256 个分片分散到更多机器
// 数据完全不用迁移,只需将表文件移动到新机器
# 预留足够多的分片数
tables:
  order:
    # 一张表对应逻辑上的 256 个分片
    actual-data-nodes: 
      # 初期 4 台机器,每台 64 个分片
      - db0.t_order_$->{0..63}
      - db1.t_order_$->{64..127}
      - db2.t_order_$->{128..191}
      - db3.t_order_$->{192..255}
      # 扩容时加机器,把部分分片移到新机器即可
      # 不会改变分片算法

缩容方案

缩容(减少分片)比扩容更少见,通常发生在业务萎缩或资源整合时。过程就是扩容的逆过程:

  1. 数据从被移除的分片迁到保留的分片
  2. 重新配置分片规则
  3. 验证一致性
  4. 下线节点

关键工具

  • canal:阿里巴巴开源的 binlog 订阅组件,用于增量数据同步
  • DataX:阿里巴巴的异构数据同步工具,适合全量迁移
  • pt-online-schema-change:Percona 的在线 DDL 工具,可辅助迁移
  • ShardingSphere-Scaling:ShardingSphere 自带的弹性迁移组件

面试要点

  • 停机迁移简单可靠但影响业务,在线迁移复杂但无感知
  • 双写 + binlog 追赶是平滑迁移的经典模式
  • 扩容的最佳策略是”提前预留足够多的分片”
  • 能说清楚:取模分片为什么扩容复杂,一致性哈希为什么好
  • 数据校验是迁移中不可忽略的环节
© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容