功能点 6:Arrow 列式格式

功能点 6:Arrow 列式格式 —— 源码阅读笔记

对应源码阅读计划功能点 6:ArrowLogWriter/Reader、ColumnProjector 列裁剪、Arrow 内存管理。


笔记 6.1:ArrowLogWriter —— 列式写入

文件:ArrowLogWriter.java

路径fluss-common/src/main/java/org/apache/fluss/record/ArrowLogWriter.java

核心实现

public class ArrowLogWriter implements LogWriter {
    private final VectorSchemaRoot root;  // Arrow 的列式内存结构
    private final BufferAllocator allocator;
    private final ArrowStreamWriter ipcWriter;
    
    /**
     * 将一批行数据以列式写入 Arrow IPC 格式
     */
    public void write(LogRecordBatch batch) {
        List<RowData> rows = batch.getRows();
        int rowCount = rows.size();
        
        // ★ 关键:按列填充数据(不是按行!)
        for (int colIdx = 0; colIdx < schema.getFieldCount(); colIdx++) {
            FieldVector vector = root.getVector(colIdx);
            vector.allocateNew();  // 预分配内存
            
            // 逐列写入(与行式相反)
            for (int rowIdx = 0; rowIdx < rowCount; rowIdx++) {
                setVectorValue(vector, rowIdx, rows.get(rowIdx), colIdx);
            }
            vector.setValueCount(rowCount);
        }
        root.setRowCount(rowCount);
        
        // ★ 写入 Arrow IPC 格式
        // Arrow IPC = 元数据 (Schema) + 数据体 (RecordBatch)
        ipcWriter.writeBatch();
    }
    
    /**
     * 将单个单元格的值写入对应的 Arrow Vector
     */
    private void setVectorValue(FieldVector vector, int index, RowData row, int col) {
        DataType type = schema.getField(col).getDataType();
        
        switch (type.getPrimaryType()) {
            case INT:
                ((IntVector) vector).set(index, row.getInt(col));
                break;
            case BIGINT:
                ((BigIntVector) vector).set(index, row.getLong(col));
                break;
            case STRING:
                byte[] bytes = row.getString(col).getBytes();
                ((VarCharVector) vector).setSafe(index, bytes);
                break;
            case DECIMAL:
                BigDecimal value = row.getDecimal(col);
                ((DecimalVector) vector).set(index, value);
                break;
            // ... 其他类型
        }
    }
}

写入到 .log 文件的 Arrow IPC 布局

.log 文件中的 Arrow 数据布局:
┌──────────────────────────────────────────┐
│  Arrow IPC Message: Schema               │
│  - field[0]: user_id (INT64, not null)   │
│  - field[1]: name (UTF8, nullable)       │
│  - field[2]: amount (DECIMAL(10,2))      │
│  ...                                     │
├──────────────────────────────────────────┤
│  Arrow IPC Message: RecordBatch 1        │
│  ┌────────────────────────────────────┐  │
│  │ user_id column: [1, 2, 3, ..., N]  │  │ ← 连续内存
│  │ name column:    ["Alice","Bob"...]  │  │ ← 连续内存
│  │ amount column:  [99.9, 49.9, ...]   │  │ ← 连续内存
│  └────────────────────────────────────┘  │
├──────────────────────────────────────────┤
│  Arrow IPC Message: RecordBatch 2        │
│  ...                                     │
└──────────────────────────────────────────┘

关键优势:同一列的所有值存储在连续内存中,CPU 缓存友好、SIMD 友好。


笔记 6.2:ArrowLogReader —— 列式读取

文件:ArrowLogReader.java

public class ArrowLogReader {
    private final int[] projectedColumns;  // 查询需要的列的索引
    private final BufferAllocator allocator;
    
    /**
     * 读取一个 RecordBatch 并进行列裁剪
     */
    public ArrowRecordBatch readBatch(FileChannel channel, long position) {
        // 1. 从指定位置读取 Arrow IPC 消息
        ArrowStreamReader reader = new ArrowStreamReader(channel, allocator);
        
        // 2. 读取完整的 RecordBatch (所有列)
        reader.loadNextBatch();
        VectorSchemaRoot fullRoot = reader.getVectorSchemaRoot();
        
        // 3. ★ 列裁剪:只保留查询需要的列
        if (projectedColumns.length < fullRoot.getSchema().getFieldCount()) {
            return project(fullRoot, projectedColumns);
        }
        
        // 4. 返回完整数据(无裁剪)
        return new ArrowRecordBatch(fullRoot);
    }
}

