Kudu 是 Cloudera 开源的新型列式存储系统,是 Apache Hadoop 生态圈的新成员之一( incubating ),专门为了对快速变化的数据进行快速的分析,填补了以往 Hadoop 存储层的空缺。本文主要对 Kudu 的动机、背景,以及架构进行简单介绍。
背景——功能上的空白
Hadoop 生态系统有很多组件,每一个组件有不同的功能。在现实场景中,用户往往需要同时部署很多 Hadoop 工具来解决同一个问题,这种架构称为 混合架构 (hybrid architecture) 。 比如,用户需要利用 Hbase 的快速插入、快读 random access 的特性来导入数据, HBase 也允许用户对数据进行修改, HBase 对于大量小规模查询也非常迅速。同时,用户使用 HDFS/Parquet + Impala/Hive 来对超大的数据集进行查询分析,对于这类场景, Parquet 这种列式存储文件格式具有极大的优势。
很多公司都成功地部署了 HDFS/Parquet + HBase 混合架构,然而这种架构较为复杂,而且在维护上也十分困难。首先,用户用 Flume 或 Kafka 等数据 Ingest 工具将数据导入 HBase ,用户可能在 HBase 上对数据做一些修改。然后每隔一段时间 ( 每天或每周 ) 将数据从 Hbase 中导入到 Parquet 文件,作为一个新的 partition 放在 HDFS 上,最后使用 Impala 等计算引擎进行查询,生成最终报表。
这样一条工具链繁琐而复杂,而且还存在很多问题,比如:
- Ÿ 如何处理某一过程出现失败?
- Ÿ 从 HBase 将数据导出到文件,多久的频率比较合适?
- Ÿ 当生成最终报表时,最近的数据并无法体现在最终查询结果上。
- Ÿ 维护集群时,如何保证关键任务不失败?
- Ÿ Parquet 是 immutable ,因此当 HBase 中删改某些历史数据时,往往需要人工干预进行同步。
这时候,用户就希望能够有一种优雅的存储解决方案,来应付不同类型的工作流,并保持高性能的计算能力。 Cloudera 很早就意识到这个问题,在 2012 年就开始计划开发 Kudu 这个存储系统,终于在 2015 年发布并开源出来。 Kudu 是对 HDFS 和 HBase 功能上的补充,能提供快速的分析和实时计算能力,并且充分利用 CPU 和 I/O 资源,支持数据原地修改,支持简单的、可扩展的数据模型。
背景——新的硬件设备
RAM 的技术发展非常快,它变得越来越便宜,容量也越来越大。 Cloudera 的客户数据显示,他们的客户所部署的服务器, 2012 年每个节点仅有 32GB RAM ,现如今增长到每个节点有 128GB 或 256GB RAM 。存储设备上更新也非常快, 在很多普通服务器中部署 SSD 也是屡见不鲜。 HBase 、 HDFS 、以及其他的 Hadoop 工具都在不断自我完善,从而适应硬件上的升级换代。然而,从根本上, HDFS 基于 03 年 GFS , HBase 基于 05 年 BigTable ,在当时系统瓶颈主要取决于底层磁盘速度。当磁盘速度较慢时, CPU 利用率不足的根本原因是磁盘速度导致的瓶颈,当磁盘速度提高了之后, CPU 利用率提高,这时候 CPU 往往成为系统的瓶颈。 HBase 、 HDFS 由于年代久远,已经很难从基本架构上进行修改,而 Kudu 是基于全新的设计,因此可以更充分地利用 RAM 、 I/O 资源,并优化 CPU 利用率。我们可以理解为, Kudu 相比与以往的系统, CPU 使用降低了, I/O 的使用提高了, RAM 的利用更充分了。
简介
Kudu 设计之初,是为了解决一下问题:
- Ÿ 对数据扫描 (scan) 和随机访问 (random access) 同时具有高性能,简化用户复杂的混合架构
- Ÿ 高 CPU 效率,使用户购买的先进处理器的的花费得到最大回报
- Ÿ 高 IO 性能,充分利用先进存储介质
- Ÿ 支持数据的原地更新,避免额外的数据处理、数据移动
- Ÿ 支持跨数据中心 replication
Kudu 的很多特性跟 HBase 很像,它支持索引键的查询和修改。 Cloudera 曾经想过基于 Hbase 进行修改,然而结论是对 HBase 的改动非常大, Kudu 的数据模型和磁盘存储都与 Hbase 不同。 HBase 本身成功的适用于大量的其它场景,因此修改 HBase 很可能吃力不讨好。最后 Cloudera 决定开发一个全新的存储系统。
Kudu 的定位是提供 ”fast analytics on fast data” ,也就是在快速更新的数据上进行快速的查询。它定位 OLAP 和少量的 OLTP 工作流,如果有大量的 random accesses ,官方建议还是使用 HBase 最为合适。
架构与设计
1. 基本框架
Kudu 是用于存储结构化( structured )的表( Table )。表有预定义的带类型的列( Columns ),每张表有一个主键( primary key )。主键带有唯一性( uniqueness )限制,可作为索引用来支持快速的 random access 。
类似于 BigTable , Kudu 的表是由很多数据子集构成的,表被水平拆分成多个 Tablets. Kudu 用以每个 tablet 为一个单元来实现数据的 durability 。 Tablet 有多个副本,同时在多个节点上进行持久化。
Kudu 有两种类型的组件, Master Server 和 Tablet Server 。 Master 负责管理元数据。这些元数据包括 talbet 的基本信息,位置信息。 Master 还作为负载均衡服务器,监听 Tablet Server 的健康状态。对于副本数过低的 Tablet , Master 会在起 replication 任务来提高其副本数。 Master 的所有信息都在内存中 cache ,因此速度非常快。每次查询都在百毫秒级别。 Kudu 支持多个 Master ,不过只有一个 active Master ,其余只是作为灾备,不提供服务。
Tablet Server 上存了 10~100 个 Tablets ,每个 Tablet 有 3 (或 5 )个副本存放在不同的 Tablet Server 上,每个 Tablet 同时只有一个 leader 副本,这个副本对用户提供修改操作,然后将修改结果同步给 follower 。 Follower 只提供读服务,不提供修改服务。副本之间使用 raft 协议来实现 High Availability ,当 leader 所在的节点发生故障时, followers 会重新选举 leader 。根据官方的数据,其 MTTR 约为 5 秒,对 client 端几乎没有影响。 Raft 协议的另一个作用是实现 Consistency 。 Client 对 leader 的修改操作,需要同步到 N/2+1 个节点上,该操作才算成功。
Kudu 采用了类似 log-structured 存储系统的方式,增删改操作都放在内存中的 buffer ,然后才 merge 到持久化的列式存储中。 Kudu 还是用了 WALs 来对内存中的 buffer 进行灾备。
2. 列式存储
持久化的列式存储存储,与 HBase 完全不同,而是使用了类似 Parquet 的方式,同一个列在磁盘上是作为一个连续的块进行存放的。例如,图中左边是 twitter 保存推文的一张表,而图中的右边表示了表在磁盘中的的存储方式,也就是将同一个列放在一起存放。这样做的第一个好处是,对于一些聚合和 join 语句,我们可以尽可能地减少磁盘的访问。例如,我们要用户名为 newsycbot
的推文数量,使用查询语句:
SELECT COUNT(*) FROM tweets WHERE user_name = ‘newsycbot’;
我们只需要查询 User_name 这个 block 即可。同一个列的数据是集中的,而且是相同格式的, Kudu 可以对数据进行编码,例如字典编码,行长编码, bitshuffle 等。通过这种方式可以很大的减少数据在磁盘上的大小,提高吞吐率。除此之外,用户可以选择使用通用的压缩格式对数据进行压缩,如 LZ4, gzip, 或 bzip2 。这是可选的,用户可以根据业务场景,在数据大小和 CPU 效率上进行权衡。这一部分的实现上, Kudu 很大部分借鉴了 Parquet 的代码。
HBase 支持 snappy 存储,然而因为它的 LSM 的数据存储方式,使得它很难对数据进行特殊编码,这也是 Kudu 声称具有很快的 scan 速度的一个很重要的原因。不过,因为列式编码后的数据很难再进行修改,因此当这写数据写入磁盘后,是不可变的,这部分数据称之为 base 数据。 Kudu 用 MVCC (多版本并发控制)来实现数据的删改功能。更新、删除操作需要记录到特殊的数据结构里,保存在内存中的 DeltaMemStore 或磁盘上的 DeltaFIle 里面。 DeltaMemStore 是 B-Tree 实现的,因此速度快,而且可修改。磁盘上的 DeltaFIle 是二进制的列式的块,和 base 数据一样都是不可修改的。因此当数据频繁删改的时候,磁盘上会有大量的 DeltaFiles 文件, Kudu 借鉴了 Hbase 的方式,会定期对这些文件进行合并。
3. 对外接口
Kudu 提供 C++ 和 JAVA API ,可以进行单条或批量的数据读写, schema 的创建修改。除此之外, Kudu 还将与 hadoop 生态圈的其它工具进行整合。目前, kudu beta 版本对 Impala 支持较为完善,支持用 Impala 进行创建表、删改数据等大部分操作。 Kudu 还实现了 KuduTableInputFormat 和 KuduTableOutputFormat ,从而支持 Mapreduce 的读写操作。同时支持数据的 locality 。目前对 spark 的支持还不够完善, spark 只能进行数据的读操作。
使用案例——小米
小米是 Hbase 的重度用户,他们每天有约 50 亿条用户记录。小米目前使用的也是 HDFS + HBase 这样的混合架构。可见该流水线相对比较复杂,其数据存储分为 SequenceFile , Hbase 和 Parquet 。
在使用 Kudu 以后, Kudu 作为统一的数据仓库,可以同时支持离线分析和实时交互分析。
性能测试
1. 和 parquet 的比较
图是官方给出的用 Impala 跑 TPC-H 的测试,对比 Parquet 和 Kudu 的计算速度。从图中我们可以发现, Kudu 的速度和 parquet 的速度差距不大,甚至有些 Query 比 parquet 还快。然而,由于这些数据都是在内存缓存过的,因此该测试结果不具备参考价值。
2. 和 Hbase 的比较
图是官方给出的另一组测试结果,从图中我们可以看出,在 scan 和 range 查询上, kudu 和 parquet 比 HBase 快很多,而 random access 则比 HBase 稍慢。然而数据集只有 60 亿行数据,所以很可能这些数据也是可以全部缓存在内存的。对于从内存查询,除了 random access 比 HBase 慢之外, kudu 的速度基本要优于 HBase 。
3. 超大数据集的查询性能
Kudu 的定位不是 in-memory database 。因为它希望 HDFS/Parquet 这种存储,因此大量的数据都是存储在磁盘上。如果我们想要拿它代替 HDFS/Parquet + HBase ,那么超大数据集的查询性能就至关重要,这也是 Kudu 的最初目的。然而,官方没有给出这方面的相关数据。由于条件限制,网易暂时未能完成该测试。下一步,我们将计划搭建 10 台 Kudu + Impala 服务器,并用 tpc-ds 生成超大数据,来完成该对比测验。