理解 Paimon changelog producer

文章介绍了Paimon中的ChangelogProducer模式,包括四种模式(None,input,lookup,fullcompaction)及其优缺点,强调了None模式虽然不完整但可通过ChangelogNormalize补全Changelog,以及lookup和fullcompaction模式在处理完整Changelog的需求和全量数据合并上的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍

目的

Chaneglog producer 的主要目的是为了在 Paimon 表上产生流读的 changelog, 所以如果只是批读的表是可以不用设置 Chaneglog producer 的.一般对于数据库如 MySQL 来说, 当执行的语句涉及数据的修改例如插入、更新、删除时,MySQL 会将这些数据变动记录在 binlog 中。相当于额外记录一份操作日志, 类似于 Paimon 中的 input changelog producer 的模式

存储形式

Chaneglog 一般是以单独的 changelog 文件的形式存储的, 也是在 snapshot commit 期间提交的. 在每次 Snapshot 的元数据中就会记录 changelogManifestList. 因此在 Snapshot 过期时, 也会一起过期.

Changelog producer 有四种模式, 分别是 None, input, lookup, full comapction. 一般来说, 是要以尽可能低的代价生成 Changelog. 这四种的生成代价是由低到高的.

四种模式

None

不查找旧值, 不额外写Chaneglog

默认就是 none, 这种模式下在 Paimon 侧不会额外存储数据. Source 读取的时候, 就是将 snapshot 的 delta list 文件读取出来, 就是本次 Snapshot 的增量 Changelog 了.

那么在这种模式下, 对于一个主键写入两条 INSERT 数据, 批式查询读出来是一个合并后的值, 流式查询应该读出来是两个 INSERT 数据, 实际上这个 changelog 是不对的, 应该读取第二条的时间应该是 -U +U 才对.

验证

CREATE TABLE T (
 a INT
,b INT
,c STRING
,PRIMARY KEY (a) NOT ENFORCED
)
WITH (
'merge-engine' = 'deduplicate'
,'changelog-producer' = 'none'
,'continuous.discovery-interval' = '1s' -- 调低discovery-interval
);

BlockingIterator<Row, Row> iterator = streamSqlBlockIter("SELECT * FROM T");
 
sql("INSERT INTO T VALUES(1, 1, '1')");
// 两次插入之间间隔2s, 这样source可以读取到两次snapshot的数据
Thread.sleep(2000);
sql("INSERT INTO T VALUES(1, 1, '2')");
 
assertThat(iterator.collect(3))
		.containsExactlyInAnyOrder(
				Row.ofKind(RowKind.INSERT, 1, 1, "1"),
				Row.ofKind(RowKind.INSERT, 2, 2, "2"));

测试流转

// 第一次commit
Successfully commit snapshot #1 (path /warehouse/default.db/T/snapshot/snapshot-1) by user 6434ee5c-ad2e-4564-a32c-568104392533 with identifier 9223372036854775807 and kind APPEND.
// 扫描到第一个snapshot
start snapshotId: 1
// 第二次commit
Successfully commit snapshot #2 (path /warehouse/default.db/T/snapshot/snapshot-2) by user ce0b10c0-e63f-4db0-ab90-1c542e832791 with identifier 9223372036854775807 and kind APPEND.
// 扫描到delta文件
scan with delta 2
// 输出数据
[+I[1, 1, 1]]
[+I[1, 1, 1], -U[1, 1, 1]]
[+I[1, 1, 1], -U[1, 1, 1], +U[1, 1, 2]]

ChangelogNormalize

可以看到流读的输出产生了正确的 changelog, 但是实际上 none 模式读取的时候是没有这个 -U. 具体可以通过 debug ValueContentRowDataRecordIterator 来查看真实读取的数据. 那这个 changelog 消息从哪里来呢 ? 实际上这个流读任务会产生 ChangelogNormalize 算子.

