题目
设计一个支持动态扩容的分库分表方案
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
分库分表策略, 扩容方案设计, 数据迁移, 分布式事务, 全局ID生成
快速回答
设计支持动态扩容的分库分表方案需考虑:
- 分片策略:采用一致性哈希或范围分片减少数据迁移量
- 扩容流程:双写迁移 + 流量切换 + 数据校验
- 全局ID:雪花算法或Leaf-Segment方案
- 事务处理:Saga模式或XA事务补偿
- 路由层:动态配置中心管理分片规则
一、核心设计原理
动态扩容目标:在不停服前提下,通过增加数据库节点提升系统容量,需解决:
- 数据重分布时的平滑迁移
- 分片规则动态更新
- 扩容期间的数据一致性
二、分库分表策略设计
1. 分片键选择:以订单表为例,选择user_id作为分片键,确保用户数据局部性
2. 分片算法:
// 一致性哈希分片(Java示例)
public class ShardingAlgorithm {
private TreeMap<Long, String> virtualNodes = new TreeMap<>();
private int virtualNodeCount = 1000; // 虚拟节点数
public void addNode(String node) {
for (int i = 0; i < virtualNodeCount; i++) {
long hash = hash("SHARD-" + node + "-NODE-" + i);
virtualNodes.put(hash, node);
}
}
public String getNode(String key) {
long hash = hash(key);
SortedMap<Long, String> tailMap = virtualNodes.tailMap(hash);
if (tailMap.isEmpty()) {
return virtualNodes.get(virtualNodes.firstKey());
}
return tailMap.get(tailMap.firstKey());
}
private long hash(String key) { /* 使用MurmurHash实现 */ }
}优势:扩容时仅需迁移约1/N数据(N为新旧节点总数)
三、动态扩容流程
步骤:
- 双写阶段:新请求同时写入新旧分片
- 数据迁移:后台任务迁移旧数据到新分片
- 数据校验:对比新旧分片数据差异
- 流量切换:配置中心更新路由规则
- 清理旧数据:延迟删除旧分片冗余数据
代码示例(数据迁移伪代码):
def migrate_data(old_shard, new_shard):
cursor = old_shard.execute("SELECT * FROM orders WHERE shard_key BETWEEN ? AND ?", [start, end])
while batch := cursor.fetch_batch(1000):
with new_shard.transaction():
new_shard.bulk_insert(batch)
# 记录迁移点位
save_checkpoint(batch[-1].id)四、关键技术实现
1. 全局ID生成:
// 雪花算法结构
0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000
└───────────────────────┘└─────────────────┘└─────┘ └───┘ └──────────────┘
时间戳(41bit) 数据中心ID(5bit) 机器ID(5bit) 序列号(12bit)2. 分布式事务:采用Saga模式
// 订单创建Saga示例
1. Begin Transaction
2. 扣减库存 --> 若失败则触发Cancel订单
3. 创建订单 --> 若失败则触发回滚库存
4. 提交事务五、最佳实践与常见错误
最佳实践:
- 分片数预留:初始设计2倍冗余分片(如1024个虚拟桶)
- 灰度发布:先迁移非核心分片
- 限流机制:迁移期间限制数据扫描QPS
常见错误:
- 错误1:直接停机迁移(违反动态扩容原则)
- 错误2:迁移后未校验数据(导致数据不一致)
- 错误3:分片键选择不当(如使用单调递增ID导致热点)
六、扩展知识
- 弹性分片:TiDB的Region自动分裂合并
- 多活架构:结合异地多活设计跨地域分片
- HTAP支持:通过Binlog同步到分析型数据库
性能指标参考:
| 阶段 | 延迟影响 | 数据一致性 |
|---|---|---|
| 双写阶段 | 写入延迟增加15-30% | 最终一致(秒级) |
| 迁移阶段 | 读放大(+20%负载) | 分段强一致 |
| 切换阶段 | 路由更新毫秒级抖动 | 强一致 |