未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合具备Flink基础的数据开发工程师理解Catalog接入、Kafka与MySQL CDC写入、流批查询及ODS下游消费,并按场景分段实践,不是可直接部署的端到端脚本。当前paimon_orders与ods_orders均按dt分区而仅声明order_id主键,未明确支持该设计的配置及跨分区更新语义;实时聚合使用的目标表order_time未声明事件时间属性或水印,窗口示例仍不完整。标为“持续读取新数据”的查询设置了scan.bounded.watermark,不能据此理解为无限持续消费。正文未落实各任务的流批执行模式,也未限定Flink、Paimon及连接器的适用版本组合;检查点片段未展示与前文TableEnvironment的衔接,一致性目标仍需结合实际任务配置验证。实时大屏仅提供聚合视图结构,不含结果消费或WebSocket推送实现。混合写入的dt列、Java插入调用链及合并操作注释已修正,不再作为现存错误;Kafka与数仓分层仅为可选拓展主题。此次仅作全文结构审阅,未作外部事实核验或运行验证。 已取消无法确定目标的可点击引用「Kafka」,保留原提及;Missing link target: Kafka 已取消无法确定目标的可点击引用「数据仓库分层架构」,保留原提及;Missing link target: 数据仓库分层架构

🔄 Paimon与Flink集成 - 流批一体实践

abstract 📋 本节概览

  • 🎯 学习目标:掌握Paimon与Flink的深度集成,实现流批一体数据处理
  • 📚 前置知识:Paimon环境搭建,Flink基础
  • ⏱️ 预计用时:30分钟
  • 🔧 核心内容:Catalog配置、流式写入、批量查询、CDC集成
  • 💡 关键收获:实现端到端的流批一体数据管道

🎯 为什么Paimon与Flink天生一对

💡 完美组合的原因

1. 同源技术栈:

Paimon最初就是Flink Table Store
由Flink社区开发
深度理解Flink的设计理念

2. 流批一体理念一致:

Flink: 流批统一的计算引擎
Paimon: 流批统一的存储系统
→ 完美的技术匹配

3. 无缝集成:

✅ 原生Table API支持
✅ 自动类型映射
✅ 高效的序列化
✅ 在正确配置检查点、连接器及提交机制等条件下支持一致性处理

生活化类比:就像手机和充电器

  • 官方原装充电器:Flink + Paimon,完美适配
  • 第三方充电器:其他计算引擎,基本可用但可能有兼容性问题

🗄️ Paimon Catalog配置

📝 配置方式

方式1:SQL DDL配置:

-- 创建Paimon Catalog
CREATE CATALOG paimon WITH (
    'type' = 'paimon',
    'warehouse' = 'hdfs://namenode:8020/paimon-warehouse'
);

-- 切换到Paimon Catalog
USE CATALOG paimon;

-- 创建数据库
CREATE DATABASE IF NOT EXISTS my_db;
USE my_db;

方式2:Table API配置:

import org.apache.flink.table.api.*;
import org.apache.flink.table.catalog.*;

public class PaimonCatalogExample {
    public static void main(String[] args) {
        // 创建Table Environment
        EnvironmentSettings settings = EnvironmentSettings
            .newInstance()
            .inStreamingMode()
            .build();

        TableEnvironment tEnv = TableEnvironment.create(settings);

        // 注册Paimon Catalog
        String catalogName = "paimon";
        String warehouse = "hdfs://namenode:8020/paimon-warehouse";

        tEnv.executeSql(String.format(
            "CREATE CATALOG %s WITH (" +
            "  'type' = 'paimon'," +
            "  'warehouse' = '%s'" +
            ")",
            catalogName, warehouse
        ));

        // 使用Catalog
        tEnv.useCatalog(catalogName);
    }
}

方式3:flink-conf.yaml配置:

# Flink配置文件
table.sql-catalog: paimon
table.sql-catalog.paimon.type: paimon
table.sql-catalog.paimon.warehouse: hdfs://namenode:8020/paimon-warehouse

🔧 高级配置选项

Metastore集成 (生产环境推荐):