if (
  isUpsertSource(resolvedSchema, table.tableSource) ||
  isSourceChangeEventsDuplicate(resolvedSchema, table.tableSource, config)
) {
  // generate changelog normalize node
  // primary key has been validated in CatalogSourceTable
  val primaryKey = resolvedSchema.getPrimaryKey.get()
  val keyFields = primaryKey.getColumns
  val inputFieldNames = newScan.getRowType.getFieldNames
  val primaryKeyIndices = ScanUtil.getPrimaryKeyIndices(inputFieldNames, keyFields)
  val requiredDistribution = FlinkRelDistribution.hash(primaryKeyIndices, requireStrict = true)
  // 给source添加pk shuffle
  val requiredTraitSet = rel.getCluster.getPlanner
	.emptyTraitSet()
	.replace(requiredDistribution)
	.replace(FlinkConventions.STREAM_PHYSICAL)
  val newInput: RelNode = RelOptRule.convert(newScan, requiredTraitSet)
 
  // 本质上就是按照 PK进行last row计算, 用于生成PK的changelog
  new StreamPhysicalChangelogNormalize(
	scan.getCluster,
	traitSet,
	newInput,
	primaryKeyIndices,
	table.contextResolvedTable
  )
}

// 表示source是upsert的source
public static boolean isUpsertSource(
		ResolvedSchema resolvedSchema, DynamicTableSource tableSource) {
	if (!(tableSource instanceof ScanTableSource)) {
		return false;
	}
	ChangelogMode mode = ((ScanTableSource) tableSource).getChangelogMode();
	boolean isUpsertMode =
			mode.contains(RowKind.UPDATE_AFTER) && !mode.contains(RowKind.UPDATE_BEFORE);
	boolean hasPrimaryKey = resolvedSchema.getPrimaryKey().isPresent();
	// 只发送update_after, 不发送update_before, 并且设置了pk
	return isUpsertMode && hasPrimaryKey;
}

可以看到在这种模式下, 默认下游流读的时候是会生成 ChangelogNormalize 算子的, 类似于一个 Last Row 的算子, 实际上就是每条 input 流入的时候, 因为插件告诉 Planner, 我这个 source 只能产生 Upsert 消息(Insert, Update_after, Delete) , 所以下游通过 Normalize 节点自己来生成 Changelog.

所以 none 模式其实本身发送的 changlog 确实是不全的, 但是通过下游 changelog normalize 补足了这个 Changelog. 所以类似于 MySQL 中 binlog 生成的行为, 他其实也是存在查找前镜像的过程的, 只不过将查找的过程放到了下游的流任务中.

当下游不依赖完整的 Chaneglog, 比如下游也是个同步, 那么下游任务是可以通过参数 scan.remove-normalize 来移除 Normalize 的, 通过伪造 ChangelogMode 为 all 来绕过.

但是这里其实还有一个问题, 下游的 ChaneglogNormalize 节点是有 ttl 的, 假如我某个 key 更新是在 ttl 之后到来, 那么可能导致第二条 Insert/update_after 到来的时候又被当做一条 insert 消息下发, 其实会有数据不准确的问题存在的.

DeltaFollowUpScanner

流式读取的时候会分为两个部分, 历史 + 增量. 有一些模式是不需要读历史数据的, 但是增量部分一般都是要读的. 历史部分是读取的某个时刻的快照. 而增量的数据是读取的 CommitKind 为 Append 的 snapshot 所对应的 delta list. 所以其实这种流读模式下, delta scanner 只会读取 L0 的文件.

input

不查找旧值, 额外写Chaneglog

写数据过程中双写一份文件, 作为 Changelog.

理论上来说这种模式应该是很轻量的一种了, 因为首先额外的一份存储是都省不了的, 在 None 模式中,虽然在 Paimon 侧没有占用额外的存储, 但是在下游的流任务的状态中, 其实是有一份全量表的额外存储的开销的. 所以如果 input 模式不考虑存储开销, 计算开销已经是最低了, 因为这种模式不查找旧值.

也因此, 这种模式解决不了的一个问题是, 如果我的输入源就是没有完整 Changelog 的, 比如我从一份有重复数据的离线表导入 Paimon, 那么即使双写一份数据作为 Changelog, 这份 Changelog 也是不对的, 里面可能存在同一个主键的重复数据.

这种模式对于 CDC 的数据源是适用的. 那 None 模式对于 cdc 的数据源是否适用呢 ? 其实是不适用的, 上面我们提到 None 模式的流读其实就是读取 L0的文件, 那么我们只要看 L0的文件是否包含 Key 的变更记录. 因为 write buffer 会有合并的逻辑, 所以, 对于 CDC 的数据, L0中可能会是已经在内存合并后的数据. 比如同一个 key 的-U 和+U 消息, 同时写入, 那么在 writer buffer 写入的时候就已经只保留+U 消息了, 所以 None 模式中 L0文件中的数据, 可能已经是合并后的数据, 对于 CDC 的数据也不适用.

