当前位置: 首页 > news >正文

Kudu vs HBase

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

Kudu vs HBase

网易云

网易云

 

已认证的官方帐号

9 人赞了该文章

本文由网易云 发布

 

背景


Cloudera在2016年发布了新型的分布式存储系统——kudu,kudu目前也是apache下面的开源项目。Hadoop生态圈中的技术繁多,HDFS作为底层数据存储的地位一直很牢固。而HBase作为Google BigTable的开源产品,一直也是Hadoop生态圈中的核心组件,其数据存储的底层采用了HDFS,主要解决的是在超大数据集场景下的随机读写和更新的问题。Kudu的设计有参考HBase的结构,也能够实现HBase擅长的快速的随机读写、更新功能。那么同为分布式存储系统,HBase和Kudu二者有何差异?两者的定位是否相同?我们通过分析HBase与Kudu整体结构和存储结构等方面对两者的差异进行比较。

整体结构Hbase的整体结构

v2-95c4f3f66b2e1ca50df5930185d58079_hd.jpg


HBase的主要组件包括Master,zookeeper服务,RegionServer,HDFS。
(1)Master:用来管理与监控所有的HRegionServer,也是管理HBase元数据的模块。
(2)zookeeper:作为分布式协调服务,用于保存meta表的位置,master的位置,存储RS当前的工作状态。
(3)RegionServer:负责维护Master分配的region,region对应着表中一段区间内的内容,直接接受客户端传来的读写请求。
(4)HDFS:负责最终将写入的数据持久化,并通过多副本复制实现数据的高可靠性。

Kudu的整体结构

v2-704be8c47924c3b7b0a4911e69dafeca_hd.jpg


Kudu的主要组件包括TServer和TMaster。

(1) TServer:负责管理Tablet,tablet是负责一张表中某块内容的读写,接收其他TServer中leader tablet传来的同步信息。
(2) TMaster:集群中的管理节点,用于管理tablet的基本信息,表的信息,并监听TServer的状态。多个TMaster之间通过Raft 协议实现数据同步和高可用。

主要区别
Kudu结构看上去跟HBase差别并不大,主要的区别包括:
(1)Kudu将HBase中zookeeper的功能放进了TMaster内,Kudu中TMaster的功能比HBase中的Master任务要多一些。
(2)Hbase将数据持久化这部分的功能交给了Hadoop中的HDFS,最终组织的数据存储在HDFS上。Kudu自己将存储模块集成在自己的结构中,内部的数据存储模块通过Raft协议来保证leader Tablet和replica Tablet内数据的强一致性,和数据的高可靠性。为什么不像HBase一样,利用HDFS来实现数据存储,笔者猜测可能是因为HDFS读小文件时的时延太大,所以Kudu自己重新完成了底层的数据存储模块,并将其集成在TServer中。

数据存储方式

HBase
HBase是一款Nosql数据库,典型的KV系统,没有固定的schema模式,建表时只需指定一个或多个列族名即可,一个列族下面可以增加任意个列限定名。一个列限定名代表了实际中的一列,HBase将同一个列族下面的所有列存储在一起,所以HBase是一种面向列族式的数据库。

v2-6de904a3f9c3bb7d5b89dff9780e762f_hd.jpg


HBase将每个列族中的数据分别存储,一个列族中的每行数据中,将rowkey\列族名、列名、timestamp组成最终存取的key值, 另 外 为 了 支 持 修 改 , 删 除 ,增 加 了 一 个 表 征 该 行 数 据 是 否 删 除 的 标 记 。 在 同 一 个 列 族 中 的 所 有 数 据 , 按照rowkey:columnfamily:columnQulifier:timestamp组成的key值大小进行升序排列,其中 rowkey 、 columnfamily 、columnQulifier 采用的是字典顺序,其值越大,Key越大,而timestamp是值越大,Key越小。HBase通过按照列族分开存储,相对于行式存储能够实现更高的压缩比,这也是其比较重要的一个特性。
HBase对一行数据进行更新时,HBase也是相当于插入一行新数据,在读数据时HBase按照timestamp的大小得到经过更新过的最新数据。

v2-bcee86f9855c6622af7e093ed999518f_hd.jpg


Kudu
Kudu是一种完全的列式存储引擎,表中的每一列数据都是存放在一起,列与列之间都是分开的。

v2-42b3a5ce0d4b79ea0dbefc55cc7e4b01_hd.jpg

 

