天天看点

Kudu 框架原理

1、表与schema

Kudu设计是面向结构化存储的,因此Kudu的表需要用户在建表时定义它的Schema信息。

Schema信息包含:列定义(含类型),Primary Key定义(用户指定的若干个列的有序组合)。数据的唯一性,依赖于用户所提供的Primary Key中的Column组合的值的唯一性。 Kudu提供了Alter命令来增删列,但位于Primary Key中的列是不允许删除的。

Kudu当前并不支持二级索引。 从用户角度来看,Kudu是一种存储结构化数据表的存储系统。

    在一个Kudu集群中可以定义任意数量的table,每个table都需要预先定义好schema。

    每个table的列数是确定的,每一列都需要有名字和类型,每个表中可以把其中一列或多列定义为主键。这么看来,Kudu更像关系型数据库,而不是像HBase、Cassandra和MongoDB这些NoSQL数据库。不过Kudu目前还不能像关系型数据一样支持二级索引。

Kudu使用确定的列类型,而不是类似于NoSQL的“everything is byte”。这可以带来两点好处: 确定的列类型使Kudu可以进行类型特有的编码。可以提供 SQL-like 元数据给其他上层查询工具,比如BI工具。

2、Kudu 底层数据模型

Kudu的底层数据文件的存储,未采用HDFS这样的较高抽象层次的分布式文件系统,而是自行开发了一套可基于

Table/Tablet/Replica视图级别的底层存储系统。

这套实现基于如下的几个设计目标:

• 可提供快速的列式查询

• 可支持快速的随机更新

• 可提供更为稳定的查询性能保障

Kudu 框架原理

具体Kudu数据存储结构如下所示:

Kudu 框架原理

一张表会分成若干个tablet,每个tablet包括MetaData元信息及若干个RowSet,RowSet包含一个MemRowSet及若干个DiskRowSet,DiskRowSet中包含一个BloomFile、Ad_hoc Index、BaseData、DeltaMem及若干个

RedoFile和UndoFile(UndoFile一般情况下只有一个)。

MemRowSet:用于新数据insert及已在MemRowSet中的数据的更新,一个MemRowSet写满后会将数据刷到磁盘形成若干个DiskRowSet。每次到达32M生成一个DiskRowSet。

DiskRowSet:用于老数据的变更(mutation),后台定期对DiskRowSet做compaction,以删除没用的数据及合并历史数据,减少查询过程中的IO开销。

BloomFile:根据一个DiskRowSet中的key生成一个bloom filter,用于快速模糊定位某个key是否在DiskRowSet中存在。

Ad_hocIndex:是主键的索引,用于定位key在DiskRowSet中的具体哪个偏移位置。

BaseData是MemRowSet flush下来的数据,按列存储,按主键有序。

UndoFile是基于BaseData之前时间的历史数据,通过在BaseData上apply UndoFile中的记录,可以获得历史数据。

RedoFile是基于BaseData之后时间的变更(mutation)记录,通过在BaseData上apply RedoFile中的记录,可获得较新的数据。

DeltaMem用于DiskRowSet中数据的变更mutation,先写到内存中,写满后flush到磁盘形成RedoFile。

DiskRowSet数据结构如下:

Kudu 框架原理

与HBase类似,也是通过增加一条新的记录来描述这次更新/删除操作的。DiskRowSet是不可修改了,那么 KUDU 要如何应对数据的更新呢?在KUDU中,把DiskRowSet分为了两部分:base data、delta stores。

- base data 负责存储基础数据

- delta stores负责存储 base data 中的变更数据

Kudu 框架原理

3、Tablet 发现过程

当创建Kudu客户端时,其会从主服务器上获取tablet位置信息,然后直接与服务于该tablet的服务器进行交谈。

为了优化读取和写入路径,客户端将保留该信息的本地缓存,以防止他们在每个请求时需要查询主机的tablet位置信息。

随着时间的推移,客户端的缓存可能会变得过时,并且当写入被发送到不再是tablet领导者的tablet服务器

时,则将被拒绝。然后客户端将通过查询主服务器发现新领导者的位置来更新其缓存。

程序代码如下:

// 构建KuduClient实例对象

kuduClient = new KuduClient.KuduClientBuilder(masterAddresses) //

    // 设置超时时间间隔,默认值为10s

    .defaultSocketReadTimeoutMs(6000)

    // 采用建造者模式构建实例对象

    .build() ;

// a. 依据表的名称获取KuduTable实例对象,操作表的句柄

KuduTable kuduTable = kuduClient.openTable("itcast_users");

Kudu 框架原理

4、Kudu 写流程

Kudu 框架原理

具体写入数据流程如下所述:

第一、写入操作先被提交到tablet 的预写日志(WAL)上,然后根据Raft 一致性算法取得追随节点的同意后,才会被添加到其中一个tablet 的内存中,插入到MemRowSet中。(因为在MemRowSet 中支持了多版本并发控制(mvcc) ,对最近插入的行(未刷新到磁盘上的新的行)的更新和删除操作将被追加到MemRowSet中的原始行之后以生成Redo 记录的列表)。

第二、Kudu在MemRowSet 中写入新数据,在MemRowSet 达到一定大小或者时间限制(1G 或者 120s),MemRowSet 会将数据落盘,生成一个DiskRowSet 用于持久化数据 和 一个 MemRowSet 继续接受新数据的请求。

5、Kudu 读流程

Kudu 框架原理

如上图,数据读取过程大致如下:先根据要扫描数据的主键范围,定位到目标的Tablets,然后读取Tablets 中的RowSets。

在读取每个RowSet时,先根据主键过滤要scan范围,然后加载范围内的base data,再找到对应的delta stores,应用所有变更,最后union上MemRowSet中的内容,返回数据给Client。

6、Kudu 更新流程

Kudu 框架原理

具体更新操作如下所述:

a、客户端连接到TMaster获取表的相关信息(分区和tablet信息);

b、找到负责写请求的tablet 所在的TServer,kudu接受客户端的请求,检查本次写操作是否符合要求;

c、因为待更新的数据可能位于MemRowSet ,也可能位于DiskRowSet 中,所以根据待更新的数据所处的位置,kudu有不同的做法:

   i、当待更新的数据位于MemRowSet时,找到它所在的行,然后将跟新操作记录在所在行中的一个mutation的链表中,在MemRowSet 数据落地的时候,kudu会将更新合并到base data,并生成undo records 用于查看历史版本的数据和MVCC, undo records 实际上也是以 DeltaFile 的形式存放;

   ii、当待跟新的数据位于DiskRowSet时,找到待跟新数据所在的DiskRowSet ,每个DiskRowSet 都会在内存中设置一个DeltaMemStore,将更新操作记录在DeltaMemStore中,在DeltaMemStore达到一定大小时,flush 在磁盘,形成Delta并存放在DeltaFile中。

d、定时合并 `RedoDeltaFile`

   - 合并策略有三种, 常见的有两种, 一种是 `major`, 会将数据合并到基线数据中, 一种是 `minor`, 只合并 `RedoDeltaFile`