构建 Flink CDC 端到端血缘:从 MySQL 到 Kafka 再到 Hive 的 Atlas 全链路追踪实战

问题原文:在 Flink CDC 场景下,如何将 MySQL 到 Kafka 再到 Hive 的端到端血缘上报至 Atlas?

本文将手把手指导你构建一个完整的、生产级的元数据管道,用于捕获 Flink CDC 作业中从 MySQL 源表,经由 Kafka 中间 Topic,最终到 Hive 目标表的端到端血缘,并将其上报至 Apache Atlas 2.4.0。我们将以 金融交易流水血缘追踪 这一高合规性要求场景为背景,深入剖析每个环节的技术挑战与解决方案,并提供可直接运行的代码、配置及验证方法。

一、场景引入:金融交易流水的血缘黑洞

在某银行的核心数据平台,客户交易流水首先存储在 MySQL 的 finance_transactions 表中。为了构建实时数仓,数据团队部署了以下 Flink CDC 作业链:

  1. CDC Source: 使用 Flink CDC 从 MySQL 捕获增量变更。
  2. Kafka Sink: 将变更事件写入 Kafka Topic finance_tx_cdc_stream
  3. Hive Sink: 另一个批处理作业定期消费该 Topic,并将数据合并到 Hive 分区表 dw.finance_tx_fact
-- Flink CDC 作业 (MySQL -> Kafka)
CREATE TABLE mysql_source (
    tx_id BIGINT,
    customer_id BIGINT,
    amount DECIMAL(10, 2),
    currency STRING,
    event_time TIMESTAMP(3),
    PRIMARY KEY (tx_id) NOT ENFORCED
) WITH (
    'connector' = 'mysql-cdc',
    'hostname' = 'mysql-host',
    'table-name' = 'finance_transactions'
);

CREATE TABLE kafka_sink (
    tx_id BIGINT,
    customer_id BIGINT,
    amount DECIMAL(10, 2),
    currency STRING,
    event_time TIMESTAMP(3)
) WITH (
    'connector' = 'kafka',
    'topic' = 'finance_tx_cdc_stream'
);

INSERT INTO kafka_sink SELECT * FROM mysql_source;
-- Hive 批处理作业 (Kafka -> Hive)
-- 通过 Spark 或 Hive Streaming 完成
INSERT INTO TABLE dw.finance_tx_fact PARTITION (dt='2026-04-24')
SELECT tx_id, customer_id, amount, currency FROM kafka_table;

业务痛点

  1. 审计合规风险:监管要求能追溯 dw.finance_tx_fact.amount 字段的源头是 mysql.finance_transactions.amount。现有方案无法提供此链路。
  2. 故障排查困难:当 Hive 表数据异常时,无法快速定位问题是出在 MySQL 源、CDC 作业、Kafka 链路还是 Hive 加载环节。
  3. 影响分析缺失:若 MySQL 表 Schema 变更(如增加 fee 字段),无法自动评估对下游 Kafka Topic 和 Hive 表的影响。

核心诉求:能否让 Atlas 自动构建并展示 mysql_table -> kafka_topic -> hive_table 的完整血缘图?

二、原理解析:端到端血缘的三大支柱

要实现端到端血缘,必须打通三个独立的数据系统。这需要三个互补的机制协同工作。

1. 支柱一:MySQL 表元数据注册(源头)

Flink CDC 本身不负责向外部系统注册 MySQL 表的元数据。我们需要一个独立的机制来完成这项工作。

  • 方案:开发一个 MySQL Metadata Scanner,定期扫描 MySQL 的 information_schema,并将表结构同步到 Atlas。
  • 实体类型:使用 Atlas 内置的 rdbms_tablerdbms_column 类型。

2. 支柱二:Flink CDC 作业血缘捕获(过程)

这是最核心也是最复杂的部分。需要捕获 Flink CDC 作业的逻辑,识别其输入(MySQL)和输出(Kafka)。

  • 方案:扩展 Flink Catalog(如前文所述),或在 Flink 作业中嵌入 血缘上报逻辑
  • 关键点:必须能从 Flink 的 Transformation DAG 中准确提取源和目标的连接信息。