为了能够保存一部分历史数据,并实现MVCC,Kudu将数据分为三个部分。一个部分叫做base data,是当前的数据;第二个部分叫做UNDO records,存储的是从插入数据时到形成base data所进行的所有修改操作,修改操作以一定形式进行组织,实现快速查看历史数据;第三个部分是REDO records,存储的是还未merge到当前数据中的更新操作。下图中表示的是在Kudu中插入一条数据、更新数据两个操作的做法,当然做法不唯一,不唯一的原因是Kudu可以选择先不将更新操作合并到base data中。

v2-78c6d643faaa285ecab852cf2700d0f8_hd.jpg


差异分析
(1)HBase是面向列族式的存储,每个列族都是分别存放的,HBase表设计时,很少使用设计多个列族,大多情况下是一个列族。这个时候的HBase的存储结构已经与行式存储无太大差别了。而Kudu,实现的是一个真正的面向列的存储方式,表中的每一列都是单独存放的;所以HBase与Kudu的差异主要在于类似于行式存储的列族式存储方式与典型的面向列式的存储方式的差异;
(2) HBase是一款NoSQL类型的数据库,对表的设计主要在于rowkey与列族的设计,列的类型可以不指定,因为HBase在实际存储中都会将所有的value字段转换成二进制的字节流。因为不需要指定类型,所以在插入数据的时候可以任意指定列名(列限定名),这样相当于可以在建表之后动态改变表的结构。Kudu因为选择了列式存储,为了更好的提高列式存储的效果,Kudu要求在建表时指定每一列的类型,这样的做法是为了根据每一列的类型设置合适的编码方式,实现更高的数据压缩比,进而降低数据读入时的IO压力;
(3) HBase对每一个cell数据中加入了timestamp字段,这样能够实现记录同一rowkey和列名的多版本数据,另外HBase将数据更新操作、删除操作也是作为一条数据写入,通过timestamp来标记更新时间,type来区分数据是插入、更新还是删除。HBase写入或者更新数据时可以指定timestamp,这样的设置可以完成某些特定的操作;

(4) 相对于HBase允许多版本的数据存在,Kudu为了提高批量读取数据时的效率,要求设计表时提供一列或者多列组成一个主键,主键唯一,不允许多个相同主键的数据存在。这样的设置下,Kudu不能像HBase一样将更新操作直接转换成插入一条新版本的数据,Kudu的选择是将写入的数据,更新操作分开存储;
(5)当然还有一些其他的行式存储与列式存储之间在不同应用场景下的性能差异。

HBase
HBase作为一种非常典型的LSM结构的分布式存储系统,是Google bigtable的apache开源版本。经过近10年的发展,HBase 已经成为了一个成熟的项目,在处理OLTP型的应用如消息日志,历史订单等应用较适用。在HBase中真正接受客户端读写请求的RegionServer的结构如下图所示:

v2-2c34f4dbed4ef737aa1879f9e73eae78_hd.jpg

 

关于HBase的几个关键点:
(1)在HBase中,充当写入缓存的这个结构叫做Memstore,另外会将写入操作顺序写入HLOG(WAL)中以保证数据不丢失;
(2)为了提高读的性能,HBase在内存中设置了blockcache,blockcache采用LRU策略将最近使用的数据块放在内存中;
(3)作为分布式存储系统,为保证数据不因为集群中机器出现故障而导致数据丢失,HBase将实际数据存放在HDFS上,包括storefile与HLOG。HBase与HDFS低耦合,HBase作为HDFS的客户端,向HDFS读写数据。
1. HBase写过程
(1)客户端通过客户端上保存的RS信息缓存或者通过访问zk得到需要读写的region所在的RS信息;
(2)RS接受客户端写入请求,先将写入的操作写入WAL,然后写入Memstore,这时HBase向客户端确认写入成功;
(3)HBase在一定情况下将Memstore中的数据flush成storefile(可能是Memstore大小达到一定阈值或者region占用的内存超过一定阈值或者手动flush之类的),storefile以HFile的形式存放在HDFS上;
(4)HBase会按照一定的合并策略对HDFS上的storefile进行合并操作,减少storefile的数量。
2. Hbase读过程
HBase读数据的过程比较麻烦,原因包括:
(1)HBase采用了LSM-tree的多组件算法作为数据组织方式,这种算法会导致一个region中有多个storefile;
(2)HBase中采用了非原地更新的方式,将更新操作和删除操作转换成插入一条新数据的形式,虽然这样能够较快的实现更新与删除,但是将导致满足指定rowkey,列族、列名要求的数据有多个,并且可能分布在不同的storefile中;
(3)HBase中允许设置插入和删除数据行的timestamp属性,这样导致按顺序落盘的storefile内数据的timestamp可能不是递增的。

