功能点 10:Delta Join 与状态外部化

功能点 10:Delta Join 与状态外部化 ------ 源码阅读笔记

对应源码阅读计划功能点 10:DeltaJoinOperator、JoinStateStore、无状态计算架构。


笔记 10.1:DeltaJoinOperator ------ 无状态 Join 算子

文件:DeltaJoinOperator.java

路径fluss-flink/fluss-flink-common/src/main/java/org/apache/fluss/flink/source/delta/DeltaJoinOperator.java

核心设计

传统 Flink Join 的状态模型:

复制代码
Flink Task
├── LeftOperator  ← 左表数据
│   └── RocksDB State (Left Side)  ← 左表状态
├── RightOperator ← 右表数据
│   └── RocksDB State (Right Side)  ← 右表状态
└── JoinOperator
    └── ⚠️ 状态都在 Flink 本地!

Delta Join 的状态模型:

复制代码
Flink Task                   Fluss TabletServer
├── JoinOperator             ├── JoinStateStore
│   └── ❌ 无本地状态!        │   └── KvStore (RocksDB)
│                             │       └── JoinKey → RightRow
│   每次收到左表记录,           │
│   直接查询 Fluss 获取右表数据   │   右表写入时自动更新 Join 状态
└─────────────────────────────┘

核心实现

java 复制代码
public class DeltaJoinOperator extends TwoInputStreamOperator<RowData, RowData, RowData> {
    
    // ★ 注意:此 Operator 没有任何状态变量!
    // 所有 Join 状态都在 Fluss 服务端的 JoinStateStore 中
    
    private final FlussConnection connection;
    private final TablePath rightTable;
    private final int[] joinKeyIndexes;
    
    /**
     * 处理左表(驱动表)的数据
     */
    @Override
    public void processElement1(StreamRecord<RowData> leftRecord) {
        RowData leftRow = leftRecord.getValue();
        
        // 1. 提取 Join Key
        byte[] joinKey = extractJoinKey(leftRow, joinKeyIndexes);
        
        // 2. ★ 向 Fluss 查询右表数据
        // 这里没有从任何本地状态读取!
        byte[] rightValue = connection.pointLookup(rightTable, joinKey);
        
        // 3. 组合输出
        if (rightValue != null) {
            RowData rightRow = deserialize(rightValue);
            RowData output = combine(leftRow, rightRow);
            output.collect(new StreamRecord<>(output));
        }
        // 注:LEFT JOIN 时,rightValue == null 也输出左表数据
    }
    
    /**
     * 处理右表(被驱动表)的数据
     * 右表数据直接写入 Fluss JoinStateStore
     */
    @Override
    public void processElement2(StreamRecord<RowData> rightRecord) {
        RowData rightRow = rightRecord.getValue();
        
        // 1. 提取 Join Key
        byte[] joinKey = extractJoinKey(rightRow, joinKeyIndexes);
        
        // 2. ★ 写入 Fluss JoinStateStore(不是本地 RocksDB!)
        byte[] value = serialize(rightRow);
        connection.putToStateStore(rightTable, joinKey, value);
        
        // 3. 如果右表更新,通知左表侧刷新
        // (通过 changelog 机制或定期刷新)
    }
    
    /**
     * ★ Checkpoint 回调:无需保存任何本地状态
     */
    @Override
    public void snapshotState(StateSnapshotContext context) {
        // 空实现!所有状态都在 Fluss 端
        // Fluss 端的状态有自己的 WAL 和 Checkpoint 机制保证持久性
    }
}

笔记 10.2:JoinStateStore ------ 服务端状态存储

文件:JoinStateStore.java

路径fluss-server/src/main/java/org/apache/fluss/server/tablet/delta/JoinStateStore.java

服务端实现

java 复制代码
public class JoinStateStore {
    
    // ★ 使用 KvStore 存储 Join 状态
    // Key = join_key 的编码
    // Value = 右表对应行的完整数据
    private final KvTablet kvTablet;
    
    /**
     * 存储右表数据到 Join 状态
     */
    public void putJoinState(byte[] joinKey, byte[] rowValue) {
        // 1. 先写 WAL(KvTablet 自动处理)
        // 2. 再写 RocksDB
        kvTablet.put(joinKey, rowValue);
    }
    