笔记 6.3:ColumnProjector —— 零拷贝列裁剪

文件:ColumnProjector.java

public class ColumnProjector {
    
    /**
     * 列裁剪的核心实现
     * 从完整的 VectorSchemaRoot 中提取需要的列
     * ★ 零拷贝:不复制数据,只修改引用
     */
    public static VectorSchemaRoot project(
            VectorSchemaRoot fullRoot, 
            int[] columnIndices) {
        
        // 1. 创建投影后的 Schema
        Schema fullSchema = fullRoot.getSchema();
        Schema projectedSchema = projectSchema(fullSchema, columnIndices);
        
        // 2. ★ 零拷贝:直接引用原 Vector,不复制数据
        List<FieldVector> projectedVectors = new ArrayList<>();
        
        for (int colIdx : columnIndices) {
            FieldVector originalVector = fullRoot.getVector(colIdx);
            
            // ★ TransferPair:创建对同一块内存的引用
            // 底层共享 DirectByteBuffer,零拷贝!
            TransferPair transfer = originalVector.getTransferPair(allocator);
            transfer.transfer();  // 不复制数据,只转移所有权/引用
            
            projectedVectors.add(transfer.getTo());
        }
        
        // 3. 创建投影后的 VectorSchemaRoot
        VectorSchemaRoot projected = new VectorSchemaRoot(
            projectedSchema, projectedVectors, fullRoot.getRowCount()
        );
        
        return projected;
    }
}

零拷贝原理

原始数据 (200 列,Arrow RecordBatch)
┌────────────────────────────────────────────────────┐
│ col_0  │ col_1  │ ... │ user_id │ ... │ amount │ ... │ col_199 │
│ [val0] │ [val1] │ ... │ [100]   │ ... │ [99.9] │ ... │ [val199]│
│                           ↑                ↑
└───────────────────────────┼────────────────┼────────┘
                            │                │
                    查询只投影 user_id 和 amount:
                            │                │
                    ┌───────┘        ┌───────┘
                    ▼                ▼
         ┌──────────────┐  ┌──────────────┐
         │ user_id      │  │ amount       │
         │ [100]        │  │ [99.9]       │
         └──────────────┘  └──────────────┘
         
         ↑ 这两个 Vector 指向原始内存缓冲区
         ↑ 没有复制数据,只是创建了新引用!

内存不复制,只修改指针引用。 这就是为什么 Fluss 能做到列裁剪几乎零 CPU 开销。


笔记 6.4:Arrow 内存管理

文件:ArrowMemoryAllocator.java

public class ArrowMemoryAllocator {
    private final BufferAllocator allocator;
    
    // 分配策略:
    // 1. Direct Memory(堆外内存)→ 避免 GC 压力
    // 2. Chunk 分配 → 减少内存碎片
    // 3. 引用计数 → 自动释放
    
    public ArrowBuf allocate(long size) {
        // 从 Netty 的 PooledByteBufAllocator 升级为
        // 自定义 Bump-Pointer ChunkedAllocationManager
        return allocator.buffer(size);
    }
}

Arrow 与 Flink 的类型映射

// org.apache.fluss.flink.row.RowDataSerializationSchema
public class RowDataSerializationSchema {
    
    public ArrowRecordBatch serialize(List<RowData> rows) {
        // Flink RowData → Arrow Vector 的类型映射:
        //
        // Flink Type          → Arrow Type
        // ─────────────────────────────────
        // IntType             → IntVector
        // BigIntType          → BigIntVector
        // VarCharType         → VarCharVector
        // DecimalType         → DecimalVector
        // TimestampType       → TimeStampMicroVector
        // ArrayType           → ListVector
        // MapType             → MapVector
        
        ArrowLogWriter writer = new ArrowLogWriter(arrowSchema);
        for (RowData row : rows) {
            writer.write(row);
        }
        return writer.flush();
    }
}

阅读小结

已理解尚未深入
✅ Arrow 列式写入(逐列填充 Vector)⬜ Arrow IPC 协议的消息边界和压缩
✅ 列裁剪的 TransferPair 零拷贝机制FilterContext 如何与列裁剪联动
✅ Arrow BufferAllocator 的 Direct Memory 策略⬜ LargeVarCharVector 的大字符串处理
✅ Flink RowData ↔ Arrow Vector 的类型映射V1 log batch format 的统计信息收集

下一步:功能点 7——副本管理器、ISR 判定、Leader 选举、Follower 同步机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值