下面介绍从HBase中读取一条指定(rowkey,column family,column)的记录:
(1)读过程与HBase客户端写过程第一步一样,先尝试获取需要读的region所在的RS相关信息;
( 2 ) RS 接收读请求, 因为HBase中支持多版本数据( 允许存在rowkey、列族名、列名相同的数据, 不同版本的数据通过
timestamp进行区分),另外更新与删除数据都是通过插入一条新数据实现的。所以要准确的读到数据,需要找到所有可能存储有该条数据的位置,包括在内存中未flush的memstore,已经flush到HDFS上的storefile,所以需要在1 memstore +N storefile中查找;
(3)在找到的所有数据中通过判断timestamp值得到最终的数据。

Kudu

v2-b118be6182f9ceddbdc31a5d48227dcf_hd.jpg



(1)Kudu中的Tablet是负责表中一块内容的读写工作,Tablet由一个或多个Rowset组成。其中有一个Rowset处于内存中,叫做Memrowset,Memrowset主要负责处理新的数据写入请求。DiskRowSet是MemRowset达到一定程序刷入磁盘后生成的,实质上是由一个CFile(Base Data)、多个DeltaFile(UNDO records &REDO records)和位于内存的DeltaMemStore组成。Base data、UNDO records、和REDO records都是不可修改的,DeltaMemStore达到一定大小后会将数据刷入磁盘生成新的REDO records。Kudu后台会有一个类似HBase的compaction线程按照一定的compaction 策略对tablet进行合并处理:

a. 将多个DeltaFile(REDO records)合并成一个大的DeltaFile;
b. 将多个REDO reccords文件与Base data进行合并,并生成新的 UNDO records;
c. 将多个DiskRowset之间进行合并,减少DiskRowset的数量。

(2)Kudu将最终的数据存储在本地磁盘上,为了保证数据可靠性,Kudu为一个tablet设置了多个副本(一般为3或5个)。所以一个tablet会由多个TServer负责维护,其中有个副本称为leader tablet,写入的请求只能通过leader tablet来处理,副本之间通过Raft协议保证其他副本与leader tablet的强一致性。

1. Kudu写过程 
Kudu与HBase不同,Kudu将写入操作分为两种,一种是插入一条新数据,一种是对一条已插入数据的更新。Kudu插入一条新数据:

(1)客户端连接TMaster获取表的相关信息,包括分区信息,表中所有tablet的信息;

(2)客户端找到负责处理读写请求的tablet所负责维护的TServer。Kudu接受客户端的请求,检查请求是否符合要求(表结构);

(3) Kudu在Tablet中的所有rowset(memrowset,diskrowset)中进行查找,看是否存在与待插入数据相同主键的数据,如果存在就返回错误,否则继续;

(4) Kudu在MemRowset中写入一行新数据,在MemRowset数据达到一定大小时,MemRowset将数据落盘,并生成一个diskrowset用于持久化数据,还生成一个memrowset继续接收新数据的请求。

Kudu对原有数据的更新

(1)客户端连接TMaster获取表的相关信息,包括分区信息,表中所有tablet的信息;

(2)Kudu接受请求,检查请求是否符合要求;

(3)因为待更新数据可能位于memrowset中,也可能已经flush到磁盘上,形成diskrowset。因此根据待更新数据所处位置不同,kudu有不同的做法:

a. 当待更新数据位于memrowset 时, 找到待更新数据所在行, 然后将更新操作记录在所在行中一个mutation链表中;在memrowset将数据落盘时,Kudu会将更新合并到base data,并生成UNDO records用于查看历史版本的数据和MVCC,UNDO records实际上也是以DeltaFile的形式存放;

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

实际上Kudu提交更新时会使用Raft协议将更新同步到其他replica上去,当然如果在memrowset和diskrowset中都没有找到这条数据,那么返回错误给客户端;另外当DiskRowset中的deltafile太多时,Kudu会采用一定的策略对一组deltafile进行合并。

2. Kudu读过程

(1)客户端连接TMaster获取表的相关信息,包括分区信息,表中所有tablet的信息;

(2) 客户端找到需要读取的数据的tablet所在的TServer,Kudu接受读请求,并记录timestamp信息,如果没有显式指定,那么表示使用当前时间;

(3) Kudu找到待读数据的所有相关信息, 当目标数据处于memrowset时, 根据读取操作中包含的timestamp 信息将该 timestamp前提交的更新操作合并到base data中,这个更新操作记录在该行数据对应的mutation链表中;

(4) 当读取的目标数据位于diskrowset中,在所有DeltaFile中找到所有目标数据相关的UNDO record和REDO records,REDO records可能位于多个DeltaFile中,根据读操作中包含的timestamp信息判断是否需要将base data进行回滚或者利用REDO records将base data进行合并更新。

