Fluss 与数据湖的深度解析(二)
2025/1/2 21:33:18
本文主要是介绍Fluss 与数据湖的深度解析(二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
上一篇文章中我们说了 Fluss 与 Paimon 数据湖的三个相关问题:
- 如何查询 Paimon 数据湖中的数据?
- 如何查询 Fluss 和 Paimon 数据的“联合视图”?
- 如何只查询 Fluss 中的数据?
大家可以先去看这一篇文章,其中第二点 如何查询 Fluss 和 Paimon 数据的“联合视图” 中还遗留一个问题:在做数据查询的时候 Fluss 和 Paimon 数据湖是怎么保证数据一致性的,也就是事务的。还有第三点 如何只查询 Fluss 中的数据 这个问题也没有说,今天这篇文章主要解决上面这几个问题。
Fluss 和 Paimon 数据湖保证事务
当我们在 Fluss 里面建表的时候配置这个表的数据会同步到数据湖的时候,当我们往 Fluss 写入数据的时候,这份数据同时会写入你配置的数据湖里面,在写入过程中会记录数据入湖的Snapshot 和 Fluss Offset 的关联关系。
那么这样 Fluss 和 Paimon 是怎么保证事务的呢?其实当 Fluss 和 Paimon 联合读取数据时,读取的瞬间,Fluss 会:
- 锁定 Paimon 的快照:读取当前 Paimon 的最新快照数据。
- 获取 Fluss 的日志范围(
end_offset
):在读取时瞬间获取当前日志的结束位置。 - 限制日志读取范围:Fluss 仅读取
<paimon_snapshot_offset, end_offset>
范围内的日志数据。 - 与 Paimon 快照数据进行排序合并:将日志增量数据和 Paimon 快照数据合并,确保结果完整且无重复。
比如下面这样一个数据场景:
1. 数据场景:订单表的实时查询
表结构和现有数据
假设有一张订单表 order_table
,配置了 table.datalake.enabled=true
。当前数据如下:
-
Paimon 快照数据(
paimon_snapshot_offset = 3
):订单 ID = 001,金额 = 100,状态 = Pending 订单 ID = 002,金额 = 200,状态 = Confirmed
-
Fluss 日志数据(Offset > 3):
Offset 4: 订单 ID = 003,金额 = 300,状态 = Shipped
用户发起查询:
SELECT SUM(amount) FROM order_table;
查询过程中并发写入的情况
-
初始读取时的数据:
-
查询开始时,读取到了:
Paimon 快照数据: 订单 ID = 001,金额 = 100,状态 = Pending 订单 ID = 002,金额 = 200,状态 = Confirmed
-
Fluss 日志:
Offset 4: 订单 ID = 003,金额 = 300,状态 = Shipped
-
-
读取进行中,新的写入发生:
-
在查询进行的过程中,新的数据被写入 Fluss:
Offset 5: 订单 ID = 004,金额 = 400,状态 = Delivered Offset 6: 订单 ID = 005,金额 = 500,状态 = Pending
-
-
读取到的结果:
-
由于未锁定 end_offset,读取的瞬间不是原子的,查询结果可能会因读取进度不同而出现多种结果:
-
如果读取日志时,Offset = 5 被写入:
SUM(amount) = 100 + 200 + 300 + 400 = 1000
-
如果读取日志时,Offset = 6 被写入:
SUM(amount) = 100 + 200 + 300 + 400 + 500 = 1500
-
-
-
最终结果的不确定性:
- 查询的返回结果可能会随着写入进度不同而变化,导致结果不一致。
- 原因:读取过程并非原子操作,新写入的数据动态影响了查询结果。
2. 不锁定 end_offset
的问题
问题:读取结果的不确定性
- 由于读取 Fluss 日志和 Paimon 快照是分阶段完成的,如果查询过程中有新的写入发生:
- 快照数据已经读取完成,但 日志数据正在读取时被新增写入。
- 这会导致查询结果依赖于写入的时间点和读取的进度,结果具有不确定性。
关键点:读取不是原子的
- 读取过程分阶段:
- 第一步读取 Paimon 的快照数据。
- 第二步读取 Fluss 的日志数据。
- 如果写入发生在读取日志数据的过程中,读取范围不固定,导致结果受动态写入影响。
3. 解决方法:锁定 end_offset
通过在查询开始时锁定 Paimon 快照 和 Fluss 的 end_offset
,可以确保读取过程是确定且一致的。
机制:锁定读取范围
-
锁定 Paimon 快照:
-
确保快照数据在查询期间保持一致。
-
例如,锁定快照 ID = 1,包含以下数据:
订单 ID = 001,金额 = 100,状态 = Pending 订单 ID = 002,金额 = 200,状态 = Confirmed
-
-
锁定 Fluss 的
end_offset
:-
查询开始时,记录当前日志的结束位置,例如:
end_offset = 4
-
限制日志读取范围为:
Offset > 3 且 <= 4
-
查询只读取以下日志数据:
Offset 4: 订单 ID = 003,金额 = 300,状态 = Shipped
-
查询过程示例
-
锁定后读取范围明确:
-
Paimon 数据:
订单 ID = 001,金额 = 100,状态 = Pending 订单 ID = 002,金额 = 200,状态 = Confirmed
-
Fluss 日志数据:
Offset 4: 订单 ID = 003,金额 = 300,状态 = Shipped
-
-
合并结果:
-
查询结果为:
SUM(amount) = 100 + 200 + 300 = 600
-
-
后续写入不影响查询:
- 查询结束后,新写入的数据(Offset 5 和 Offset 6)不会影响当前查询。
- 它们将在下一次查询中被读取。
4. 总结
不锁定 end_offset
的问题:
- 查询过程中数据写入,导致读取范围动态变化,结果具有不确定性。
- 原因:读取过程不是原子的,新数据在读取期间被引入。
锁定 end_offset
的好处:
确保读取范围一致:
- 快照数据和日志数据范围明确,结果不受写入影响。
避免结果不确定性:
- 查询结果固定,且与查询开始时的数据状态一致。
隔离性和一致性:
- 查询过程锁定数据范围,确保事务隔离性。
通过锁定 end_offset
,Fluss 实现了事务性的读取,解决了不确定性问题,确保查询结果的准确性和一致性。
5.Paimon 数据湖如何利用 MVCC 保证事务
Paimon 数据湖使用 多版本并发控制(MVCC) 来保证事务的一致性和隔离性。MVCC 的核心思想是:每次对数据的写入操作(新增、更新或删除)都会生成一个新的 快照(Snapshot),而查询操作总是基于某个固定的快照进行。以下通过一个具体数据的例子,说明 Paimon 如何利用 MVCC 保证事务。
MVCC 的核心概念
- 快照(Snapshot): 每次写入都会生成一个快照,快照记录了写入时刻数据的完整状态。
- 时间旅行查询: 用户可以基于指定的快照读取历史数据,不受后续写入影响。
- 并发操作: 写入操作生成新的快照,不会修改旧快照的数据,保证并发读写的隔离性。
数据场景:订单表的读写操作
表结构
我们有一个订单表 order_table
,包含以下字段:
订单 ID(order_id): STRING,主键 金额(amount): INT 状态(status): STRING
初始数据
假设当前 Paimon 数据湖中已有以下数据:
快照 ID = 1: 订单 ID = 001,金额 = 100,状态 = Pending 订单 ID = 002,金额 = 200,状态 = Confirmed
写入操作:新增和更新订单
新写入的操作
-
新增订单:
订单 ID = 003,金额 = 300,状态 = Shipped
-
更新订单:
订单 ID = 001,金额 = 150,状态 = Confirmed
生成新快照
-
Paimon 会基于上述写入生成一个新的快照:
快照 ID = 2: 订单 ID = 001,金额 = 150,状态 = Confirmed (更新) 订单 ID = 002,金额 = 200,状态 = Confirmed 订单 ID = 003,金额 = 300,状态 = Shipped (新增)
事务保障:查询操作
查询开始前
-
查询基于快照
ID = 1,此时 Paimon 的数据状态为:
快照 ID = 1: 订单 ID = 001,金额 = 100,状态 = Pending 订单 ID = 002,金额 = 200,状态 = Confirmed
查询期间的写入
- 查询执行时,Paimon 收到了新的写入请求,并生成了新的快照(快照 ID = 2)。
- 新写入不会影响当前查询。
- 当前查询仍然基于快照 ID = 1。
查询结果
-
查询操作读取的是快照 ID = 1 的数据:
查询结果: 订单 ID = 001,金额 = 100,状态 = Pending 订单 ID = 002,金额 = 200,状态 = Confirmed
写入后的查询
-
新的查询如果基于快照 ID = 2,则会读取最新的数据状态:
查询结果: 订单 ID = 001,金额 = 150,状态 = Confirmed 订单 ID = 002,金额 = 200,状态 = Confirmed 订单 ID = 003,金额 = 300,状态 = Shipped
MVCC 的优势
并发读写隔离
- 读取固定快照:
- 查询基于某个快照,保证数据的一致性和隔离性。
- 查询不受写入操作的影响。
- 写入生成新快照:
- 写入操作不会修改现有快照的数据,而是生成新的快照,保证并发写入的隔离性。
时间旅行
-
用户可以基于指定快照回溯历史数据:
SELECT * FROM order_table$snapshots WHERE snapshot_id = 1;
返回快照
ID = 1
的数据状态。
事务保证
- 所有写入都是原子的,只有在快照生成成功后,新的数据状态才会对外可见。
总结
通过 MVCC,Paimon 数据湖实现了以下事务特性:
- 一致性:查询总是基于固定快照,保证数据状态一致。
- 隔离性:写入操作生成新快照,不会影响并发查询。
- 持久性:每个快照都是持久化存储,支持历史数据的回溯。
Paimon 的 MVCC 机制使其在高并发的读写场景下,能够保证事务性和高效性,同时为实时数据分析提供强有力的支持。
如何只查询 Fluss 中的数据
其实无论你建表的时候 加不加 table.datalake.enabled=true 这个配置,在我们往 Fluss 写入数据的时候,你所写入的全部数据都会写到 Fluss 本地存储,然后根据一定的策略 Fluss 会把数据存储到远程存储,这就是我们讲Fluss 架构那篇文章里面说的那个远程存储,这个存储和数据湖是没有任何关系的,只是Fluss 框架自己的操作,因为 Fluss 作为一个存储和分析的系统,它肯定会存储所有的数据的。
只是你加了 table.datalake.enabled=true 这个配置的时候,这个配置就相当于一个开关,当我们往 Fluss 写入数据的时候,这份数据就会通过 Fluss 的 compact service 服务把数据同步到数据湖的。
在我们查询的时候也会根据你建表的时候有没有加 table.datalake.enabled=true 这个配置,如果没有加的话,这个时候是只能根据主键查询的,查询的所有数据都是来自 Fluss 自己框架的数据。当你建表的时候加了上面那个配置的时候,你去查询全表的时候,数据是按照我们上篇文章说的那样查询的是Paimon 数据湖和 Fluss 的数据,至于查询的原理在这两篇内容里面已经详细阐述了。
写在最后
这篇文章讲了 Fluss 和 Paiom 数据湖的事务和 Fluss 一些设计原理,能让大家对 Fluss 从设计理念到实现细节上面都有了一个全面的认识。我讲的这些知识点和细节小伙伴在官网或者其他地方都是看不到的,欢迎大家一起来讨论大数据技术,同时我也给大家整理了 2024 年 最新的大厂Java、大数据、大模型等相关内容的面试题,欢迎大家来取。关注微信公众号 大圣数据星球 带你搞定数据开发不迷路。
这篇关于Fluss 与数据湖的深度解析(二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-04从 LB Ingress 到 ZTM:集群服务暴露新思路
- 2025-01-03从入门到精通:AWS认证攻略指南
- 2025-01-03DevOps和平台工程,哪个更适合你?
- 2025-01-03Spark聚合优化:我们如何通过解决GroupBy性能瓶颈和避免使用spark EXPAND命令,将运行时间从4小时缩减至40分钟。
- 2025-01-03彼得·德西斯在AWS reinvent 2024大会上的精彩演讲要点
- 2025-01-03开源商业化 Sealos 如何做到月入 160万
- 2025-01-03数据仓库、数据湖与湖仓架构:一站式数据处理进化论
- 2025-01-02阿里云部署方案项目实战:新手入门教程
- 2025-01-02阿里云RDS项目实战:新手入门教程
- 2025-01-02阿里云部署方案资料详解:新手入门指南