对比方面 | HBase | Kudu |
---|---|---|
开发语言 | Java | Java、C++(核心) |
数据模型 | key-value系统,无模式 | 强类型的结构化表 |
软件架构 | 利用ZK进行Master选举,数据存储到HDFS实现容错 | 使用Raft协议实现高可用,底层数据存储使用Raft实现多副本 |
存储方式 | 列簇式存储 | 纯列式存储 |
数据分区 | 一致性哈希 | Hash或Range |
索引 | 支持 | 不支持 |
数据一致性 | 强一致 | Snashot和External Consisitency |
架构
HBase
- Master:管理与监控的RegionServer,管理HBase元数据;
- Zookeeper:分布式协调服务,存储Meta表位置、Master位置、RS当前工作状态;
- RegionServer:维护Master分配的Region(表一段区间的内容),接受客户端的读写请求;
- HDFS:提供多副本的数据存储(包括Storefile和HLOG),HBase作为HDFS的客户端,与HDFS低耦合;
- Java实现,内存的释放通过GC来完成,在内存比较紧张时可能引发full GC进而导致服务不稳定;
Kudu
- TServer:管理Tablet,接收其他TServer中Leader Tablet传来的同步消息;
- TMaster:管理Tablet的基本信息、表的基本信息,监听TServer状态,多个TMaster之间通过Raft协议实现数据同步、高可用;
- Tablet:负责表中某块内容的读写,类似Region;由一或多个Rowset组成;
- 相当于把HBase中ZK的功能放在TMaster,比HBase中Master的功能更重;
- 数据存储模块集成在自身结构(避免HDFS读取小文件时延太大的问题);
- 核心模块用的C++来实现,没有full gc的风险。
存储
HBase
- K-V型的NoSQL数据库,建表时指定一或多个列蔟,列蔟下有任意个列,相同列蔟的列存放在一起(面向列蔟存储),比行式存储有更高的压缩比;
- 每行数据包括:rowkey:columnFamily:columnQulifier:timestamp,其中columnQulifier为数据行是否删除的标记、TimeStamp区分多版本数据;
- 建表时不需要指定列和类型(value字段转换成二进制的字节流),相当于可以动态该表表结构;
- 每个cell存放多个版本的数据,更新删除通过插入一条新版本的数据实现;
- 表设计时一般会用单个列蔟,此时与行式存储无太大区别;
Kudu
- 完全的列式存储;
- 数据分三个部分:BaseData(当前数据),UndoRecords(历史修改数据),RedoRecords(未merge到当前数据的更新操作);
- 建表时要定义每列的类型(设置合适的编码方式),实现更高的数据压缩比,降低I/O压力;
- 主键唯一,不能把更新操作转换成插入一条新版本的数据。
读写过程
HBase
特点
- 写入缓存:Memstore,同时会将写入操作顺序写入HLOG(WAL)保证数据不丢失;
- 读出缓存:BlockCache,采用LRU策略;
- 采用了LSM-tree的多组件算法作为数据组织方式,一个Region存在多个Storefile;
- 采用了非原地更新的方式(插入新版本数据),能实现快速的更新和删除,导致满足指定rowkey,列族、列名要求的数据有多个,并且可能分布在不同的Storefile中;
- timestamp属性可以设置,按顺序写入Storefile的数据timestamp可能不是递增的;
- 可考虑读写中使用的优化技术如Bloomfilter、timestamp range;
写过程:
- 1、客户端通过缓存的RS信息或访问ZK取得Region所在的RS信息;
- 2、RS接受客户端写入请求,先将写入的操作写入WAL,然后写入Memstore,HBase向客户端确认写入成功;
- 一定情况下(Memstore达到一定阈值或region占用内存超过一定阈值或手动flush),HBase将Memstore中的数据flush成Storefile,存放到HDFS;
- HBase将Storefile根据一定策略合并。
读过程:
- 1、客户端通过缓存或访问ZK取得Region所在的RS信息;
- 2、RS 接收读请求,要准确的读到相应版本的数据,需要找到所有可能存储有该条数据的位置,包括在内存中未flush的Memstore,已经flush到HDFS上的storefile,所以需要在1 Memstore + N Storefile中查找;
- 3、在所有数据中通过timestamp中得到最终数据。
Kudu
特点
- Tablet负责表某块内容读写,由一或多个Rowset组成:
- MemRowset:处于内存的Rowset,负责新数据写入请求,只有一个;
- DiskRowSet:MemRowset达到一定程序刷入磁盘后生成,由一个CFile(Base Data)、多个DeltaFile(UNDO records & REDO records)和位于内存的DeltaMemStore(达到一定大小后会将数据刷入磁盘生成新的REDO records)组成。
- Kudu后台有一个类似HBase的compaction线程按照一定策略对tablet进行合并:
- 将多个DeltaFile(REDO records)合并成一个大的DeltaFile
- 将多个REDO reccords文件与Base data进行合并,并生成新的 UNDO records;
- 将多个DiskRowset之间进行合并,减少DiskRowset的数量。
- 最终数据存储在本地磁盘,每个tablet设置多个副本且由多个TServer负责维护,写入请求由Leader Tablet处理,副本之间通过Raft协议保证强一致性;
写过程(插入):
- 连接TMaster获取表相关信息(分区、Tablet);
- 找到负责读写请求Tablet的TServer,Kudu接收到请求后检查是否符合要求(表结构);
- Kudu在TServer的所有rowset中(内存、磁盘)根据主键查找,存在相同的返回错误;
- Kudu在MemRowset中写入一行新数据,如达到阈值则将数据写入磁盘(DiskRowSet),并生成一个memrowset继续接收新数据的请求。
写过程(更新):
- 前两步相同;
- 待更新数据可能位于MemRowset中,也可能位于DiskRowSet:
- 位于MemRowset:找到待更新数据所在行, 然后将更新操作记录在所在行中一个mutation链表中;在memrowset将数据落盘时,Kudu会将更新合并到base data,并生成UNDO records用于查看历史版本的数据和MVCC,UNDO records实际上也是以DeltaFile的形式存放;
- 位于DiskRowSet:找到待更新数据所在的DiskRowset , 每个DiskRowset 都会在内存中设置一个DeltaMemStore,将更新操作记录在DeltaMemStore中,在DeltaMemStore达到一定大小时,flush在磁盘,形成Delta并存在方DeltaFile中。
- Kudu提交更新时通过Raft协议将同步到其他replica,如没有在MemRowset和DiskRowSet中找到这条数据则返回错误,deltafile太多时会采用一定策略对一组deltafile合并。
读过程:
- 连接TMaster获取表相关信息(分区、Tablet);
- Client找到相应Tablet的Tserver,Kudu接收读请求并记录timestamp(默认当前时间);
- Kudu找到待读数据的所有相关信息:
- 位于MemRowset:根据读取操作中包含的timestamp 信息将该timestamp前提交的更新操作合并到base data中,这个更新操作记录在该行数据对应的mutation链表中;
- 位于DiskRowSet:在所有DeltaFile中找到所有目标数据相关的UNDO record和REDO records,REDO records可能位于多个DeltaFile中,根据读操作中包含的timestamp信息判断是否需要将base data进行回滚或者利用REDO records将base data进行合并更新。
特点:
HBase写的时候,不管是新插入一条数据还是更新数据,都当作插入一条新数据来进行;而Kudu将插入新数据与更新操作分别看待;
Kudu表结构中必须设置一个唯一键,插入数据的时候必须判断一些该数据的主键是否唯一,所以插入的时候其实有一个读的过程;而HBase没有太多限制,待插入数据将直接写进memstore;
HBase实现数据可靠性是通过将落盘的数据写入HDFS来实现,而Kudu是通过将数据写入和更新操作同步在其他副本上实现 数据可靠性。
在HBase中,读取的数据可能有多个版本,所以需要结合多个storefile进行查询;Kudu数据只可能存在于一个DiskRowset或 者MemRowset中,但是因为可能存在还未合并进原数据的更新,所以Kudu也需要结合多个DeltaFile进行查询;
HBase写入或者更新时可以指定timestamp,导致storefile之间timestamp范围的规律性降低,增加了实际查询storefile的数量;Kudu不允许人为指定写入或者更新时的timestamp值,DeltaFile之间timestamp连续,可以更快的找到需要的DeltaFile;
HBase通过timestamp值可以直接取出数据;而Kudu实现多版本是通过保留UNDO records(已经合并过的操作)和REDO records(未合并过的操作)完成,在一些情况下Kudu需要将base data结合UNDO records进行回滚或者结合REDO records进行合并然后才能得到真正所需要的数据。
HBase和Kudu在读取一条数据时都需要从多个文件中搜寻相关信息。Kudu将插入和更新操作分开,一条数据只可能存在于一个DiskRowset或者MemRowset中,只需要搜寻到一个rowset中存在指定数据就不用继续往下找了,用户不能设置更新和插入时的timestamp值,减少了在rowset中DeltaFile的读取数量。这样在scan情况下可以结合列式存储的优点实现较高的读性能,特别是在更新数量较少的情况下能够有效提高scan性能。