1.写过程

(1)HBase写的时候,不管是新插入一条数据还是更新数据,都当作插入一条新数据来进行;而Kudu将插入新数据与更新操作分 别看待;

(2)Kudu表结构中必须设置一个唯一键,插入数据的时候必须判断一些该数据的主键是否唯一,所以插入的时候其实有一个读的 过程;而HBase没有太多限制,待插入数据将直接写进memstore;

(3)HBase实现数据可靠性是通过将落盘的数据写入HDFS来实现,而Kudu是通过将数据写入和更新操作同步在其他副本上实现 数据可靠性。

结合以上几点,可以看出Kudu在写的性能上相对HBase有一定的劣势。

2. 读过程

(1)在HBase中,读取的数据可能有多个版本,所以需要结合多个storefile进行查询;Kudu数据只可能存在于一个DiskRowset或 者MemRowset中,但是因为可能存在还未合并进原数据的更新,所以Kudu也需要结合多个DeltaFile进行查询;

(2)HBase写入或者更新时可以指定timestamp,导致storefile之间timestamp范围的规律性降低,增加了实际查询storefile的数 量;Kudu不允许人为指定写入或者更新时的timestamp值,DeltaFile之间timestamp连续,可以更快的找到需要的DeltaFile;

(3)HBase通过timestamp值可以直接取出数据;而Kudu实现多版本是通过保留UNDO records(已经合并过的操作)和REDO records(未合并过的操作)完成的,在一些情况下Kudu需要将base data结合UNDO records进行回滚或者结合REDO records进 行合并然后才能得到真正所需要的数据。

结合以上三点可以得出,不管是HBase还是Kudu,在读取一条数据时都需要从多个文件中搜寻相关信息。相对于HBase,Kudu选 择将插入数据和更新操作分开,一条数据只可能存在于一个DiskRowset或者memRowset中,只需要搜寻到一个rowset中存在指 定数据就不用继续往下找了,用户不能设置更新和插入时的timestamp值,减少了在rowset中DeltaFile的读取数量。这样在scan 的情况下可以结合列式存储的优点实现较高的读性能,特别是在更新数量较少的情况下能够有效提高scan性能。

另外,本文在描述HBase读写过程中没有考虑读写中使用的优化技术如Bloomfilter、timestamp range等。其实Kudu中也有使用 类似的优化技术来提高读写性能,本文只是简单的分析,因此就不再详细讨论读写过程。如有需要了解HBase的详细读写过程,

3. 其它差异
HBase:使用的java,内存的释放通过GC来完成,在内存比较紧张时可能引发full GC进而导致服务不稳定;
Kudu:核心模块用的C++来实现,没有full gc的风险。
 

总 结

本文主要简单介绍了一下Kudu,并在整体结构、数据存储结构和读写过程等方面上对HBase和Kudu这两款分布式存储系统进行大 体上的比较。Kudu通过要求完整的表结构设置,主键的设定,以列式存储作为数据在磁盘上的组织方式,更新和数据分开等技巧, 使得Kudu能够实现像HBase一样实现数据的随机读写之外,在HBase不太擅长的批量数据扫描(scan)具有较好的性能。而批量 读数据正是olap型应用所关注的重点,正如Kudu官网主页上描述的,Kudu实现的是既可以实现数据的快速插入与实时更新,也可以实现数据的快速分析。Kudu的定位不是取代HBase,而是以降低写的性能为代价,提高了批量读的性能,使其能够实现快速在线分析。

## == 是什么 ==

 

Kudu是Todd Lipcon@Cloudera带头开发的存储系统,其整体应用模式和HBase比较接近,即支持行级别的随机读写,并支持批量顺序检索功能。

 

那既然有了HBase,为什么还需要Kudu呢,简单的说,就是嫌弃HBase在OLAP场合,SQL/MR类的批量检索场景中,性能不够好。通常这种海量数据OLAP场景,要不走预处理的路,比如像EBAY麒麟这样走Cube管理的,或者像谷歌Mesa这样按业务需求走预定义聚合操作。再有就是自己构建数据通道,串接实时和批量处理两种系统,发挥各自的特长。

 

但是OLAP是个复杂的问题,场景众多,必然不可能有完美的通用解决方案,Kudu定位于应对快速变化数据的快速分析型数据仓库,希望靠系统自身能力,支撑起同时需要高吞吐率的顺序和随机读写的应用场景(可能的场景,比如时间序列数据分析,日志数据实时监控分析),提供一个介于HDFS和HBase的性能特点之间的一个系统,在随机读写和批量扫描之间找到一个平衡点,并保障稳定可预测的响应延迟

 

