kudu 读写流程
前言
为什么会有kudu?先贴一个经典的图。
kudu诞生之前大数据的主要2种方式存储
-
静态数据
以hdfs引擎作为存储引擎,适用于高吞吐量的离线大数据分析场景,缺点是实现随机读写性能差,更新数据难 -
动态数据
以Hbase为代表作为存储引擎,适用于大数据随机读写的场景,缺点是大批量读取吞吐量远不如hdfs,不适用批量数据分析的场景。
要实现大批量的读写,随机读写有比较弱,要随机读写+更新,吞吐量有不行,当时的处理策略是,使用两套集群来使用兼顾。
类似架构
如上图所示:
数据实时写入Hbase,实时的数据更新也在Hbase完成,为了应对OLAP的场景需求,通常使用定时(T+1、T+H)将hbase数据写成静态文件(parquet)导入到OLAP引擎(HDFS)
这一架构技能够实现随机读写,又可以支持OLAP分析的场景
但存在弊端:
- 架构复杂,设计环节较多,运维成本很高,还需要考虑高可用,多副本,资源层面也比较浪费,数据安全、监控都比较困难
- 时效性低,从hbase写入到hdfs需要同步加工时间
为了解决上述种种问题,诞生了一个”中流砥柱“ kudu
kudu:定位是Fast Analytics on Fast Data, 是一个既支持随机读写,又支持OLAP分析的大数据引擎。
KUDU 是一个折中的产品,在 HDFS 和 HBase 这两个偏科生中平衡了随机读写和批量分析的性能。从 KUDU 的诞生可以说明一个观点:底层的技术发展很多时候都是上层的业务推动的,脱离业务的技术很可能是空中楼阁。
Kudu是什么 Apache
Kudu是由Cloudera开源的存储引擎,可以同时提供低延迟的随机读写和高效的数据分析能力。它是一个融合HDFS和HBase的功能的新组件,具备介于两者之间的新存储组件。Kudu支持水平扩展,并且与Cloudera Impala和Apache Spark等当前流行的大数据查询和分析工具结合紧密。
kudu架构
名词说明
角色 | 作用 |
---|---|
Master Server | 集群中的老大,负责集群管理、元数据管理等功能【为实现高可用,存在lead-follower节点】 |
Tablet Server | 集群中的小弟,负责数据存储,并提供数据读写服务 一个 tablet server 存储了table表的tablet 和为 tablet 向 client 提供服务。对于给定的 tablet,一个tablet server 充当 leader,其他 tablet server 充当该 tablet 的 follower 副本。 只有 leader服务写请求,然而 leader 或 followers 为每个服务提供读请求 。一个 tablet server 可以服务多个 tablets ,并且一个 tablet 可以被多个 tablet servers 服务着。 【 tablet server—leader提供读写服务,follower只提供读服务】 |
Table(表) | 一张table是数据存储在Kudu的tablet server中。表具有 schema 和全局有序的primary key(主键)。table 被分成称为 tablets 的 segments。 |
Tablet | 一个 tablet 是一张 table连续的segment,tablet是kudu表的水平分区,类似于google Bigtable的tablet,或者HBase的region。每个tablet存储着一定连续range的数据(key),且tablet两两间的range不会重叠。一张表的所有tablet包含了这张表的所有key空间。与其它数据存储引擎或关系型数据库中的 partition(分区)相似。给定的tablet 冗余到多个 tablet 服务器上,并且在任何给定的时间点,其中一个副本被认为是leader tablet。任何副本都可以对读取进行服务,并且写入时需要在为 tablet 服务的一组 tablet server之间达成一致性。 |
kudu的工作模式
说明:
- 每个kudu table 按照hash或range分为多个tablet
- 每个tablet中包含一个MemRowSet以及多个DiskRowSet
- 每个DiskRowSet包含BaseData以及DeltaStores
- DeltaStores由多个DeltaFile和一个DeltaMemStore组成
- insert 请求的新增数据以及对MemRowSet中数据的Update操作(新增的数据还没来得及触发compaction操作,再次进行更新操作的新数据)会先进入到MemRowSet
- 当触发flush条件时将新增数据真正的持久化到磁盘的DiskRowSet内
- 对老数据的update和delete操作是提交到内存中的DeltaMemStore
- 当触发flush条件时会将更新和删除操作持久化到磁盘DiskRowSet中的DeltaFile内,此时老数据还在BaseData【逻辑删除】,新数据已在DeltaFile内
- 当触发compaction条件时,将DeltaFile和BaseData进行合并,DiskRowSet将进行合并,此时老数据才真正的从磁盘移除掉【物理删除】,只留下更新后的数据记录
正文来了
前置补充说明下名词
- 1个Table包含多个Tablet,其中Tablet的数量是根据hash或者是range进行设置的
- 1个Tablet中包含MetaData信息和多个RowSet信息 //MetaData就是保存的Tablet Server上的位置信息等
- 1个RowSet包含一个MemRowSet和多个DiskRowSet
- 其中MemRowSet用于存储insert数据和update后的数据,写满后会刷新到磁盘中也就是多个DiskRowSet中**,默认是1G刷新一次或者是2分钟** //1G数据刷新到众多的DiskRowSet
- DiskRowSet会定期的刷新,进行合并操作,删除更新数据之前的历史数据
- 1个DiskRowSet包含1个BloomFilter,1个Ad_hoc Index,一个BaseData,多个Delta file文件(UndoFile、RedoFile),一个Delta MemStore
- BloomFile:根据DiskRowSet中key生成一个bloom filter,用于快速模糊的定位某一个key是否在DiskRowSet中
- Ad_hoc Index:是主键的索引,用于定位key在DiskRowSet中具体哪个偏移位置
- BaseData:是MemRowSet flush下来的数据,按照列存储,按照主键有序 //也就是CFile文件
- Deltafile:
UndoFile:是BaseData之前的数据历史数据 //更新数据之前的数据
RedoFile:是BaseData之后的mutation记录,可以获得较新的数据 //最新的更新数据 - DeltaMemStore:Delta Memstore达到一定程度后会在内存中合并数据,然后生成Redo data到磁盘
合并策略:
①多个DeltaFile进行合并生成一个大的DeltaFile。默认是1000个DeltaFile进行合并一次
②DeltaFile文件的大小和Base data的文件的比例为0.1的时候,会进行合并操作,生成Undo data
kudu的读流程
- 客户端向kudu Master 请求tablet的所在位置
- kudu Master返回tablet的所在位置
- 为了优化读取和写入,客户端会将元数据进行缓存
- 根据主键范围过滤目标的tablet,请求tablet follower
- 根据主键过滤Scan范围,定位DataRowSets
- 加载BaseData,并与DeltaStores合并
- 拼接上一步骤得到的老数据与MemRowSet数据,得到所需数据
- 将数据返回到客户端
kudu的写流程
- 客户端向kudu Master请求tablet所在的位置
- kudu Master返回tablet所在的位置
- 为了优化读取和写入,客户端会将元数据进行缓存
- 根据分区策略,路由到对应Tablet,请求Tablet Leader
- 根据RowSet【memrowset,diskrowset】记录的主键范围过滤掉不包含新增数据主键的RowSet
- 根据RowSet【memrowset,diskrowset】布隆过滤器再进行一次过滤,过滤掉不包含新数据主键的RowSet
- 查询RowSet【memrowset,diskrowset】中的B树索引判断命中新数据的主键,若命中则报错主键冲突,否则新数据写入MemRowSet(写入操作先被提交到tablet的预写日志(WAL),并根据Raft一致性算法取得追随节点的同意,然后才会被添加到其中一个tablet的内存中)「①插入会被添加到tablet的MemRowSet中 ② 更新和删除操作将被追加到MemRowSet中的原始行之后以生成redo记录的列表」
- 返回响应给客户端
「
- Kudu在MemRowset中写入一行新数据,在MemRowset(1G或者是120s)数据达到一定大小时
- MemRowset将数据落盘,并生成一个diskrowset用于持久化数据还生成一个memrowset继续接收新数据的请求
」
kudu更新流程
更新删除流程与写入流程类似,区别就是最后判断是否存在主键时候的操作,若存在才能更新,不存在才能插入新数据。
- 客户端向kudu Master请求tablet所在位置
- kudu Master 返回tablet所在位置
- 为了优化读取和写入,客户端将元数据进行缓存
- 根据分区策略,路由到对应的Tablet,请求Tablet Leader
- 根据RowSet【memrowset,diskrowset】记录的主键范围过滤掉不包含新增数据主键的RowSet
- 根据RowSet【memrowset,diskrowset】布隆过滤器再进行一次过滤,过滤掉不包含新数据主键的RowSet
- 查询RowSet【memrowset,diskrowset】中的B树索引判断是否命中修改的数据主键,若命中则修改至DeltaStores,否则报错数据不存在
- 返回响应给客户端
补充说明:RowSet包含memrowset,diskrowset
- 当待更新数据位于memrowset时:找到待更新数据所在行,然后将更新操作记录在所在行中一个mutation链表中,与插入数据不同的位置上,在memrowset将数据落盘时,Kudu会将更新合并到base data,并生成UNDO records用于查看历史版本的数据
- 当待更新数据位于DiskRowset时:找到待更新数据所在的DiskRowset,每个DiskRowset都会在内存中设置一个DeltaMemStore,将更新操作记录在DeltaMemStore中,在DeltaMemStore达到一定大小时,flush在磁盘,形成DeltaFile中
- 注意:wal日志的作用是如果我们在做真正的操作之前,先将这件事记录下来,持久化到可靠存储中(因为日志一般很小,并且是顺序写,效率很高),然后再去执行真正的操作。这样执行真正操作的时候也就不需要等待执行结果flush到磁盘再执行下一步,因为无论在哪一步出错,我们都能够根据备忘录重做一遍,得到正确的结果。
这一篇感觉也比较清晰,也可参考:
https://cloud.tencent.com/developer/article/2187652
贴几个图吧
写入数据
当 Client 请求写数据时,先根据主键从 Mater Server 中获取要访问的目标 Tablets,然后到对应的 Tablet 获取数据。因为 KUDU 表存在主键约束,所以需要进行主键是否已经存在的判断。一个 Tablet 中存在很多个 RowSets,为了提升性能,我们要尽可能地减少要扫描的 RowSets 数量。首先,我们先通过每个 RowSet 中记录的主键的(最大最小)范围,过滤掉一批不存在目标主键的 RowSets,然后在根据 RowSet 中的布隆过滤器,过滤掉确定不存在目标主键的 RowSets,最后再通过 RowSets 中主键索引,精确定位目标主键是否存在,如果主键已经存在,则报错:主键重复,否则就进行写 MemRowSet。写入操作先被提交到tablet的预写日志(WAL)目录,并根据Raft一致性算法取得follow节点的同意,然后才会被添加到其中一个tablet的内存中,插入会被添加到tablet的MemRowSet中。
读取数据
数据读取过程大致如下:先根据要扫描数据的主键范围,定位到目标的Tablets,然后读取Tablets中的 RowSets。在读取每个RowSet时,先根据主键过滤要scan范围,然后加载范围内的base data,再找到对应的delta stores,应用所有变更,最后union上MenRowSet中的内容,返回数据给 Client。
更新数据
数据更新的核心是定位到待更新数据的位置,这块与写入的时候类似,就不展开了,等定位到具体位置后,然后将变更写到对应的delta store中。