3. 支柱三:Hive 表元数据注册(终点)

这部分最为简单,因为 Hive 已有官方的 HiveHook

  • 方案:确保 HiveHook 已正确配置并启用。
  • 实体类型:Atlas 内置的 hive_tablehive_column

4. 整体架构与数据流

1. Metadata Scanner

2. Custom Listener

3. HiveHook

MySQL

Atlas Server

Flink CDC Job

Hive Metastore

Atlas UI / API

生活化类比:整个流程就像一条“护照签发链”。MySQL 是你的出生地,它给你发了第一份身份证明(Metadata Scanner)。你去旅行(Flink CDC Job),出入境管理局(Custom Listener)在你的护照上盖了章,记录了你从哪里来、到哪里去。最后,你在目的地国家(Hive)定居,当地市政厅(HiveHook)也给你登记了户籍。中央档案馆(Atlas Server)汇集了所有这些信息,任何人都可以查询你的完整人生轨迹。技术本质差异在于,这里的“盖章”和“登记”是自动化、程序化的,而非人工操作。

三、分步实现:代码、配置与集成

1. 步骤一:注册 MySQL 表元数据

1.1 创建 MySQL 元模型(如果不存在)

Atlas 2.4.0 通常已内置 rdbms_* 类型。可通过 REST API 验证:

curl -u admin:admin http://atlas-server:21000/api/atlas/v2/types/typedefs?type=rdbms_table
1.2 开发 Metadata Scanner

这是一个独立的定时任务(可用 Airflow 调度)。

// MysqlMetadataScanner.java
public class MysqlMetadataScanner {
    private final AtlasClientV2 atlasClient;
    private final String clusterName = "prod_mysql_cluster";

    public void scanAndSync(String dbName, String tableName) throws Exception {
        // 1. 从 MySQL 获取表信息
        Connection conn = DriverManager.getConnection("jdbc:mysql://...", "...", "...");
        DatabaseMetaData metaData = conn.getMetaData();
        ResultSet columns = metaData.getColumns(null, dbName, tableName, null);
        
        // 2. 构建 Atlas Entity
        AtlasEntity tableEntity = new AtlasEntity("rdbms_table");
        String qualifiedName = String.format("%s.%s@%s", dbName, tableName, clusterName);
        tableEntity.setAttribute("qualifiedName", qualifiedName);
        tableEntity.setAttribute("name", tableName);
        tableEntity.setAttribute("db", dbName);
        
        List<AtlasEntity> columnEntities = new ArrayList<>();
        while (columns.next()) {
            AtlasEntity col = new AtlasEntity("rdbms_column");
            col.setAttribute("qualifiedName", qualifiedName + "." + columns.getString("COLUMN_NAME"));
            col.setAttribute("name", columns.getString("COLUMN_NAME"));
            col.setAttribute("dataType", columns.getString("TYPE_NAME"));
            columnEntities.add(col);
        }
        tableEntity.setAttribute("columns", columnEntities);
        
        // 3. 上报到 Atlas
        atlasClient.createEntity(tableEntity);
        LOG.info("Synced MySQL table {} to Atlas", qualifiedName);
    }
}

2. 步骤二:捕获 Flink CDC 作业血缘

我们采用在 Flink 作业中嵌入上报逻辑的方式。

2.1 Flink 作业增强
// FlinkCdcJob.java
public class FlinkCdcJob {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        StreamTableEnvironment tEnv = StreamTableEnvironment.create(env);

        // 执行 CDC SQL
        tEnv.executeSql("CREATE TABLE mysql_source ...");
        tEnv.executeSql("CREATE TABLE kafka_sink ...");
        Table result = tEnv.sqlQuery("INSERT INTO kafka_sink SELECT * FROM mysql_source");

        // 关键:在 execute() 之前上报血缘
        reportCdcLineageToAtlas();
        