那为什么不能想办法改进HBase呢?Todd自己做为HBase的重要贡献者之一,没有选择这条路,自然是因为任何系统设计时都有Tradeoff,基于HBase的设计思想很难实现Kudu所定位的目标

 

相关链接:

 

  • http://getkudu.io/kudu.pdf
  • http://getkudu.io/

 

## == 核心思想 ==

 

### 数据模型:

 

数据模型定义上,Kudu管理的是类似关系型数据库的结构化的表,表结构由类Sql的Schema进行定义,相比于HBase这样的NoSql类型的数据库,Kudu的行数据是由固定个数有明确类型定义的列组成,并且需要定义一个由一个或多个列组成的主键来对每行数据进行唯一索引,相比于传统的关系型数据库,kudu在索引上有更多的限制,比如暂时不支持二级索引,不支持主键的更新等等。

 

尽管表结构类似于关系型数据库,但是Kudu自身并不提供SQL类型的语法接口,而是由上层其他系统实现,比如目前通过Impala提供SQL语法支持。

 

Kudu底层API,主要面对简单的更新检索操作,Insert/Update/Delete等必须指定一个主键进行,而Scan检索类型的操作则支持条件过滤和投影等能力。

 

### 集群架构:

 

Kudu的集群架构基本和HBase类似,采用主从结构,Master节点管理元数据,Tablet节点负责分片管理数据,

 

和HBase不同的是,Kudu没有借助于HDFS存储实际数据,而是自己直接在本地磁盘上管理分片数据,包括数据的Replication机制,kudu的Tablet server直接管理Master分片和Slave分片,自己通过raft协议解决一致性问题等,多个Slave可以同时提供数据读取服务,相对于HBase依托HDFS进行Region数据的管理方式,自主性会强一些,不过比如Tablet节点崩溃,数据的迁移拷贝工作等,也需要Kudu自己完成。

 

### 存储结构:

 

因为数据是有严格Schema类型定义,所以Kudu底层可以使用列式存储的方案来提高存储和投影检索效率(不过,设计kudu时,因果关系我估计是倒过来的,先决定要使用列式存储,再决定需要schema)

 

和HBase一样,Kudu也是通过Tablet的分区来支持水平扩展,与HBase不同的是,Kudu的分区策略除了支持按照Key Range来分区以外,还支持Hash based的策略,实际上,在主键上,Kudu可以混合使用这两种不同的策略

 

Hash分区的策略在一些场合下可以更好的做到负载均衡,避免数据倾斜,但是它最大的问题就是分区数一旦确定就很难再调整,所以目前Kudu的分区数必须预先指定(对Range的分区策略也有这个要求,估计是先简单化统一处理),不支持动态分区分裂,合并等,因此表的分区一开始就需要根据负载和容量预先进行合理规划。

 

在处理随机写的效率问题方面,Kudu的基本流程和HBase的方案差不多,在内存中对每个Tablet分区维护一个MemRowSet来管理最新更新的数据,当尺寸超过一定大小后Flush到磁盘上形成DiskRowSet,多个DiskRowSet在适当的时候进行归并处理

 

和HBase采用的LSM(LogStructured Merge)方案不同的是,Kudu对同一行的数据更新记录的合并工作,不是在查询的时候发生的(HBase会将多条更新记录先后Flush到不同的Storefile中,所以读取时需要扫描多个文件,比较rowkey,比较版本等),而是在更新的时候进行,在Kudu中一行数据只会存在于一个DiskRowSet中,避免读操作时的比较合并工作。那Kudu是怎么做到的呢? 对于列式存储的数据文件,要原地变更一行数据是很困难的,所以在Kudu中,对于Flush到磁盘上的DiskRowSet(DRS)数据,实际上是分两种形式存在的,一种是Base的数据,按列式存储格式存在,一旦生成,就不再修改,另一种是Delta文件,存储Base数据中有变更的数据,一个Base文件可以对应多个Delta文件,这种方式意味着,插入数据时相比HBase,需要额外走一次检索流程来判定对应主键的数据是否已经存在。因此,Kudu是牺牲了写性能来换取读取性能的提升。

 

 