那么是不是可以在内存中不进行合并, L0写入之后在后续 compact 的时候才进行合并, 这样 None 模式就可以替换 input 的功能, 这样不引入额外双写的代价, 也不用额外查找, 就可以保留上游 cdc 数据的完整 Change log.

lookup

查找旧值, 额外存储Chaneglog

image

如果不是 CDC 的数据源, 或者此 Paimon 表本身在写入的过程中还有计算逻辑(如 partial-update/aggregation), 那么以上两种模式都不能生成正确的 Changelog.

lookup 的做法, 如其名字, 就是在 compaction 的过程中, 会去向高层查找本次新增 key 的旧值, 如果没有查找到, 那么本次的就是新增 key, 如果有查找到, 那么就生成完整的 UB 和 UA 消息.

LookupCompaction

如何保证本次写入的数据一定能够产生的 Chaneglog. 首先按照 Universal Compaction策略挑选文件参与本次 compaction. 如果没有挑选到, 那么会通过 LookupCompaction 策略来挑选, 这里其实隐含了, 如果 Universal Compaction 产生了 Compaction Unit, 一定包含所有的 L0文件.通过 LookupCompaction 策略会将 L0 文件进行 Compaction.

LookupMergeFunction

在 Compaction rewrite 的过程中, 会将相同 key 的数据喂给 LookupMergeFunction

public KeyValue getResult() {
	// 1. Find the latest high level record
	Iterator<KeyValue> descending = candidates.descendingIterator();
	while (descending.hasNext()) {
		KeyValue kv = descending.next();
		if (kv.level() > 0) {
			if (highLevel != null) {
				descending.remove();
			} else {
				highLevel = kv;
			}
		} else {
			containLevel0 = true;
		}
	}
 
	// 2. Do the merge for inputs
	mergeFunction.reset();
	candidates.forEach(mergeFunction::add);
	return mergeFunction.getResult();
}

  • candidates 存储的相同 key 的多个 SortedRun 的数据

  • 插入顺序是 sequence number 的增序.对于非 L0 的 kv, sequence 越大, level 越小. 因此 candidates 中的 level 是递减的, 最后的一部分是 L0的. 可以参见一部分 LookupChangelogMergeFunctionWrapperTest

  • 按照 candidates 倒序查找就是, 找到最近的 highlevel 的 value

LookupChangelogMergeFunctionWrapper

public ChangelogResult getResult() {
	reusedResult.reset();
 
	KeyValue result = mergeFunction.getResult();
	if (result == null) {
		return reusedResult;
	}
 
	KeyValue highLevel = mergeFunction.highLevel;
	boolean containLevel0 = mergeFunction.containLevel0;
 
	// 1. No level 0, just return
	// 1. No level 0, just return
	// 没有level 0的数据, 意味着没有新数据产生
	// 那么没有changelog文件产生, 只是高层文件的合并
	if (!containLevel0) {
		return reusedResult.setResult(result);
	}
 
	// 2. With level 0, with the latest high level, return changelog
	// 出现了highlevel的value, 很幸运, 这样直接就可以得出change log了.
	if (highLevel != null) {
		// For first row, we should just return old value. And produce no changelog.
		setChangelog(highLevel, result);
		return reusedResult.setResult(result);
	}
 
	// 3. Lookup to find the latest high level record
	// 向更高level中查找这个key先前的数据, 为了产生变更流代价还是挺高的
	// org.apache.paimon.mergetree.LookupLevels#lookup
	highLevel = lookup.apply(result.key());
 
	if (highLevel != null) {
		// 找到了更高level的数据, 那么别浪费这个结果, 可以再次进行合并, 得到一个更新的值, 并生成UB和UA消息
		mergeFunction2.reset();
		mergeFunction2.add(highLevel);
		mergeFunction2.add(result);
		result = mergeFunction2.getResult();
		setChangelog(highLevel, result);
	} else {
		// 没有找到更高level的数据, 那么Changelog就是一条insert
		setChangelog(null, result);
	}
	return reusedResult.setResult(result);
}

根据 LookupMergeFunction#getResult 得到的 containLevel0 和 highLevel 的信息, 以及高层 Lookup 完成 Change log 的生成. 在 Lookup 的过程中需要进行文件的二分查找, 以及 Lookup file 的索引文件构建, 整体代价还是比较高的.

full compaction

