功能点 3:Bucket、Partition 与数据分布

功能点 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 二分查找。

相关推荐
头茬韭菜2 天前
第 9 篇:「Fluss 运维实战」—— 部署、监控与故障排查
运维·fluss
头茬韭菜2 天前
功能点 1:项目骨架与启动流程 —— 源码阅读笔记
运维·笔记·debian·fluss
头茬韭菜3 天前
第 10 篇:「Fluss 实战案例集」—— 完整解决方案与最佳实践
c#·linq·fluss
头茬韭菜5 天前
第 5 篇:「Fluss 列式流处理」—— Arrow 格式与查询优化
fluss
头茬韭菜5 天前
第 4 篇:「Fluss + Flink 集成实战」—— Catalog、Source 与 Sink
大数据·python·flink·fluss
头茬韭菜9 天前
第 2 篇:「Fluss 架构深入」—— CoordinatorServer、TabletServer 与存储引擎
架构·fluss
头茬韭菜10 天前
Apache Fluss 项目深度分析与技术文档系列计划
flink·spark·realtime·fluss