既然存在Delta数据,也就意味着数据查询时需要同时检索Base文件和Delta文件,这看起来和HBase的方案似乎又走到一起去了,不同的地方在于,Kudu的Delta文件与Base文件不同,不是按Key排序的,而是按被更新的行在Base文件中的位移来检索的,号称这样做,在定位Delta内容的时候,不需要进行字符串比较工作,因此能大大加快定位速度。但是无论如何,Delta文件的存在对检索速度的影响巨大。因此Delta文件的数量会需要控制,需要及时的和Base数据进行合并。由于Base文件是列式存储的,所以Delta文件合并时,可以有选择性的进行,比如只把变化频繁的列进行合并,变化很少的列保留在Delta文件中暂不合并,这样做也能减少不必要的IO开销。

 

除了Delta文件合并,DRS自身也会需要合并,为了保障检索延迟的可预测性(这一点是HBase的痛点之一,比如分区发生Major Compaction时,读写性能会受到很大影响),Kudu的compaction策略和HBase相比,有很大不同,kudu的DRS数据文件的compaction,本质上不是为了减少文件数量,实际上Kudu DRS默认是以32MB为单位进行拆分的,DRS的compaction并不减少文件数量,而是对内容进行排序重组,减少不同DRS之间key的overlap,进而在检索的时候减少需要参与检索的DRS的数量。

 

 

以32MB这样小的单位进行拆分,也是为了能够以有限的资源快速的完成compaction的任务,及时根据系统负载调整Compaction行为,而不至于像HBase一样,Major Compaction动作成为导致性能不稳定的一个重要因素。所以对于Kudu来说,IO操作可以是一个持续平缓的过程,这点对响应的可预测性至关重要。

 

### 其它

 

Kudu底层核心代码使用C++开发,对外提供Java API接口,没有使用Java开发核心代码,也许有部分原因是希望通过自己管理内存,更好的适应和利用当前服务器上普遍越来越大的内存空间(256G+),另外也便于在关键逻辑中更好的优化代码。

 

## == 小结 ==

 

总体来说,个人感觉,Kudu本质上是将性能的优化,寄托在以列式存储为核心的基础上,希望通过提高存储效率,加快字段投影过滤效率,降低查询时CPU开销等来提升性能。而其他绝大多数设计,都是为了解决在列式存储的基础上支持随机读写这样一个目的而存在的。比如类Sql的元数据结构,是提高列式存储效率的一个辅助手段,唯一主键的设定也是配合列式存储引入的定制策略,至于其他如Delta存储,compaction策略等都是在这个设定下为了支持随机读写,降低latency不确定性等引入的一些Tradeoff方案

 

官方测试结果上,如果是存粹的随机读写,或者单行的检索请求这类场景,由于这些Tradeoff的存在,HBASE的性能吞吐率是要优于Kudu不少的(2倍到4倍),kudu的优势还是在支持类SQL检索这样经常需要进行投影操作的批量顺序检索分析场合。

 

目前kudu还处在Incubator阶段,并且还没有成熟的线上应用(小米走在了前面,做了一些业务应用的尝试),在数据安全,备份,系统健壮性等方面也还要打个问号,所以是否使用kudu,什么场合,什么时间点使用,是个需要好好考量的问题 ;)

## == 是什么 ==

 

Kudu是Todd Lipcon@Cloudera带头开发的存储系统,其整体应用模式和HBase比较接近,即支持行级别的随机读写,并支持批量顺序检索功能。

 

那既然有了HBase,为什么还需要Kudu呢,简单的说,就是嫌弃HBase在OLAP场合,SQL/MR类的批量检索场景中,性能不够好。通常这种海量数据OLAP场景,要不走预处理的路,比如像EBAY麒麟这样走Cube管理的,或者像谷歌Mesa这样按业务需求走预定义聚合操作。再有就是自己构建数据通道,串接实时和批量处理两种系统,发挥各自的特长。

 

但是OLAP是个复杂的问题,场景众多,必然不可能有完美的通用解决方案,Kudu定位于应对快速变化数据的快速分析型数据仓库,希望靠系统自身能力,支撑起同时需要高吞吐率的顺序和随机读写的应用场景(可能的场景,比如时间序列数据分析,日志数据实时监控分析),提供一个介于HDFS和HBase的性能特点之间的一个系统,在随机读写和批量扫描之间找到一个平衡点,并保障稳定可预测的响应延迟

 

那为什么不能想办法改进HBase呢?Todd自己做为HBase的重要贡献者之一,没有选择这条路,自然是因为任何系统设计时都有Tradeoff,基于HBase的设计思想很难实现Kudu所定位的目标

 

相关链接:

 

  • http://getkudu.io/kudu.pdf
  • http://getkudu.io/

 

## == 核心思想 ==

 

### 数据模型:

 

