1、Apache Kudu
介绍
在 Kudu
之前,大数据主要以两种方式存储;
- 静态数据: 以
HDFS
引擎作为存储引擎,适用于高吞吐量的离线大数据分析场景。这类存储的局限性是数据无法进行随机的读写。 - 动态数据: 以
HBase
、Cassandra
作为存储引擎,适用于大数据随机读写场景。局限性是批量读取吞吐量远不如HDFS
,不适用于批量数据分析的场景。
面对既需要随机读写,又需要批量分析的大数据场景,该如何选择呢?
数据实时写入 HBase,实时的数据更新也在 HBase
完成,为了应对 OLAP
需求,我们定时将 HBase
数据写成静态的文件(如:Parquet
)导入到 OLAP
引擎(如:Impala
、Hive
)。这一架构能满足既需要随机读写,又可以支持 OLAP
分析的场景,但他有如下缺点:
- 架构复杂: 从架构上看,数据在
HBase
、消息队列、HDFS
间流转,涉及环节太多,运维成本很高。并且每个环节需要保证高可用,都需要维护多个副本,存储空间也有一定的浪费。最后数据在多个系统上,对数据安全策略、监控等都提出了挑战。 - 时效性低: 数据从
HBase
导出成静态文件是周期性的,一般这个周期是一天(或一小时),在时效性上不是很高。 - 难以应对后续的更新: 真实场景中,总会有数据是延迟到达的。如果这些数据之前已经从 HBase 导出到 HDFS,新到的变更数据就难以处理了,一个方案是把原有数据应用上新的变更后重写一遍,但这代价又很高。
为了解决上述架构的这些问题,Kudu
应运而生。Kudu
的定位是 Fast Analytics on Fast Data
, 是 一个既支持随机读写、又支持 OLAP
分析的大数据存储引擎。
2、Kudu
是什么?
Apache Kudu
是由 Cloudera
开源的存储引擎,可以同时提供低延迟的随机读写和高效的数据分析能力。它是一个融合 HDFS
和 HBase
的功能的新组件,具备介于两者之间的新存储组件。
Kudu 支持水平扩展,并且与 Cloudera Impala 和 Apache Spark
等当前流行的大数据查询和分析工具结合紧密。
3、Kudu
应用场景
- 适用于那些既有随机访问,也有批量数据扫描的复合场景;
- 高计算量的场景;
- 使用了高性能的存储设备,包括使用更多的内存;
- 支持数据更新,避免数据反复迁移;
- 支持跨地域的实时数据备份和查询。
4、Apache Kudu
架构
与 HDFS
和 HBase
相似,Kudu
使用单个的 Master
节点,用来管理集群的元数据,并且使用任意数量的 Tablet Server
(类似 HBase
中的 RegionServer
角色)节点用来存储实际数据。可以部署多个 Master
节点来提高容错性。
4.1、 Table
表(Table
)是数据库中用来存储数据的对象,是有结构的数据集合。kudu
中的表具有schema
(纲要)和全局有序的primary key
(主键)。Kudu
中一个table
会被水平分成多个被称之为 tablet 的片段。
4.2、Tablet
一个tablet
是一张 table
连续的片段,tablet
是kudu
表的水平分区,类似于HBase
的region
。每个 tablet
存储着一定连续 range
的数据(key
),且 tablet
两两间的 range
不会重叠。一张表的所有 tablet
包含了这张表的所有 key
空间。
tablet
会冗余存储。放置到多个tablet server
上,并且在任何给定的时间点,其中一个副本被认为是 leader tablet
,其余的被认之为 follower tablet
。每个 tablet
都可以进行数据的读请求,但只有 Leader tablet
负责写数据请求。
4.3、Tablet Server
tablet server
集群中的小弟,负责数据存储,并提供数据读写服务一个 tablet server
存储table
表的 tablet
,向 kudu client
提供读取数据服务。对于给定的 tablet
,一个tablet server
充当 leader
,其他tablet server
充当该 tablet
的 follower
副本。
只有 leader
** 服务写请求**,然而leader
或followers
为每个服务提供读请求 。一个tablet server
可以服务多个 tablets
,并且一个 tablet
可以被多个 tablet servers
服务着。
4.4 、Master Server
集群中的老大,负责集群管理、元数据管理等功能。
5、table
与schema
Kudu
设计是面向结构化存储的,因此,Kudu
的表需要用户在建表时定义它的 Schema
信息,这些 Schema
信息包含:列定义(含类型),Primary Key
定义(用户指定的若干个列的有序组合)。数据的唯一性,依赖于用户所提供的Primary Key
中的 Column
组合的值的唯一性。Kudu
提供了 Alter
命令来增删列,但位于 Primary Key
中的列是不允许删除的。
从用户角度来看,Kudu
**** 是一种存储结构化数据表的存储系统。在一个 Kudu
集群中可以定义任意数量的 table
,每个 table
都需要预先定义好 Schema
。每个table
的列数是确定的,每一列都需要有名字和类型,每个表中可以把其中一列或多列定义为主键。这么看来,Kudu
**** 更像关系型数据库,而不是像 HBase
、Cassandra
和 MongoDB
这些 NoSQL
数据库。不过Kudu
目前还不能像关系型数据一样支持二级索引。
Kudu
使用确定的列类型,而不是类似于NoSQL
的“everything is byte
”。带来好处:确定的列类型使 Kudu
可以进行类型特有的编码,可以提供元数据给其他上层查询工具。
6、Kudu
底层数据模型
Kudu
的底层数据文件的存储,未采用 HDFS
这样的较高抽象层次的分布式文件系统,而是自行开发了一套可基于 Table/Tablet/Replica
视图级别的底层存储系统。
这套实现基于如下的几个设计目标:
- 可提供快速的列式查询;
- 可支持快速的随机更新;
- 可提供更为稳定的查询性能保障。
一张 table
会分成若干个 tablet
,每个 tablet
包括 MetaData
元信息及若干个 RowSet
。
RowSet
包含一个MemRowSet
及若干个 DiskRowSet
,DiskRowSet
中包含一个BloomFile
、Ad_hoc Index
、BaseData
、DeltaMem
及若干个RedoFile
和UndoFile
。
MemRowSet
: 用于新数据insert
及已在 MemRowSet
中的数据的更新,一个MemRowSet
写满后会将数据刷到磁盘形成若干个 DiskRowSet
。默认是 1G 或者或者 120S。
DiskRowSet
: 用于老数据的变更,后台定期对 DiskRowSet
做 Compaction
,以删除没用的数据及合并历史数据,减少查询过程中的 IO
开销。
BloomFile
:根据一个 DiskRowSet
中的 key
生成一个 bloom filter
,用于快速模糊定位某个 key
是否在DiskRowSet
中。
Ad_hocIndex
: 是主键的索引,用于定位 key
在 DiskRowSet
中的具体哪个偏移位置。
BaseData
是 MemRowSet flush
下来的数据,按列存储,按主键有序。
UndoFile
是基于 BaseData
之前时间的历史数据,通过在 BaseData
上 apply UndoFile
中的记录,可以获得历史数据。
RedoFile
是基于 BaseData
之后时间的变更记录,通过在 BaseData
上apply RedoFile
中的记录,可获得较新的数据。
DeltaMem
用于 DiskRowSet
中数据的变更,先写到内存中,写满后 flush
到磁盘形成RedoFile
。
REDO
与 UNDO
与关系型数据库中的 REDO
与 UNDO
日志类似(在关系型数据库中,REDO
日志记录了更新后的数据,可以用来恢复尚未写入 Data File
的已成功事务更新的数据。而 UNDO
日志用来记录事务更新之前的数据,可以用来在事务失败时进行回滚)
MemRowSets
可以对比理解成 HBase
中的 MemStore
,而 DiskRowSets
可理解成 HBase
中的 HFile
。MemRowSets
中的数据被 Flush
到磁盘之后,形成 DiskRowSets
。 DisRowSets
中的数据,按照32MB 大小为单位,按序划分为一个个的 DiskRowSet
。 DiskRowSet
中的数据按照 Column
进行组织,与 Parquet
类似。
这是 Kudu
可支持一些分析性查询的基础。每一个 Column
的数据被存储在一个相邻的数据区域,而这个数据区域进一步被细分成一个个的小的 Page
单元,与HBase File
中的Block
类似,对每一个Column Page
可采用一些 Encoding
算法,以及一些通用的 Compression
算法。 既然可对 Column Page
可采用 Encoding
以及 Compression
算法,那么,对单条记录的更改就会比较困难了。
前面提到了 Kudu
可支持单条记录级别的更新/删除,是如何做到的?
与 HBase
类似,也是通过增加一条新的记录来描述这次更新/删除操作的。DiskRowSet
是不可修改了,那么Kudu
要如何应对数据的更新呢?在Kudu
中,把 DiskRowSet
** 分为了两部分:base data
、delta stores
。base data
负责存储基础数据,delta stores
负责存储 base data
中的变更数据。
如上图所示,数据从 MemRowSet
刷到磁盘后就形成了一份 DiskRowSet
(只包含base data
),每份DiskRowSet
在内存中都会有一个对应的DeltaMemStore
,负责记录此 DiskRowSet
后续的数据变更(更新、删除)。DeltaMemStore
内部维护一个 B-树索引
,映射到每个row_offset
对应的数据变更。DeltaMemStore
数据增长到一定程度后转化成二进制文件存储到磁盘,形成一个 DeltaFile
,随着 base data
对应数据的不断变更,DeltaFile
逐渐增长。
7、Tablet
发现过程
当创建 Kudu
客户端时,其会从主服务器上获取tablet
位置信息,然后直接与服务于该tablet
的服务器进行交谈。
为了优化读取和写入路径,客户端将保留该信息的本地缓存,以防止他们在每个请求时需要查询主机的tablet
位置信息。随着时间的推移,客户端的缓存可能会变得过时,并且当写入被发送到不是tablet
领导者的 tablet
服务器时,则将被拒绝。然后客户端将通过查询主服务器发现新领导者的位置来更新其缓存。
8、Kudu
写流程
当 Client
请求写数据时,先根据主键从 Master Server
中获取要访问的目标 Tablets
,然后到依次对应的 Tablet
获取数据。
因为 Kudu
表存在主键约束,所以需要进行主键是否已经存在的判断,这里就涉及到之前说的索引结构对读写的优化了。一个 Tablet
中存在很多个 RowSets
,为了提升性能,我们要尽可能地减少要扫描的 RowSets
数量。
首先,我们先通过每个RowSet
中记录的主键的(最大最小)范围,过滤掉一批不存在目标主键的RowSets
,然后在根据 RowSet
中的布隆过滤器,过滤掉确定不存在目标主键的 RowSets
,最后再通过 RowSets
中的 B-树索引
,精确定位目标主键是否存在。
如果主键已经存在,则报错(主键重复),否则就进行写数据(写 MemRowSet
)。
9、Kudu
读流程
数据读取过程大致如下:先根据要扫描数据的主键范围,定位到目标的 Tablets
,然后读取 Tablets
中的 RowSets
。
在读取每个 RowSet
时,先根据主键过滤要 scan
范围,然后加载范围内的base data
,再找到对应的 delta stores
,应用所有变更,最后 union
上MemRowSet
中的内容,返回数据给 Client
。
10、Kudu
更新流程
数据更新的核心是定位到待更新数据的位置,这块与写入的时候类似,就不展开了,等定位到具体位置后,然后将变更写到对应的 delta store
中。