功能点 3:Bucket、Partition 与数据分布 ------ 源码阅读笔记
对应源码阅读计划功能点 3:BucketingFunction、RebalanceCoordinator、TabletBalancer、数据如何切分和路由。
笔记 3.1:BucketingFunction ------ 数据路由算法
文件:BucketingFunction.java
路径 :fluss-common/src/main/java/org/apache/fluss/bucketing/BucketingFunction.java
核心实现
java
public class BucketingFunction {
/**
* 根据 bucketing key 决定数据落入哪个 Bucket
* @param key 分桶键的字节表示
* @param numBuckets 总 Bucket 数
* @return bucket id (0 ~ numBuckets-1)
*/
public int getBucket(byte[] key, int numBuckets) {
// 使用 MurmurHash3 计算哈希值
int hash = MurmurHash3.hash32(key, 0, key.length, SEED);
// 取绝对值 + 取模
return Math.abs(hash) % numBuckets;
}
}
为什么选择 MurmurHash3?
| 哈希算法 | 分布均匀性 | 性能 | 碰撞率 |
|---|---|---|---|
| MurmurHash3 | 优秀 | 极快(~5 cycles/byte) | 极低 |
| Java hashCode() | 一般 | 快 | 中等 |
| MD5 | 优秀 | 慢 | 极低 |
MurmurHash3 是流式存储系统(Kafka、Pulsar)的首选,因为它在保证分布均匀的同时性能最优。
分桶键的确定逻辑
对于 PK 表:分桶键 = 主键(或指定的 bucketing columns)
对于 Log 表:分桶键 = 写入时指定的 key(或轮询)
java
// 实际使用示例
// PK 表: 分桶键 = PRIMARY KEY 的所有列
// bucket_id = MurmurHash3(concat(pk_col1, pk_col2)) % bucket.num
// Log 表: 分桶键 = 消息 key(如果没有 key,则轮询或随机)
// bucket_id = message_key != null ? hash(key) % N : round_robin_next()
笔记 3.2:TabletBalancer ------ Tablet 分配策略
文件:TabletBalancer.java
路径 :fluss-server/src/main/java/org/apache/fluss/server/coordinator/rebalance/TabletBalancer.java
分配算法
java
public class TabletBalancer {
/**
* 将 newTablets 均匀分配到 availableServers 上
* 策略:轮询 + 容量感知
*/
public List<TabletAssignment> allocate(
int numTablets,
List<TabletServerInfo> servers,
int replicationFactor) {
List<TabletAssignment> result = new ArrayList<>();
// 1. 按当前 Tablet 数排序(轻载优先)
servers.sort(Comparator.comparingInt(TabletServerInfo::getLeaderCount));
// 2. 轮询分配
int serverIndex = 0;
for (int tabletId = 0; tabletId < numTablets; tabletId++) {
// Leader 分配
TabletServerInfo leader = servers.get(serverIndex % servers.size());
// Follower 分配(避免与 Leader 在同一节点)
List<TabletServerInfo> followers = selectFollowers(
servers, leader, replicationFactor - 1
);
result.add(new TabletAssignment(tabletId, leader, followers));
// 更新 Leader 的负载计数
leader.incrementLeaderCount();
for (TabletServerInfo f : followers) {
f.incrementFollowerCount();
}
serverIndex++;
}
return result;
}
}
关键设计原则
分配约束:
1. Leader 副本均匀分布 ← 避免热点
2. 同一 Tablet 的副本不在同一机器 ← 容错
3. 优先分配负载较低的机器 ← 负载均衡
4. 扩容时最小迁移量 ← 减少迁移成本
笔记 3.3:RebalanceCoordinator ------ 扩缩容机制
文件:RebalanceCoordinator.java
路径 :fluss-server/src/main/java/org/apache/fluss/server/coordinator/rebalance/RebalanceCoordinator.java
触发条件
java
public class RebalanceCoordinator {
// Rebalance 触发条件检查(定期执行,默认 5 分钟)
public void checkAndTriggerRebalance() {
// 条件 1: 有新的 TabletServer 加入
if (hasNewTabletServers()) {
triggerRebalance("New TabletServers detected");
}
// 条件 2: TabletServer 下线
if (hasDownTabletServers()) {
triggerEmergencyRebalance(); // 立即执行
}
// 条件 3: 负载标准差超过阈值
double stddev = calculateLoadStdDev();
if (stddev > MAX_LOAD_STDDEV) {
triggerRebalance("Load imbalance detected (stddev=" + stddev + ")");
}
}
}
Rebalance 计划生成
java
public RebalancePlan generateRebalancePlan() {
// 1. 计算最优分布
int totalTablets = getTotalTabletCount();
int totalServers = getActiveServerCount();
int idealPerServer = totalTablets / totalServers;
// 2. 生成迁移计划
RebalancePlan plan = new RebalancePlan();
for (TabletServerInfo server : activeServers) {
int current = server.getLeaderCount();
int target = idealPerServer;
if (current > target) {
// 需要迁出 current - target 个 Tablet
List<TabletInfo> toMigrate = selectTabletsToMigrate(
server, current - target
);
for (TabletInfo tablet : toMigrate) {
TabletServerInfo dest = selectLeastLoadedServer(servers);
plan.addMigration(tablet, server, dest);
}
}
}
return plan;
}
Rebalance 执行过程
RebalancePlan 执行(分批、逐步):
Batch 1 (20% of migrations):
├── migrate TabletA: Server1 → Server3
│ ├── 1. Server3 作为 Follower 开始同步日志
│ ├── 2. 追上 Leader 后加入 ISR
│ ├── 3. 触发 Leader 切换:Server1 → Server3
│ └── 4. Server1 删除副本
└── migrate TabletB: Server2 → Server4
└── (同样流程)
Batch 2 (next 20%) ...
Batch 3 ...
...
Batch N (last batch)
渐进式迁移的好处:
- 避免一次性迁移过多导致的集群压力
- 每批次迁移后检查集群稳定性
- 允许在中间步骤中断和回滚
阅读小结
| 已理解 | 尚未深入 |
|---|---|
| ✅ MurmurHash3 分桶的计算和路由 | ⬜ 分区表的自动分区创建逻辑 |
| ✅ TabletBalancer 的轮询+容量感知分配 | ⬜ AutoPartitionManager 的触发条件 |
| ✅ Rebalance 的触发条件和渐进式执行 | ⬜ emergencyRebalance vs 普通 rebalance 的差异 |
| ✅ Leader/Follower 分开分配避免单点 | ⬜ 迁移过程中客户端的请求路由 |
下一步:功能点 4------深入 LogTablet 的 Segment 管理、.index/.log 文件布局、OffsetIndex 二分查找。