数据模型定义上,Kudu管理的是类似关系型数据库的结构化的表,表结构由类Sql的Schema进行定义,相比于HBase这样的NoSql类型的数据库,Kudu的行数据是由固定个数有明确类型定义的列组成,并且需要定义一个由一个或多个列组成的主键来对每行数据进行唯一索引,相比于传统的关系型数据库,kudu在索引上有更多的限制,比如暂时不支持二级索引,不支持主键的更新等等。

 

尽管表结构类似于关系型数据库,但是Kudu自身并不提供SQL类型的语法接口,而是由上层其他系统实现,比如目前通过Impala提供SQL语法支持。

 

Kudu底层API,主要面对简单的更新检索操作,Insert/Update/Delete等必须指定一个主键进行,而Scan检索类型的操作则支持条件过滤和投影等能力。

 

### 集群架构:

 

Kudu的集群架构基本和HBase类似,采用主从结构,Master节点管理元数据,Tablet节点负责分片管理数据,

 

和HBase不同的是,Kudu没有借助于HDFS存储实际数据,而是自己直接在本地磁盘上管理分片数据,包括数据的Replication机制,kudu的Tablet server直接管理Master分片和Slave分片,自己通过raft协议解决一致性问题等,多个Slave可以同时提供数据读取服务,相对于HBase依托HDFS进行Region数据的管理方式,自主性会强一些,不过比如Tablet节点崩溃,数据的迁移拷贝工作等,也需要Kudu自己完成。

 

### 存储结构:

 

因为数据是有严格Schema类型定义,所以Kudu底层可以使用列式存储的方案来提高存储和投影检索效率(不过,设计kudu时,因果关系我估计是倒过来的,先决定要使用列式存储,再决定需要schema)

 

和HBase一样,Kudu也是通过Tablet的分区来支持水平扩展,与HBase不同的是,Kudu的分区策略除了支持按照Key Range来分区以外,还支持Hash based的策略,实际上,在主键上,Kudu可以混合使用这两种不同的策略

 

Hash分区的策略在一些场合下可以更好的做到负载均衡,避免数据倾斜,但是它最大的问题就是分区数一旦确定就很难再调整,所以目前Kudu的分区数必须预先指定(对Range的分区策略也有这个要求,估计是先简单化统一处理),不支持动态分区分裂,合并等,因此表的分区一开始就需要根据负载和容量预先进行合理规划。

 

在处理随机写的效率问题方面,Kudu的基本流程和HBase的方案差不多,在内存中对每个Tablet分区维护一个MemRowSet来管理最新更新的数据,当尺寸超过一定大小后Flush到磁盘上形成DiskRowSet,多个DiskRowSet在适当的时候进行归并处理

 

和HBase采用的LSM(LogStructured Merge)方案不同的是,Kudu对同一行的数据更新记录的合并工作,不是在查询的时候发生的(HBase会将多条更新记录先后Flush到不同的Storefile中,所以读取时需要扫描多个文件,比较rowkey,比较版本等),而是在更新的时候进行,在Kudu中一行数据只会存在于一个DiskRowSet中,避免读操作时的比较合并工作。那Kudu是怎么做到的呢? 对于列式存储的数据文件,要原地变更一行数据是很困难的,所以在Kudu中,对于Flush到磁盘上的DiskRowSet(DRS)数据,实际上是分两种形式存在的,一种是Base的数据,按列式存储格式存在,一旦生成,就不再修改,另一种是Delta文件,存储Base数据中有变更的数据,一个Base文件可以对应多个Delta文件,这种方式意味着,插入数据时相比HBase,需要额外走一次检索流程来判定对应主键的数据是否已经存在。因此,Kudu是牺牲了写性能来换取读取性能的提升。

 

 

既然存在Delta数据,也就意味着数据查询时需要同时检索Base文件和Delta文件,这看起来和HBase的方案似乎又走到一起去了,不同的地方在于,Kudu的Delta文件与Base文件不同,不是按Key排序的,而是按被更新的行在Base文件中的位移来检索的,号称这样做,在定位Delta内容的时候,不需要进行字符串比较工作,因此能大大加快定位速度。但是无论如何,Delta文件的存在对检索速度的影响巨大。因此Delta文件的数量会需要控制,需要及时的和Base数据进行合并。由于Base文件是列式存储的,所以Delta文件合并时,可以有选择性的进行,比如只把变化频繁的列进行合并,变化很少的列保留在Delta文件中暂不合并,这样做也能减少不必要的IO开销。

 

除了Delta文件合并,DRS自身也会需要合并,为了保障检索延迟的可预测性(这一点是HBase的痛点之一,比如分区发生Major Compaction时,读写性能会受到很大影响),Kudu的compaction策略和HBase相比,有很大不同,kudu的DRS数据文件的compaction,本质上不是为了减少文件数量,实际上Kudu DRS默认是以32MB为单位进行拆分的,DRS的compaction并不减少文件数量,而是对内容进行排序重组,减少不同DRS之间key的overlap,进而在检索的时候减少需要参与检索的DRS的数量。

 

 