        env.execute("FinanceTx-CDC-Job");
    }

    private static void reportCdcLineageToAtlas() {
        try {
            AtlasClientV2 client = new AtlasClientV2(new String[]{"http://atlas-server:21000"}, "admin", "admin");
            
            // 1. 构建 Process Entity
            AtlasEntity process = new AtlasEntity("flink_cdc_process");
            process.setAttribute("name", "FinanceTx-CDC-Job");
            process.setAttribute("qualifiedName", "FinanceTx-CDC-Job@flink-cluster");
            
            // 2. 设置 Inputs (MySQL Table)
            AtlasEntityHeader mysqlInput = new AtlasEntityHeader();
            mysqlInput.setTypeName("rdbms_table");
            mysqlInput.setUniqueAttributes(
                Collections.singletonMap("qualifiedName", "finance.finance_transactions@prod_mysql_cluster")
            );
            
            // 3. 设置 Outputs (Kafka Topic)
            AtlasEntityHeader kafkaOutput = new AtlasEntityHeader();
            kafkaOutput.setTypeName("kafka_topic");
            kafkaOutput.setUniqueAttributes(
                Collections.singletonMap("qualifiedName", "finance_tx_cdc_stream@prod_kafka_cluster")
            );
            
            process.setRelationshipAttribute("inputs", Collections.singletonList(mysqlInput));
            process.setRelationshipAttribute("outputs", Collections.singletonList(kafkaOutput));
            
            // 4. 上报
            client.createEntity(process);
            LOG.info("Reported Flink CDC lineage to Atlas");
        } catch (Exception e) {
            LOG.error("Failed to report lineage", e);
            // 注意:这里不应抛出异常导致作业失败,应降级处理
        }
    }
}

⚠️ 警告:血缘上报逻辑必须与 Flink 主作业逻辑解耦。上报失败绝不应导致 CDC 作业失败,否则会影响核心数据链路。应采用异步、带重试的上报机制。

3. 步骤三:确保 Hive 表自动注册

只需确认 Hive 的 hive-site.xml 中已正确配置 HiveHook

<!-- hive-site.xml -->
<property>
    <name>hive.exec.post.hooks</name>
    <value>org.apache.atlas.hive.hook.HiveHook</value>
</property>
<property>
    <name>atlas.hook.hive.synchronous</name>
    <value>false</value> <!-- 强烈建议设为 false,异步上报 -->
</property>

4. 关键配置:Atlas application.properties

确保 Atlas Server 能接收来自各方的实体。

# application.properties
# 启用嵌入式 Kafka(或配置外部 Kafka)
atlas.notification.embedded=true

# HBase 存储
atlas.graph.storage.backend=hbase
atlas.graph.storage.hostname=localhost
atlas.graph.storage.hbase.table=apache_atlas

# Solr 索引
atlas.graph.index.search.backend=solr
atlas.graph.index.search.solr.zookeeper-url=localhost:2181/solr

四、验证与端到端测试

1. 验证步骤

步骤 1: 手动触发各环节
  • 运行 MysqlMetadataScanner 同步 finance_transactions 表。
  • 提交并运行增强后的 Flink CDC 作业。
  • 执行 Hive INSERT 语句,触发 HiveHook
步骤 2: 验证各实体存在
# 验证 MySQL 表
curl -u admin:admin "http://atlas-server:21000/api/atlas/v2/entity/uniqueAttribute/type/rdbms_table?attr:qualifiedName=finance.finance_transactions@prod_mysql_cluster"

# 验证 Kafka Topic
curl -u admin:admin "http://atlas-server:21000/api/atlas/v2/entity/uniqueAttribute/type/kafka_topic?attr:qualifiedName=finance_tx_cdc_stream@prod_kafka_cluster"

# 验证 Hive 表
curl -u admin:admin "http://atlas-server:21000/api/atlas/v2/entity/uniqueAttribute/type/hive_table?attr:qualifiedName=dw.finance_tx_fact@primary"

验证点:三个命令均应返回 200 OK。

