Kudu
简介
kudu简单来说与结构化数据库非常相似,kudu中定义表时与结构化数据库相似,需要定义Schema信息,并且需要且必须要定义primary key,primary key可以定义在一列或多列。kudu是介于Hive、Hbase之间的数据存储引擎。Kudu可以作为数仓的选型记录,存储海量数据并提供较为优秀的海量数据随机读写能力,这主要得益于kudu底层的数据存储模型。
架构
Kudu架构师分布式的主从架构,分为Master与Tablet两部分组件。
kudu架构.jpg
- 架构
- Master Server
集群中的老大,负责集群管理、元数据管理等功能,当Kudu需要对数据CRUD时,Master Server可以根据SQL中的表信息等初步定为到数据变更会在哪些Tablets中,确保某Table中的数据记录不会落到其他Table的Tablet中,这点非常重要。架构上Master为了避免单点故障,也可以部署成集群做主备方案,产生leader、follower两个角色,只有leader对外提供服务。 - Tablet Server
集群中的小弟,负责数据存储,并提供数据读写服务。其中Tablet服务中也是分布式架构,针对每一个tablet有leader与followers角色,与redis的主从架构相似,leader、followers都可以进行读操作,但是事务操作(写)只能在leader中进行。每一个tablet server中存储的是一个个tablet
- Master Server
- kudu tablet
- tablet是kudu表中数据水平分区(避免原始表数据量太大,只在一个tablet server),一个表可以划分成多个tablet(类似于hbase的region)
- tablet中主键是不重复且连续的 所有tabler加起来就是一个原始table的所有数据
- tabler在存储的时候 会进行冗余存放,设置多个副本
- 在一个tablet的所有冗余中, 任意时刻 只有一个是leader,其他冗余都是follower,但只有tablet leader才可以进行事务操作。
Quick Start
kudu官网中有quick start安装教程:https://kudu.apache.org/docs/quickstart.html,在此不做详细描述。
在centos /etc/profile.d目录中编写开机自启的脚本:kudu-docker.sh脚本,用于linux启动自动拉起kudu docker,脚本如下:
#!/bin/sh
MASTER1=` docker inspect --format '{{.State.Running}}' docker_kudu-master-1_1 `
MASTER2=` docker inspect --format '{{.State.Running}}' docker_kudu-master-2_1 `
TSERVER1=` docker inspect --format '{{.State.Running}}' docker_kudu-tserver-1_1 `
TSERVER2=` docker inspect --format '{{.State.Running}}' docker_kudu-tserver-2_1 `
if ( $MASTER1 == "true" );
then
echo "docker_kudu-master-1_1 had started"
else
docker start docker_kudu-master-1_1
fi
if ( $MASTER2 == "true" );
then
echo "docker_kudu-master-2_1 had started"
else
docker start docker_kudu-master-2_1
fi
if ( $TSERVER1 == "true" );
then
echo "docker_kudu-tserver-1_1 had started"
else
docker start docker_kudu-tserver-1_1
fi
if ( $TSERVER2 == "true" );
then
echo "docker_kudu-tserver-2_1 had started"
else
docker start docker_kudu-tserver-2_1
fi
数据分区策略
TODO
原理简说
底层数据模型
kudu的底层数据文件的存储,自行开发了一套可基于Table/Tablet/Replica(冗余)视图级别的底层存储系统。
这一套底层存储的优点:
- 可提供快速的列式查询
- 可支持快速的随机更新
-
可提供更为稳定的查询性能保障
底层架构如下:
kudu底层存储.jpg
一张table按照分区策略分成若干个tablet,每个tablet包括MetaData元信息及若干个Rowset;
- RowSet包含一个MemRowSet以及若干个DiskRowSet,DiskRowSet中包含一个BloomFile、AdhocIndex、UndoFile、RedoFile、BaseData、DeltaMem。
- MemRowSet:
用于新数据 insert 以及已经在 MemRowSet 中的数据的更新,一个MemRowSet 写满后悔将数据刷到磁盘形成若干个DiskRowSet,默认是1G或者120S。 - DiskRow:
用于老数据的变更,后台定期uiDiskRowSet做compaction,已删除没用的数据以及合并历史数据,减少查询过程中的IO开销。- BloomFile:
根据一个DiskRowSet中的key生成一个Bloom Filter,用于快速模块定位某个key(这个key是根据分区策略流入该Tablet的record primary key,因此这就是为什么kudu的record一定要有primary key的原因)是否存在于该 DiskRowSet中。 - Ad_hocIndex:
是record主键的索引,用于定位key在DiskRowSet中具体的偏移位置。 - BaseData
是MemRowSer Flush下来的数据,按列存储,按主键有序,简而言之,该文件中才是存放真正的Table数据。 - UndoFile
是基于BaseData之前时间的历史数据,通过在BaseData上apply UndoFile中的记录,可以获得历史数据。 - RedoFile
是基于BaseData之后时间的变更记录,通过在BaseData上apply RedoFile中的记录,可获得较新的数据。 - DeltaFile
DiskRowSet中BaseData不会直接接受数据变更,而是先将变更写到内存中,写满后flush到磁盘形成RedoFile。
- BloomFile:
工作流程
整体工作流
在了解了上述的底层数据模型之后,当一次MemRowSet达到规定的体量之后刷新到磁盘进行持久化成DiskRowSet。于此同时基础数据会进入DiskRowSet中的BaseData中存储,与此同时,每份DiskRowSet都会在内存中有一个对应的DeltaMemStore,负责记录该DiskRowSet后续的数据变更(更新、删除)。DeltaMenStore内部维护一个B-树索引映射到每个row_offset对应的数据变更。DeltaMenStore数据增长到一定程度后转换成二进制文件存储到磁盘形成DeltaFile文件。随着BaseData对应数据的不断变更,DeltaFile会慢慢增长。
数据变更流程.jpg
数据写入流程
数据写入流程,有两个主要的步骤:
- Tablet按对应策略路由
- 判断当前record主键是否已经存在于路由的tablet中
流程图:
kudu数据写入流程.jpg
流程梳理
- 对于分区策略路由没有太多好说的,kudu中记录进入tablet的分区路由策略有三种,range、hash、混合,master只需要根据记录的primary key进行计算得出tablet即可。
- 因为主键的唯一性,kudu有严格的要求,因此在记录写入之前先判断主键是否存在尤为的关键,在熟悉了kudu底层数据模型之后,会发现路由到的tablet中有很多的RowSet,这些RowSet中有大量的数据,因此当插入一条记录就需要对比所有的RowSet中所有的记录就显得非常的“吃力”。因此在判重时,尽量减少RowSet文件的扫描就成了首要因素,步骤如下:
- 每个RowSet的Primary Key都有各自区间,先判断新Record的Primary Key符合哪些区间,筛选出区间对应的RowSet准备进行后续操作判断,若没有符合的RowSet则说明不会有Primary Key冲突,放心写入MemRowSet即可。
- 若第一步筛选出RowSet,则说明,新Record的Primary Key可能与这些RowSet中记录有冲突需要进一步筛选,在熟悉了kudu底层数据模型之后,知道在每个RowSet中都有BloomFile,拿着Primary Key进行Bloom之后去判断key是否存在于Bloom Filter中,若不存在,则说明数据一定不存在与Bloom Filter中,则可以大胆的写入MemRow中,若有些RowSet的BloomFile中存在新纪录的Primary Key,则需要筛选出这些RowSet进行后一步的筛选,因为BloomFilter存在,不一定数据一定存在,这一点不熟悉的伙伴可以看一下Bloom Filter。
- 若上一步某些RowSet依然被命中,那就需要针对这些RowSet进行最后一步判断,因为每个RowSet中都有Ad_hoxIndex文件,拿着新Primary Key进行索引查询,查看是否能找到具体记录,若索引中未命中Primary Key,则说明该记录可以写入MemRowSet中,若命中了,则说明经过这三步的判断,可以判定出新记录以及存在,无法插入,对外报错即可。
数据查询流程
数据查询流程响应比较简单一些,但是kudu中数据变更并不会直接应用到数据落到磁盘,因此某一时刻的数据其实是DiskRowSet中的BaseData记录与变更文件记录的应用。
kudu数据读取流程.jpg
- 读请求进入kudu
- Master Server根据SQL中的信息初步筛选出符合记录的Tablets
- 每个Tablet都有若干个RowSet,将所有Tablet中的所有RowSet筛选出来之后,进行主键范围判断,找出符合primary key范围的RowSet,读取这些RowSet中的BaseData。
- 加载对应RowSet的delta stores与已经在磁盘的delta file,两者合并,应用所有变更。
- 拿到上述两个步骤的数据之后,再与内存中MemRowSet数据进行Union合并返回给Client。
数据更新流程
数据更新流程比较重要的一步与数据写入流程相似,都是要先判断record的primary key,唯一不同的是,更新流程更希望数据已经存在及希望primary key命中。
kudu数据更新流程.jpg
- Master Server根据SQL中的信息初步筛选出符合记录的Tablets
- 与数据写入流程类似进行筛选出命中record primary key的RowSet
- 若不存在对应的RowSet则说明需要更新的记录不存在并报错,若RowSet存在,则根据kudu底层数据模型,拿到RowSet中的AdhocFile索引文件获取记录的偏移row_offset
- 最后将变更写到delta Store中
网友评论