以32MB这样小的单位进行拆分,也是为了能够以有限的资源快速的完成compaction的任务,及时根据系统负载调整Compaction行为,而不至于像HBase一样,Major Compaction动作成为导致性能不稳定的一个重要因素。所以对于Kudu来说,IO操作可以是一个持续平缓的过程,这点对响应的可预测性至关重要。

 

### 其它

 

Kudu底层核心代码使用C++开发,对外提供Java API接口,没有使用Java开发核心代码,也许有部分原因是希望通过自己管理内存,更好的适应和利用当前服务器上普遍越来越大的内存空间(256G+),另外也便于在关键逻辑中更好的优化代码。

 

## == 小结 ==

 

总体来说,个人感觉,Kudu本质上是将性能的优化,寄托在以列式存储为核心的基础上,希望通过提高存储效率,加快字段投影过滤效率,降低查询时CPU开销等来提升性能。而其他绝大多数设计,都是为了解决在列式存储的基础上支持随机读写这样一个目的而存在的。比如类Sql的元数据结构,是提高列式存储效率的一个辅助手段,唯一主键的设定也是配合列式存储引入的定制策略,至于其他如Delta存储,compaction策略等都是在这个设定下为了支持随机读写,降低latency不确定性等引入的一些Tradeoff方案

 

官方测试结果上,如果是存粹的随机读写,或者单行的检索请求这类场景,由于这些Tradeoff的存在,HBASE的性能吞吐率是要优于Kudu不少的(2倍到4倍),kudu的优势还是在支持类SQL检索这样经常需要进行投影操作的批量顺序检索分析场合。

 

目前kudu还处在Incubator阶段,并且还没有成熟的线上应用(小米走在了前面,做了一些业务应用的尝试),在数据安全,备份,系统健壮性等方面也还要打个问号,所以是否使用kudu,什么场合,什么时间点使用,是个需要好好考量的问题 ;)

转载于:https://my.oschina.net/hblt147/blog/1843698

相关文章:

  • springboot系列(一) Spring Boot浅谈(转载)
  • UITableView 的头视图和分区视图
  • 进击的 JavaScript(四) 之 闭包
  • netty源码分析
  • Android开发 Firebase动态链接打开APP
  • js进阶 12-18 jquery如何实现自定义右键菜单(把问题分细)
  • 数据结构之串
  • Linux命令三剑客
  • 我的免费静态博客
  • 崛起于Springboot2.X之配置文件详解(10)
  • Dijkstra算法 C++
  • 使用 JSON JavaScriptSerializer 进行序列化或反序列化时出错
  • Oracle学习笔记——imp还原数据库
  • Nginx proxy pass路由转发简单用法
  • [译]D3.js 之 d3-selection 原理
  • 2019年如何成为全栈工程师?
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • CSS实用技巧干货
  • eclipse(luna)创建web工程
  • java正则表式的使用
  • KMP算法及优化
  • redis学习笔记(三):列表、集合、有序集合
  • VUE es6技巧写法(持续更新中~~~)
  • Vue 重置组件到初始状态
  • 给新手的新浪微博 SDK 集成教程【一】
  • 汉诺塔算法
  • 容器服务kubernetes弹性伸缩高级用法
  • 因为阿里,他们成了“杭漂”
  • # 达梦数据库知识点
  • # 数论-逆元
  • #{}和${}的区别?
  • $.ajax()方法详解
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (2022 CVPR) Unbiased Teacher v2
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (libusb) usb口自动刷新
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (二十四)Flask之flask-session组件
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (三)docker:Dockerfile构建容器运行jar包
  • (转)淘淘商城系列——使用Spring来管理Redis单机版和集群版
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • .Net Attribute详解(上)-Attribute本质以及一个简单示例
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .NET 回调、接口回调、 委托
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)
  • .netcore 如何获取系统中所有session_如何把百度推广中获取的线索(基木鱼,电话,百度商桥等)同步到企业微信或者企业CRM等企业营销系统中...
  • .Net各种迷惑命名解释
  • .net和php怎么连接,php和apache之间如何连接
  • .py文件应该怎样打开?
  • /etc/sudoers (root权限管理)
  • @autowired注解作用_Spring Boot进阶教程——注解大全(建议收藏!)
  • @test注解_Spring 自定义注解你了解过吗?
  • [1] 平面(Plane)图形的生成算法