-- 使用Hive Metastore管理元数据
CREATE CATALOG paimon_hive WITH (
    'type' = 'paimon',
    'metastore' = 'hive',
    'uri' = 'thrift://hive-metastore:9083',
    'warehouse' = 'hdfs://namenode:8020/paimon-warehouse'
);

多Catalog混用:

-- 创建多个Catalog
CREATE CATALOG paimon_dev WITH (...);
CREATE CATALOG paimon_prod WITH (...);

-- 跨Catalog查询
SELECT
    d.*,
    p.user_name
FROM paimon_dev.db.orders d
LEFT JOIN paimon_prod.db.users p
ON d.user_id = p.user_id;

✍️ 流式写入场景

📊 场景1:Kafka到Paimon实时同步

架构图:

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    A["Kafka Topic"] --> B["数据清洗转换<br/>(Flink消费)"]
    B --> C["Paimon表<br/>(Flink处理写入)"]
    C --> D["下游应用<br/>(实时可查)"]
    class A,B,C,D default

完整示例:

-- 1. 创建Kafka源表
CREATE TABLE kafka_source (
    order_id BIGINT,
    user_id BIGINT,
    product_id BIGINT,
    amount DECIMAL(10, 2),
    order_time TIMESTAMP(3),
    WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (
    'connector' = 'kafka',
    'topic' = 'orders',
    'properties.bootstrap.servers' = 'localhost:9092',
    'properties.group.id' = 'paimon-consumer',
    'scan.startup.mode' = 'latest-offset',
    'format' = 'json'
);

-- 2. 创建Paimon目标表
CREATE TABLE paimon_orders (
    order_id BIGINT,
    user_id BIGINT,
    product_id BIGINT,
    amount DECIMAL(10, 2),
    order_time TIMESTAMP(3),
    dt STRING,                    -- 分区字段
    PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH (
    'bucket' = '8',
    'changelog-producer' = 'input'
);

-- 3. 实时同步任务
INSERT INTO paimon_orders
SELECT
    order_id,
    user_id,
    product_id,
    amount,
    order_time,
    DATE_FORMAT(order_time, 'yyyy-MM-dd') as dt
FROM kafka_source;

🔄 场景2:维度表实时更新

业务场景:用户画像表需要实时更新

实现方案:

-- 1. 创建CDC源表(MySQL Binlog)
CREATE TABLE mysql_user_cdc (
    user_id BIGINT,
    username STRING,
    age INT,
    city STRING,
    level INT,
    update_time TIMESTAMP(3),
    PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
    'connector' = 'mysql-cdc',
    'hostname' = 'localhost',
    'port' = '3306',
    'username' = 'root',
    'password' = 'password',
    'database-name' = 'mydb',
    'table-name' = 'users'
);

-- 2. 创建Paimon维度表
CREATE TABLE dim_user (
    user_id BIGINT,
    username STRING,
    age INT,
    city STRING,
    level INT,
    update_time TIMESTAMP(3),
    PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
    'bucket' = '4',
    'changelog-producer' = 'full-compaction',
    'full-compaction.delta-commits' = '1'
);

-- 3. CDC实时同步
INSERT INTO dim_user
SELECT * FROM mysql_user_cdc;

⚡ 写入性能优化

1. 批量提交:

-- 配置写入选项
INSERT INTO paimon_table
/*+ OPTIONS(
    'sink.parallelism' = '8',          -- 写入并行度
    'sink.use-managed-memory-allocator' = 'true',
    'sink.managed.writer-buffer-memory' = '256mb'  -- 写缓冲
) */
SELECT * FROM source_table;

2. 分桶优化:

// 代码中配置
Table result = tEnv.sqlQuery("SELECT * FROM source_table");

// 设置Table执行并行度;具体取值需结合分桶与运行资源评估
 tEnv.getConfig().getConfiguration().setInteger("parallelism.default", 8);
result.executeInsert("paimon_table");

3. 检查点配置:

// 配置检查点
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

env.enableCheckpointing(60000);  // 60秒一次检查点
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(30000);
env.getCheckpointConfig().setCheckpointTimeout(300000);

📖 批量查询场景

📊 场景1:离线数据分析

全表扫描:

-- 批量分析订单数据
SELECT
    dt,
    COUNT(*) as order_count,
    SUM(amount) as total_amount,
    AVG(amount) as avg_amount
FROM paimon_orders
WHERE dt >= '2024-01-01' AND dt <= '2024-01-31'
GROUP BY dt
ORDER BY dt;

分区查询:

-- 只查询特定分区
SELECT * FROM paimon_orders
WHERE dt = '2024-01-01'
AND user_id = 1001;

时间旅行:

-- 查询历史快照
SELECT * FROM paimon_orders
/*+ OPTIONS('scan.snapshot-id' = '5') */
WHERE dt = '2024-01-01';

-- 查询指定时间点的数据
SELECT * FROM paimon_orders
/*+ OPTIONS('scan.timestamp-millis' = '1704096000000') */
WHERE dt = '2024-01-01';

🔍 场景2:增量数据处理

增量ETL:

-- 读取增量数据(从快照3到快照10)
CREATE TEMPORARY VIEW incremental_orders AS
SELECT * FROM paimon_orders
/*+ OPTIONS('incremental-between' = '3,10') */;

-- 处理增量数据
INSERT INTO target_table
SELECT
    order_id,
    user_id,
    SUM(amount) as total_amount
FROM incremental_orders
GROUP BY order_id, user_id;

流式消费最新数据:

-- 持续读取新数据
SELECT * FROM paimon_orders
/*+ OPTIONS(
    'scan.mode' = 'latest',           -- 只读最新数据
    'scan.bounded.watermark' = '10000'  -- 边界水印
) */;

🔄 流批混合场景

💡 场景:实时大屏 + 历史分析

架构设计:

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    A["实时流写入"] --> B["Paimon表"]
    B --> C["流式查询(实时大屏)"]
    B --> D["批量查询(历史分析)"]
    class A,B,C,D default

实现代码:

-- 1. 流式写入任务(持续运行)
INSERT INTO paimon_orders
SELECT
    order_id,
    user_id,
    product_id,
    amount,
    order_time,
    DATE_FORMAT(order_time, 'yyyy-MM-dd') AS dt
FROM kafka_source;

-- 2. 定义实时聚合视图(尚缺目标表事件时间声明;不含查询结果消费及WebSocket推送实现)
CREATE TEMPORARY VIEW realtime_stats AS
SELECT
    window_start,
    window_end,
    COUNT(*) as order_count,
    SUM(amount) as total_amount
FROM TABLE(
    TUMBLE(TABLE paimon_orders /*+ OPTIONS('scan.mode'='latest') */,
           DESCRIPTOR(order_time),
           INTERVAL '1' MINUTE)
)
GROUP BY window_start, window_end;

-- 3. 批量分析(定时任务)
SELECT
    dt,
    user_id,
    COUNT(*) as order_count,
    SUM(amount) as total_amount
FROM paimon_orders
WHERE dt >= CURRENT_DATE - INTERVAL '7' DAY
GROUP BY dt, user_id;

🎯 实战案例:构建实时数仓ODS层

📋 需求场景

业务需求:

1. 实时同步MySQL订单表到数据湖
2. 支持订单的增删改查
3. 下游实时数仓和离线分析同时使用
4. 保证数据一致性

🏗️ 架构设计

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
graph LR
    A[MySQL订单表] -->|Flink CDC| B[Paimon ODS层]
    B -->|流式读取| C[实时数仓DWD层]
    B -->|批量查询| D[离线分析]

    style A fill:#e1f5fe
    style B fill:#fff3e0
    style C fill:#c8e6c9
    style D fill:#f3e5f5

💻 完整实现

步骤1:创建ODS表:

-- ODS层:ods_orders
CREATE TABLE ods_orders (
    order_id BIGINT,
    user_id BIGINT,
    product_id BIGINT,
    quantity INT,
    amount DECIMAL(10, 2),
    status STRING,
    create_time TIMESTAMP(3),
    update_time TIMESTAMP(3),
    dt STRING,
    PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH (
    'bucket' = '8',
    'changelog-producer' = 'full-compaction',
    'full-compaction.delta-commits' = '3',
    'snapshot.time-retained' = '2h',
    'snapshot.num-retained.min' = '20'
);

步骤2:CDC同步任务:

-- 创建MySQL CDC源
CREATE TABLE mysql_orders_source (
    order_id BIGINT,
    user_id BIGINT,
    product_id BIGINT,
    quantity INT,
    amount DECIMAL(10, 2),
    status STRING,
    create_time TIMESTAMP(3),
    update_time TIMESTAMP(3),
    PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
    'connector' = 'mysql-cdc',
    'hostname' = 'mysql-host',
    'port' = '3306',
    'username' = 'cdc_user',
    'password' = 'password',
    'database-name' = 'ecommerce',
    'table-name' = 'orders',
    'server-time-zone' = 'Asia/Shanghai'
);

-- 实时同步
INSERT INTO ods_orders
SELECT
    order_id,
    user_id,
    product_id,
    quantity,
    amount,
    status,
    create_time,
    update_time,
    DATE_FORMAT(create_time, 'yyyy-MM-dd') as dt
FROM mysql_orders_source;

步骤3:下游消费:

-- 实时消费(DWD层)
CREATE TABLE dwd_orders AS
SELECT
    order_id,
    user_id,
    product_id,
    quantity,
    amount,
    status,
    create_time
FROM ods_orders
/*+ OPTIONS('scan.mode' = 'latest') */
WHERE status = 'PAID';

-- 离线分析
SELECT
    dt,
    COUNT(*) as order_count,
    SUM(amount) as total_amount
FROM ods_orders
WHERE dt >= '2024-01-01'
GROUP BY dt;

📊 性能监控

-- 查看表统计信息
SELECT * FROM ods_orders$snapshots;

-- 查看文件分布
SELECT
    partition,
    bucket,
    COUNT(*) as file_count,
    SUM(file_size_in_bytes) / 1024 / 1024 as total_size_mb
FROM ods_orders$files
GROUP BY partition, bucket;

-- 触发default数据库的合并操作(非只读状态查询)
CALL sys.compact_database('default');

🚀 最佳实践总结

✅ 配置最佳实践

1. Catalog配置:

✅ 生产环境使用Hive Metastore
✅ 合理设置warehouse路径
✅ 配置多环境隔离(dev/test/prod)

2. 表设计:

✅ 主键表用于维度数据和需要更新的事实表
✅ 仅追加表用于日志和不需要更新的事实表
✅ 合理设计分区,避免过度分区
✅ bucket数量匹配写入并行度

3. 性能优化:

✅ 配置合理的检查点间隔
✅ 使用managed memory allocator
✅ 定期执行compact
✅ 监控快照数量,及时清理

⚠️ 常见陷阱

1. 并行度设置:

❌ 错误:并行度远大于bucket数
✅ 正确:并行度 = bucket数 或 bucket数的整数倍

2. 合并配置:

❌ 错误:从不合并,导致文件数暴增
✅ 正确:配置自动合并 + 定期全量合并

3. 快照管理:

❌ 错误:快照无限保留,占用大量空间
✅ 正确:设置合理的保留策略

📋 本节要点回顾

✅ 核心知识点检查

  • [ ] 掌握Paimon Catalog的配置方法
  • [ ] 理解流式写入的实现方式
  • [ ] 了解批量查询的优化技巧
  • [ ] 能够实现流批混合场景
  • [ ] 掌握CDC数据同步最佳实践

🎯 实践能力检查

  • [ ] 能够配置Kafka到Paimon的实时同步
  • [ ] 会实现MySQL CDC到Paimon的数据同步
  • [ ] 能够进行性能调优和问题诊断
  • [ ] 掌握ODS层的建设方法

🔗 学习路径连接

相关主题:

下一步:

success 掌握Flink集成的价值

Flink + Paimon的组合是构建实时数据湖的黄金搭档。掌握这个组合,你就能够实现真正的流批一体数据处理架构。


创建时间:2024-12-04 最后更新:2024-12-04 学习时长:30分钟 难度等级:⭐⭐ (中级)

#Paimon #Flink #流批一体 #CDC #实时数仓 #数据集成