    /**
     * 查询 Join 状态
     */
    public byte[] getJoinState(byte[] joinKey) {
        return kvTablet.get(joinKey);  // 亚毫秒级
    }
    
    /**
     * 删除 Join 状态(右表删除行时)
     */
    public void deleteJoinState(byte[] joinKey) {
        kvTablet.delete(joinKey);
    }
}

与传统方案的故障恢复对比

复制代码
传统 Flink 有状态 Join:
  故障 → Checkpoint 恢复
  ├── 下载 RocksDB 状态快照到新 TaskManager(数分钟)
  ├── 重建 LSM 树(数分钟)
  └── 从 Kafka 回放 offset gap(数十秒)
  总恢复时间:5-15 分钟

Fluss Delta Join:
  故障 → 直接启动新 TaskManager
  ├── 新算子连接 Fluss(秒级)
  ├── JoinStateStore 状态已在 Fluss 端持久化(无需下载)
  └── 新算子立即查询 Fluss 获取最新状态(毫秒级)
  总恢复时间:3-5 秒

笔记 10.3:成本优化分析

状态存储成本

复制代码
场景:100TB Join 状态,Flink 集群 100 核

传统方案:
├── Flink 计算:100 核
├── 状态存储(本地 SSD × 副本):100TB × 3 = 300TB SSD
├── Checkpoint 存储(S3):~30TB/次 × 10 保留 = 300TB S3
└── 总计:~$15,000 - $25,000/月

Fluss Delta Join:
├── Flink 计算:50 核(无状态后并行度可降低)
├── Fluss TabletServer:统一管理状态存储
├── 无需独立 Checkpoint 存储
└── 总计:~$5,000 - $8,000/月(含 Fluss 集群)

成本节省:~60-75%

计算弹性

复制代码
场景:凌晨低峰 → 白天高峰

传统方案:
├── 扩缩容需状态重分布 → 不敢频繁操作
└── 高峰配置要覆盖峰值 → 凌晨资源浪费

Fluss Delta Join:
├── 计算无状态 → 秒级弹性扩缩
├── 凌晨:10 核 → 白天:100 核
└── 按需付费,成本再降 40-50%

阅读小结

已理解 尚未深入
✅ DeltaJoinOperator 的无状态设计 ⬜ 多流 Join(N-way Join)的支持计划
✅ JoinStateStore 如何用 KvStore 存储 Join 状态 ⬜ 右表更新如何触发左表刷新
✅ 故障恢复时间从分钟级到秒级的原因 ⬜ 非等值 Join 的实现可能性
✅ 成本优化的量化分析 ⬜ Delta Join 的端到端延迟影响

下一步:功能点 11-12------FlussClient 客户端、Netty RPC 通信、监控指标。

相关推荐
头茬韭菜10 小时前
图解 Fluss(三):读写全链路 —— 一次写入、一次扫描、一次点查
fluss
头茬韭菜11 小时前
图解 Fluss(五):Flink 接入与数据生命周期 —— Connector、表生命周期与冷热分层
大数据·flink·fluss
StarRocks_labs12 小时前
StarRocks × Fluss × Paimon:流湖仓分析与 ETL 闭环
starrocks·flink·jni·native·paimon·湖仓·fluss
头茬韭菜12 小时前
图解 Fluss(四):分布式协调 —— 选举、副本状态机与
分布式·fluss
头茬韭菜1 天前
图解 Fluss(一):一张图看清整体架构,两张图理解核心服务
架构·fluss
StarRocks_labs1 天前
基于 Fluss、Paimon 与 StarRocks 构建淘天集团湖流一体数据链路
starrocks·olap·schema·paimon·fluss·湖流一体
StarRocks_labs3 天前
StarRocks x Fluss x Paimon 湖流一体方案:构建秒级响应、湖流一体的实时数据引擎
starrocks·kafka·lambda·查询·paimon·fluss·湖流一体
头茬韭菜3 天前
功能点 11-12:客户端、RPC 通信、监控与安全
网络协议·安全·rpc·fluss
头茬韭菜6 天前
功能点 9:Flink Connector
大数据·flink·fluss