步骤 3: 验证端到端血缘
# 获取 Hive 表 GUID
HIVE_GUID=$(curl -s -u admin:admin "...dw.finance_tx_fact..." | jq -r '.entity.guid')

# 查询上游两层
curl -u admin:admin "http://atlas-server:21000/api/atlas/v2/lineage/upstream?guid=$HIVE_GUID&depth=2"

预期结果:返回的 JSON 应包含一个三层结构:

  1. 第一层:hive_table (dw.finance_tx_fact)
  2. 第二层:kafka_topic (finance_tx_cdc_stream)
  3. 第三层:rdbms_table (finance.finance_transactions)

2. 在 Atlas UI 中可视化

登录 Atlas Web UI,搜索 dw.finance_tx_fact,点击“Lineage”标签页,应能看到清晰的血缘图,完美展示从 MySQL 到 Hive 的全链路。

五、FAQ 与生产最佳实践

Q1: 如何处理 Flink CDC 作业的动态扩缩容?

A1: 血缘实体(flink_cdc_process)应与作业逻辑绑定,而非具体的 TaskManager 实例。只要作业名(qualifiedName)不变,扩缩容不会影响血缘的准确性。

Q2: 如果 Kafka Topic 被多个作业消费,血缘如何表示?

A2: Atlas 的血缘模型天然支持一对多。一个 kafka_topic 实体可以作为多个 flink_cdc_processspark_processoutput,同时也可以作为多个下游 processinput。图数据库会自动维护这种复杂关系。

Q3: 字段级血缘如何实现?

A3: 本文聚焦于表/Topic 级别。要实现字段级血缘,需要在 Flink 作业的血缘上报逻辑中,详细解析 SELECT 子句中的字段映射,并在 flink_cdc_process 实体中增加 columnLineages 属性。这需要深度集成 Flink 的 Planner,复杂度较高,但对于金融场景可能值得投入。

Q4: 如何保证血缘上报的 Exactly-Once 语义?

A4: 很难做到。血缘上报通常是 Best-Effort 的。生产实践中,通过 幂等写入(基于 qualifiedName)和 定期对账(扫描作业列表与 Atlas 中的血缘实体进行比对)来保证最终一致性。

Q5: 与 OpenMetadata 的 DataHub Lineage Connector 相比如何?

A5: OpenMetadata 生态更活跃。DataHub 提供了开箱即用的 Flink 和 Kafka Lineage Connector。如果你的公司尚未重度投资 Atlas,迁移到 OpenMetadata 可能是更省力的选择。但如果你已有成熟的 Atlas 平台,自研集成仍是可控的方案。

生产最佳实践总结

  1. 解耦上报:血缘上报逻辑绝不能阻塞主数据链路。
  2. 幂等设计:所有上报操作必须基于 qualifiedName 实现幂等。
  3. 统一命名qualifiedName 的生成规则必须在所有系统(MySQL Scanner, Flink, Hive)中保持一致。
  4. 监控告警:监控血缘实体的创建延迟和失败率。
  5. 定期对账:建立离线任务,定期校验 Atlas 中的血缘完整性。

总结

在 Flink CDC 场景下,将 MySQL 到 Kafka 再到 Hive 的端到端血缘上报至 Atlas,是一个涉及多系统集成的工程挑战。其成功依赖于三大支柱的协同:源头的元数据扫描、过程的作业血缘捕获、终点的 Hook 自动注册。通过本文提供的金融交易流水案例,你可以掌握从理论到代码的完整实施路径。虽然需要一定的开发和运维投入,但构建起这条血缘链路后,将极大提升数据治理能力,满足合规审计、故障排查和影响分析等核心业务需求,为数据驱动的决策提供坚实可信的基础。

作者署名:九师兄

注意:本文由 AI 辅助生成,技术细节请以官方文档为准。生产环境使用前务必充分测试。

Logo

CANN开发者社区旨在汇聚广大开发者,围绕CANN架构重构、算子开发、部署应用优化等核心方向,展开深度交流与思想碰撞,携手共同促进CANN开放生态突破!

更多推荐