查找旧值, 额外存储 Chaneglog

这种模式下一般通过设置 full-compaction.delta-commits 定期进行 full compact, 因为 full compact 其实代价是比较高的. 所以这种模式整体的开销也是比较大的. 但是在 full compact 的过程中, 其实数据都会被写到最高层, 所以所有 value 的变化都是可以推演出来的.

FullChangelogMergeFunctionWrapper

public ChangelogResult getResult() {
        reusedResult.reset();
        if (isInitialized) {
            KeyValue merged = mergeFunction.getResult();
            // 没有topLevel
            if (topLevelKv == null) {
	            // merged结果为ADD消息, 那么产生insert的消息. 如果merge完是一条DELETE消息, 相当于这条消息的Changelog还没有下发就已经删除了, 所以这个Changelog就不下发了.
                if (merged != null && isAdd(merged)) {
                    reusedResult.addChangelog(replace(reusedAfter, RowKind.INSERT, merged));
                }
            } else {
	            // 有topLevel的数据, merged结果为空或者为DELETE消息, 那么产生UB和UA消息
                if (merged == null || !isAdd(merged)) {
                    reusedResult.addChangelog(replace(reusedBefore, RowKind.DELETE, topLevelKv));
                } else if (!changelogRowDeduplicate
                        || !valueEqualiser.equals(topLevelKv.value(), merged.value())) {
                    reusedResult
                            .addChangelog(replace(reusedBefore, RowKind.UPDATE_BEFORE, topLevelKv))
                            .addChangelog(replace(reusedAfter, RowKind.UPDATE_AFTER, merged));
                }
            }
            return reusedResult.setResultIfNotRetract(merged);
        } else {
	        // 只有一个value, 并且这个value不在topLevel, 那么就是本次新的Changelog, 置为 insert 数据.
            if (topLevelKv == null && isAdd(initialKv)) {
                reusedResult.addChangelog(replace(reusedAfter, RowKind.INSERT, initialKv));
            }
            // either topLevelKv is not null, but there is only one kv,
            // so topLevelKv must be the only kv, which means there is no change
            //
            // or initialKv is not an ADD kv, so no new key is added
            return reusedResult.setResultIfNotRetract(initialKv);
        }
    }

文章转载自:血染河山

原文链接:https://www.cnblogs.com/Aitozi/p/17909012.html

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

