大家好,我是大圣。
Fluss 提供了可靠的底层存储设计与灵活的查询更新机制。然而,这一切听起来似乎很复杂,里面有太多看似晦涩的技术名词——比如日志表(LogTablet)、键值表(KvTablet)、Tablet、TabletServer 等等。
那么,Fluss 的存储到底是怎么运作的?本文将从一个具体的数据例子出发,带你逐步了解 Fluss 的底层存储逻辑,以及查询和更新数据时,系统背后的变化过程。
从一个具体的例子开始
假设我们在 Fluss 中创建了一个表,用于记录用户的行为数据。这张表的结构如下:
CREATE TABLE user_activity (
user_id BIGINT,
activity STRING,
event_date DATE,
score INT,
PRIMARY KEY (user_id, event_date) NOT ENFORCED
)
PARTITIONED BY (event_date)
WITH ('bucket.num' = '2');
- 字段解释:
user_id
:用户的唯一标识。activity
:用户的行为类型(如登录、浏览)。event_date
:行为发生的日期。score
:行为得分。
- 配置说明:
- 主键是
user_id
和event_date
,这意味着每个用户在每一天的行为记录是唯一的。 - 表按
event_date
进行分区,每个分区包含 2 个桶(bucket.num=2
)。
- 主键是
接下来,我们向表中插入以下三条记录,模拟一些用户行为:
user_id | activity | event_date | score |
---|---|---|---|
1 | login | 2023-01-01 | 10 |
2 | browse | 2023-01-01 | 15 |
1 | purchase | 2023-01-02 | 50 |
1. 数据写入时的存储过程
Fluss 是如何存储这些数据的?下面我们从 分区存储、分桶存储、日志表存储 和 键值表存储 四个角度详细说明。
1.1 分区存储
Fluss 按照 event_date
字段对数据进行分区,将数据分为不同的分区。我们的数据中有两个不同的 event_date
:
-
分区
event_date=2023-01-01
:user_id=1, activity=login, score=10 user_id=2, activity=browse, score=15
-
分区
event_date=2023-01-02
:user_id=1, activity=purchase, score=50
分区存储的好处在于,可以按照数据的某些维度(如时间、地域等)将数据进行逻辑划分,提高查询效率和管理方便性。
1.2 分桶存储
每个分区中的数据会进一步按照 哈希算法 分配到不同的 桶 中。假设我们将 user_id
作为分桶的依据,并设置每个分区有 2 个桶(bucket.num=2
)。
- 对于
user_id=1
,哈希值是 1,1 % 2 = 1
,所以它被分配到 桶 1。 - 对于
user_id=2
,哈希值是 2,2 % 2 = 0
,所以它被分配到 桶 0。
这样,分区和分桶的最终数据分布如下:
- 分区
event_date=2023-01-01
:- 桶 0:
user_id=2, activity=browse, score=15
- 桶 1:
user_id=1, activity=login, score=10
- 桶 0:
- 分区
event_date=2023-01-02
:- 桶 1:
user_id=1, activity=purchase, score=50
- 桶 1:
通过 分桶,Fluss 实现了数据的并行处理,使得每个桶的数据可以单独进行处理,极大地提升了并发性能。
1.3 存储到日志表(LogTablet)
Fluss 使用日志表(LogTablet)记录所有的 数据变更(插入、更新、删除)。每条数据变更都会按顺序写入 .log
文件,并生成对应的 .index
文件,便于快速定位。
-
日志表(.log 文件) 存储的内容:
[Offset=0] user_id=1, event_date=2023-01-01, activity=login, score=10 [Offset=64] user_id=2, event_date=2023-01-01, activity=browse, score=15 [Offset=128] user_id=1, event_date=2023-01-02, activity=purchase, score=50
-
日志表的索引(.index 文件) 内容:
Offset=0 -> user_id=1, event_date=2023-01-01 Offset=64 -> user_id=2, event_date=2023-01-01 Offset=128 -> user_id=1, event_date=2023-01-02
日志表保证了每一条数据变更都被记录下来,并且通过索引文件可以高效定位到变更记录。
1.4 存储到键值表(KvTablet)
键值表(KvTablet)用于存储 每个主键的最新状态,即每个用户在每一天的最新行为记录。在 Fluss 中,键值表的底层存储使用了 RocksDB,它基于 LSM 树 的设计,支持高效的查询和更新操作。
键值表中存储的数据如下:
Key | Value |
---|---|
1-2023-01-01 |
{"activity":"login", "score":10} |
2-2023-01-01 |
{"activity":"browse", "score":15} |
1-2023-01-02 |
{"activity":"purchase", "score":50} |
通过键值表,我们可以快速查询某个主键的最新数据,例如查询 user_id=1
在 2023-01-02
的行为记录,直接从键值表中获取 {"activity":"purchase", "score":50}
。
2. Fluss 中的 Tablet 和 TabletServer
在 Fluss 中,Tablet 和 TabletServer 是关键组件,负责管理和存储数据。接下来我们将详细讲解这两个概念如何与数据存储和查询流程结合。
2.1 Tablet
- Tablet 是 Fluss 中的最小存储单元。每个 Tablet 存储一定范围的数据,这些数据根据 分区(如
event_date
) 和 分桶(如user_id
) 进行划分。 - 数据存储到 Tablet 中时,根据 分区字段(例如
event_date
)和 桶字段(例如user_id
)进行分配。
例如:
- Tablet 1 存储分区
event_date=2023-01-01
的数据,并按user_id
哈希值分配到桶中:- 桶 0:
user_id=2, activity=browse, score=15
- 桶 1:
user_id=1, activity=login, score=10
- 桶 0:
- Tablet 2 存储分区
event_date=2023-01-02
的数据,并将所有数据分配到桶 1:- 桶 1:
user_id=1, activity=purchase, score=50
- 桶 1:
每个 Tablet 可以包含多个桶的数据,它们是数据存储的 逻辑单元,存储在物理硬盘上,支持高效的并行存储和查询。
2.2 TabletServer
TabletServer 是 Fluss 中的服务端组件,负责管理和处理多个 Tablet。每个 TabletServer 管理一定数量的 Tablet,确保数据的存储和查询能够顺利进行。
- TabletServer 的工作方式
- 当客户端发起查询请求时,TabletServer 根据请求中的分区字段(如
event_date
)和桶字段(如user_id
),快速定位到目标 Tablet,并从中检索数据。
- 当客户端发起查询请求时,TabletServer 根据请求中的分区字段(如
- TabletServer 如何处理查询
- 假设我们查询
user_id=1
和event_date=2023-01-01
的数据。TabletServer 会首先定位到 Tablet 1,然后根据哈希值找到桶 1,获取数据user_id=1, activity=login, score=10
。
- 假设我们查询
3. 数据查询和更新流程
查询流程:
假设我们查询 user_id=1
在 2023-01-01
的行为数据。
-
查询键值表:
- Fluss 首先会查询 键值表,通过
user_id=1
和event_date=2023-01-01
直接查找对应的键值。 - 返回结果:
{"activity":"login", "score":10}
。
- Fluss 首先会查询 键值表,通过
-
查询日志表:
-
如果需要查看历史变更,Fluss 会查询 日志表。日志表通过
.index
文件快速定位到.log
文件中的变更记录。 -
查找到 Offset=0,读取日志记录:
[Offset=0] user_id=1, event_date=2023-01-01, activity=login, score=10
-
-
Tablet 和 TabletServer 的作用:
- 在查询过程中,TabletServer 会根据查询条件(如分区字段
event_date
)定位到对应的 Tablet,并从该 Tablet 中返回数据。 - 例如,查询
user_id=1, event_date=2023-01-01
时,TabletServer 定位到 Tablet 1,从中返回数据。
- 在查询过程中,TabletServer 会根据查询条件(如分区字段
更新流程:
假设我们更新 user_id=1
在 2023-01-01
的 score
为 20
。
-
写入日志表:
-
追加一条更新记录到 .log文件:
[Offset=192] UPDATE user_id=1, event_date=2023-01-01, activity=login, score=20
-
-
更新键值表:
-
在键值表中更新对应的键值对:
Key: `1-2023-01-01` Value: `{"activity":"login", "score":20}`
-
-
Tablet 和 TabletServer 的作用:
- 在更新过程中,TabletServer 会根据数据的位置(由分区字段和桶字段决定),定位到对应的 Tablet,进行数据的更新操作。
总结
Fluss 的底层存储和查询更新流程通过 分区、分桶、日志表 和 键值表 的协同工作,保证了高效的数据存储、查询与更新:
- 分区存储:数据按
event_date
进行分区,方便管理和查询。 - 分桶存储:每个分区的数据根据
user_id
划分到不同的桶,支持并行查询和处理。 - 日志表存储:记录所有变更数据,支持数据追溯和恢复。
- 键值表存储:存储最新的主键值,支持高效查询和更新。
- Tablet 和 TabletServer:通过 Tablet 来管理数据,TabletServer 管理多个 Tablet,确保数据存储和查询操作的高效执行。
写在最后
上面的内容通过一个具体数据的例子,简单梳理了 Fluss 的底层存储和数据查询、更新的过程。后面的文章会陆续更新 Fluss 的文章,让大家对 Fluss 这个后起之秀有更直观的认识,最后欢迎大家关注"大圣数据星球"微信公众号,一起来讨论大数据技术。
共同学习,写下你的评论
评论加载中...
作者其他优质文章