为什么要学习这门课程?·新一代流式数据湖技术组件深入讲解,帮助你快速构造数据湖知识体系。·为构建湖仓一体架构提供底层技术支撑。本课程将从原理、架构、底层存储细节、性能优化、管理等层面对Paimon流式数据湖组件进行详细讲解,原理+实战,帮助你快速上手使用数据湖技术。讲师介绍华为HCIP认证大数据高级工程师北京猎豹移动大数据技术专家中科院大数据研究院大数据技术专家51CTO企业IT学院优秀讲师电子工业出版社2022年度优秀作者出版书籍:《Flink入门与实战》、《大数据技术及架构图解实战派》。本课程提供配套课件、软件、试题、以及源码。课程内容介绍:1、什么是Apache Paimon2、Paimon的整体架构3、Paimon的核心特点4、Paimon支持的生态5、基于Flink SQL操作Paimon6、基于Flink DataStream API 操作Paimon7、Paimon中的内部表和外部表8、Paimon中的分区表和临时表9、Paimon中的Primary Key表(主键表)10、Paimon中的Append Only表(仅追加表)11、Changelog Producers原理及案例实战12、Merge Engines原理及案例实战13、Paimon中的Catalog详解14、Paimon中的Table详解15、Paimon之Hive Catalog的使用16、动态修改Paimon表属性17、查询Paimon系统表18、批量读取Paimon表19、流式读取Paimon表20、流式读取高级特性Consumer ID21、Paimon CDC数据摄取功能22、CDC之MySQL数据同步到Paimon23、CDC之Kafka数据同步到Paimon24、CDC高级特性之Schema模式演变25、CDC高级特性之计算列26、CDC高级特性之特殊的数据类型映射27、CDC高级特性之中文乱码28、Hive引擎集成Paimon29、在Hive中配置Paimon依赖30、在Hive中读写Paimon表31、在Hive中创建Paimon表32、Hive和Paimon数据类型映射关系33、Paimon底层文件基本概念34、Paimon底层文件布局35、Paimon底层文件操作详解36、Flink流式写入Paimon表过程分析37、读写性能优化详细分析38、Paimon中快照、分区、小文件的管理39、管理标签(自动管理+手工管理)40、管理Bucket(创建+删除+回滚)
<think>嗯,用户之前问过关于Flink集成Paimon读取changelog的问题,现在又接着问了两个更深入的问题。首先需要确认用户的需求,他们可能是在构建实时数据处理系统,想了解Flink和Paimon的集成方式,特别是流处理和批处理的区别,以及除了读取changelog之外的其他集成方式。另外,用户还比较关心Paimonchangelog与传统数据库binlog的区别,这可能涉及到数据捕获机制和存储方式的差异。 首先,回答流处理和批处理的区别。用户可能已经知道Flink有两种处理模式,但需要详细对比在读取Paimon时的具体差异。比如数据范围、处理方式、触发时机、结果输出等。这部分需要明确两者的不同点,帮助用户选择适合的场景。 然后,关于Flink集成Paimon的其他方式,用户可能想了解除了读取changelog之外的批量读取、写入数据、执行DDL、时间旅行查询等。需要列举出不同的使用场景,比如离线分析、数据导入导出、版本回溯等,说明Paimon的多功能性。 最后,比较changelog和传统数据库binlog的区别。这里需要从生成机制、存储方式、数据内容、应用场景等方面展开。用户可能想知道为什么选择Paimonchangelog而不是传统的binlog,特别是在大数据场景下的优势,比如扩展性、存储成本、流批一体支持等。 需要确保回答结构清晰,分点说明,让用户容易理解。同时,注意使用技术术语的正确性,比如准确描述Paimonchangelog-producer配置,Flink的流批模式设置等。可能用户是开发人员或架构师,需要详细的配置示例和应用场景,帮助他们实际应用这些知识。 另外,用户可能隐含的需求是评估Paimon是否适合他们的系统架构,所以需要强调Paimon的优势,比如ACID支持、与Flink的深度集成、流批一体等,帮助他们做出决策。同时,对比binlog时,指出Paimonchangelog更适合大数据环境,避免传统数据库的扩展性问题。 需要注意避免过于技术性的术语,用通俗的语言解释概念,同时保持专业性。例如,解释binlog时,可以提到它是MySQL等数据库的日志机制,而Paimonchangelog是专为数据湖设计的,更适合大规模数据处理。 最后,总结部分需要归纳主要观点,帮助用户快速回顾重点。确保回答覆盖了所有用户的问题点,没有遗漏,并且逻辑连贯,结构合理。</think>以下是关于 **Flink 读取 Paimon Changelog 的流批处理区别**、**其他集成方式**以及 **Changelog 与传统数据库 Binlog 的区别**的详细说明: --- ### **一、Flink 读取 Paimon Changelog:流处理 vs 批处理的区别** #### **1. 数据处理模式** | **特性** | **流处理(Streaming Mode)** | **批处理(Batch Mode)** | |-------------------|-------------------------------------------------------|--------------------------------------------------| | **数据范围** | 持续读取表的 **增量变更日志(Changelog)**,包含所有插入、更新、删除操作。 | 读取表的 **当前快照(Snapshot)**,仅包含某一时刻的全量数据,不包含历史变更。 | | **处理方式** | 基于事件驱动的增量处理,逐条消费变更记录。 | 全量一次性处理,无增量逻辑。 | | **触发时机** | 持续运行,实时响应新数据的写入。 | 按需触发(如手动执行或定时调度)。 | | **结果输出** | 支持实时输出增量结果(如 Kafka、数据库、Paimon 表等)。 | 输出一次性结果(如文件、静态表)。 | | **适用场景** | 实时监控、实时聚合、CDC 数据同步等。 | 离线分析、全量数据导出、历史快照查询等。 | #### **2. 核心配置差异** - **流处理模式**需显式指定参数(如 `log.scan='latest'` 或 `scan.mode='latest'`): ```sql -- Flink SQL 流式读取 SELECT * FROM paimon_table /*+ OPTIONS('log.scan'='latest') */; ``` - **批处理模式**默认直接读取快照,无需额外配置: ```sql -- Flink SQL 批式读取 SELECT * FROM paimon_table; ``` #### **3. 示例对比** 假设 Paimon 表 `orders` 有一条更新操作: - **流处理**:会读到两条记录(`-U` 旧值、`+U` 新值)。 - **批处理**:仅读到当前快照中的最新值,不感知历史变更。 --- ### **二、Flink 集成 Paimon 的其他方式** 除了读取 Changelog,Flink 还可通过以下方式与 Paimon 集成: #### **1. 批量读写快照数据** - **功能**:以批处理方式读写 Paimon 表的全量数据。 - **场景**:离线分析、数据迁移、T+1 报表等。 - **示例**: ```sql -- 写入数据到 Paimon 表(批处理) INSERT INTO paimon_table SELECT * FROM source_table; ``` #### **2. 流式写入数据** - **功能**:将 Flink 流式作业的结果直接写入 Paimon 表,支持 Upsert 和 Append 模式。 - **场景**:实时数仓、流式 ETL。 - **示例**: ```sql -- 流式写入 Paimon(需定义主键) CREATE TABLE paimon_sink ( user_id INT PRIMARY KEY, cnt BIGINT ) WITH ('connector'='paimon', 'path'='...'); INSERT INTO paimon_sink SELECT user_id, COUNT(*) FROM kafka_source GROUP BY user_id; ``` #### **3. 时间旅行查询(Time Travel)** - **功能**:读取 Paimon 表的历史快照(基于时间戳或版本号)。 - **场景**:数据版本回溯、故障恢复、审计分析。 - **示例**: ```sql -- 读取 24 小时前的快照 SELECT * FROM paimon_table /*+ OPTIONS('scan.timestamp-millis'='1672531200000') */; ``` #### **4. 执行 DDL 操作** - **功能**:通过 Flink SQL 直接管理 Paimon 表的元数据(如创建、删除表)。 - **示例**: ```sql CREATE TABLE paimon_table ( id INT PRIMARY KEY, name STRING ) WITH ( 'connector' = 'paimon', 'path' = 'file:///path/to/table' ); ``` --- ### **三、Paimon Changelog 与传统数据库 Binlog 的区别** #### **1. 核心特性对比** | **特性** | **Paimon Changelog** | **传统数据库 Binlog** | |-------------------|-------------------------------------------------------|------------------------------------------| | **生成机制** | 由数据湖表主动生成(通过 `changelog-producer` 配置),支持流批一体。 | 由数据库内核生成(如 MySQL 的事务日志),仅服务于数据库自身。 | | **存储形式** | 基于文件(如日志文件或列存格式),支持大规模分布式存储。 | 基于数据库本地文件(如 MySQL 的二进制日志),通常单机存储。 | | **数据内容** | 包含完整的行级变更(`+I`/`-U`/`+U`/`-D`),支持主键去重。 | 记录数据库所有事务操作(如 DML、DDL),格式与数据库强绑定。 | | **一致性保证** | 基于快照隔离(Snapshot Isolation),支持 ACID 语义。 | 严格遵循数据库的 ACID 事务模型。 | | **扩展性** | 天然支持分布式存储和计算(如 HDFS/S3),适合大数据场景。 | 受限于单机或主从架构,扩展性较低。 | | **使用场景** | 流批一体分析、实时数仓、CDC 数据湖。 | 数据库主从同步、数据备份、点查恢复。 | #### **2. 典型差异点** - **存储成本**: - Paimon Changelog 基于数据湖存储(如 HDFS/S3),成本低且支持冷热分层。 - Binlog 通常存储在本地磁盘,需定期清理,长期存储成本高。 - **数据处理灵活性**: - Paimon Changelog 可直接被 Flink/Spark 等引擎消费,无需中间组件。 - Binlog 需通过 Debezium/Canal 等工具解析并转发到 Kafka,链路复杂。 - **数据规模**: - Paimon 支持 PB 级数据变更日志存储,Binlog 通常用于单库或中小规模数据。 --- ### **四、总结** #### **1. Flink 读取 Paimon Changelog 的流批区别** - **流处理**:实时消费变更日志,适合增量计算。 - **批处理**:读取静态快照,适合全量分析。 #### **2. 其他集成方式** - 批量读写、流式写入、时间旅行查询、DDL 操作等。 #### **3. Changelog vs Binlog** - **Paimon Changelog** 是为大数据场景设计的分布式、低成本、流批一体的变更日志。 - **Binlog** 是传统数据库的核心日志,强一致但扩展性有限。 通过 Flink 和 Paimon 的深度集成,用户可以在数据湖中实现流批一体、实时分析、版本回溯等复杂场景,同时避免传统数据库 Binlog 的扩展性和成本问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值