Hadoop系列五——HBase简介

本文是对《 Hadoop系列四——HBase简介 》一文的补充,不过本文不会进行系统性介绍,只是针对一个个独立的点介绍,并且会不断更新,有点类似于FAQ吧。

1. HBase的架构图

简易版架构图:

Hadoop系列五——HBase简介

复杂版架构图:

Hadoop系列五——HBase简介

架构讲解见《 Hadoop系列四——HBase简介 》。

2. HBase的数据模型

在《 Hadoop系列四——HBase简介 》里面已经介绍过HBase的数据模型了,但这个的确非常重要,这里再做一些补充。HBase是根据Google的Bigtable论文实现的开源版"Bigtable",所以对Bigtable的描述同样适用于HBase:

A Bigtable is a sparse,distributed, persistentmulti-dimensional sorted map. The map is indexed by a row key, column key, and a timestamp; each value in the map is anuninterpreted array of bytes.

这里有几个关键词我已经加粗了,从这几个关键词可以引出来多个问题。

2.1 HBase里面的"NULL值"处理

第一个就是 sparse关键字。首先NULL值是RDBMS里面的概念,HBase里面其实没有这个概念,这里只是为了作对比,介绍稀疏性这个特性。我们把HBase想成一个二维矩阵(这样不准确,但有助于理解),那么其实就是一个稀疏矩阵。在RDBMS里面,没有值的地方一般用NULL表示,虽然相比于其他类型,NULL占很小的空间(一般是1bit),但仍然是占空间的,在RDBMS里面这没有问题,主要有两个原因:(1)RDBMS里面数据量不会很大,目前RDBMS能承载的最大数据量一般也就是百万级别的。(2)如果把RDBMS也看成二维矩阵的话,一般RDBMS都属于稠密矩阵,所以NULL值不会占大多数。

但HBase不一样,它正好与RDBMS的特性相反:数据量很大,一般都大于百万级别(如果小于这个量级,那你该重新考虑是否有必要使用HBase了);NULL值可能占很大部分,这个和HBase的设计使用有关系。比如一个表里面有1百万个列,有1千万行数据,每一行里面可能只有一小部分列有值,其他列都是“NULL值”(实质是没有值)。所以, 在HBase里面,"NULL值"是不存储的,即不占用任何存储。其实这个和稀疏矩阵的存储原理上是一样的,只存储有数据的cell的“坐标”及值,其它的默认就是NULL。当我们在HBase里面获取一行数据的时候,只会返回有数据的列,不像RDBMS会返回所有列,没有值的用NULL占位。

更多信息见本文“ HBase的逻辑存储和物理存储”部分。

2.2 分布式

想想如果因为性能或存储等不够了,需要将RDBMS扩展成分布式的,能否做到?也许可以做到,但一般是受限的,而且难度比较高,比如像业内的GreenPlum,如果还想要保证RDBMS的所有特性,那就只能用更好的硬件做垂直扩展了。但HBase本身就是分布式的,扩展一个HBase节点基本上没有什么难度。所以这也是很多企业选择HBase的一个非常重要的考量。

2.3 多维有序map

多维的概念在我的《 Hadoop系列四——HBase简介 》里面已经讲过了。

有序之前也提过,在HBase里面,不论是内部存储,还是查询返回的数据,都是有序的: 依次按照row key、Column Family、Column qualifier、Timestamp四个维度排序,时间戳是按照从新到旧排的,其它都是字典序。

2.4 HBase里面的数据是否有类型

上面对于value的描述是“ uninterpreted array of bytes”,也就是没有类型,默认按字节流解析(我们知道任何类型其实都可以按字节流其解析)。其实在HBase里面除了会被作为路径的名称之外,其它都是作为字节流的,也就是没有类型。具体就是:表名、Column Family会作为路径名,所以名称必须是合法的路径名,也就是必须是可打印字符;row key,Column qualifier、Cell都是没有类型的,也就是可以是任何数据。实际中,我们经常会在row key、Column qualifier中存储数据,而不光是cell里面。

3. HBase的逻辑存储和物理存储

引用

HBase:The Definitive Guide上的一幅图:

Hadoop系列五——HBase简介

第一幅图(左上角)是HBase里面一个典型的表的逻辑图,里面有 cf1cf2两个Column Family(以下简称CF),每个CF里面有两列。里面的红色和黄色小块表示有数据,其他地方都没有数据(如前面所说,是一个稀疏矩阵),多个层叠的部分表示有多个版本的数据。

第二幅图(右上角)也是一个逻辑图,主要为了说明以下几个点:

  • 不同CF里面的数据是分开存储的;
  • 同一个CF里面的数据是按照row key顺序存储的;
  • 统一cell里面有多个版本的数据的时候,新版本数据在前,旧版本数据在后,这样方便先取到新版本数据。

第三幅图(右下角)是上面的逻辑存储在物理文件上的存储形式,需要注意以下几个点:

  • 不同CF的数据是存储在不同的文件里面的(Storefile或HFile),这就是为什么我们要将要一起使用的数据字段定义在同一个Column Family的原因;
  • 同一个文件里面还是按照row key、CF、Column qualifier排序的;
  • 每一条数据里面都要存储Key(row key、Column Family、Column qualifier、Timestamp)和value。在RDBMS里面我们设计字段名时一般要求能够“见名知意”,但在HBase里面不推荐这样做, Key的设计在保证功能的前提下,越短越好(比如仅用一个字母表示),至于其含义可以其它地方记录,比如文档里面。

第四幅图(左下角)是为了说明查询时指定各个Key对性能的影响:

  • 指定 row key可以大幅度提高查询性能,因为根据row key可以确定在哪些region上面查(也就是说可以跳过那些不包含该row key的region)。在scan命令里面,可以通过STARTROWSTOPROW指定row key范围。
  • 指定 Column Family可以大幅度提高查询性能,因为根据CF可以确定跳过哪些Storefile/HFile,一般查询时都建议指定CF。
  • 指定 Timestamp也可以较大幅度提高查询性能,因为每个Storefile会存储它所保存的所有数据的时间区间,如果所指定的Timestamp不在该区间内,则直接跳过。
  • 指定 Column Qualifier
    Value的过滤条件可以提高查询性能,但提高的很少。因为必须把每个Cell的值读出来和指定的条件做对比。

4. Tall-Narrow or Flat-Wide表

Tall-Narrow也就是我们所说的“窄表”,Flat-Wide是“宽表”。举个例子,比如我们要存储一个用户的邮件信息:用户ID、邮件ID、邮件内容。如果按照Tall-Narrow的思想去设计,表结构可能是下面这样:

# 将用户id和邮件id拼接成row key,邮件内容作为一列数据userid-emailid, cf:emailbaody

如果按照 Tall-Narrow思路去设计,表结构可能是下面这样:

# userid为row key,emailid和emailbody作为两个列userid, cf:emailid, cf:emailbody

两种设计各有利弊,使用宽表的好处主要在于HBase的ACID特性仅限于行内,所以如果把所有数据都放在一行,那可以很好的利用其ACID特性。而窄表在实际使用中更加常见一些,因为HBase里面的的row key类似于RDBMS里面的主键,所以我们尽可能将要经常查询的维度放在row key里面,可以提高查询性能;另外一个表里面不推荐有太多的Column Family,一般1个最好,最多也不要超过3个,具体见本文“Column Family的数量”部分。

5. HBase的表和Column Family能不能修改

先说结论: 可以修改。使用alter命令可以修改表和Column Family,具体语法可以help "alter"查看。这里需要注意两点:

  1. 0.92版本之前,表必须先disable后才可以修改。之后的版本增加了一个配置项 hbase.online.schema.update.enable,如果设为true,那可以直接修改,不需要disable。但官方推荐生产环境最好还是先disable再修改,在线修改可能会引发一些问题。
  2. 修改动作并非立即生效,而是等待下一次 major compaction,Storefile重写之后才会生效。

6. HBase的Compaction

我们知道当MemStore里面的数据量达到一定值的时候,就会落盘形成StoreFile(HFile),这样就会形成很多文件,而Compaction就是将这些文件合并成大文件。HBase里面有两种Compaction: Minor compactionsMajor compactions

,主要有如下区别:

  • Minor compactions一次只选取少量存储在一起的文件做合并压缩,其结果就是一个store(或Column Family)的数据被合并成了多个大一些的Storefile,而Major compactions合并之后的结果是一个store(或Column Family)的数据全部到一个Storefile里面去了。
  • Minor compactions合并时不删除已经标记删除或者过期版本的数据,而Major compactions会删掉那些标记删除或过期版本的数据。所以需要注意,HBase里面的删除是标记删除,真正的物理删除发生在Major compactions阶段。

虽然Compaction合并文件是为了提高性能,但合并这个操作却是消耗资源的,就跟Jvm的GC一样。默认 Major compactions一周一次。

7. Column Family的数量

先说结论: 一个表内的Column Family最好1个,最多不要超过3个。原因主要有两点:

  1. 现在HBase的flush(MemStore满了之后就会flush)和Compaction操作都是基于region的,从前面的架构图中可以看到一个region里面是包含多个Column Family的,所以当region内的某个Column Family需要flush或Compaction的时候,和它处于同一region内的其它Column Family也会一起flush或Compaction,但它们可能只有少量新增数据,这就会浪费IO。
  2. 假设有两个Column Family:CF-A和CF-B,其中CF-A有一百万条数据,CF-B有一千万条数据。假设CF-A占了100个HFile,CF-B占了1000个HFile,因为数据写入的先后顺序,很可能CF-A的100个文件会被CF-B的1000个文件打散,本来可能一个RegionServer上面的region足够存储所有的CF-A的HFile了,现在可能被打散到多个RegionServer上面去了,这样查询CF-A的数据的时候效率就会降低。这种现象称为“Cardinality of ColumnFamilies”。

8. Value的版本数和TTL

版本数指我们之前说的Timestamp,和这个特性相关的设置有两个: max versionsmin versions,其含义也很明确。max versions的默认值是1,min versions的默认值是0,表示不启用多版本这个特性,即往一个Cell里面重复写数据会覆盖,而不是保留多个版本。

min versions和HBase的TTL(time-to-live)一起使用。我们可以给Column Family设置一个TTL时间(单位为秒),时间到期后的row会自动被删除。如果一个Storefile里面的row全部是过期的,那么在minor compaction阶段这个Storefile会被删除,我们可以通过把hbase.store.delete.expired.storefile设置为false或者把min versions设置为非0值来关闭删除这个特性。

9. 选择HBase还是RDBMS

一般来说,这个选择是比较好做的。HBase、Impala等数据库的诞生并不是为了替代传统的RDBMS,只是为了解决新的RDBMS解决不了或者解决起来比较困难的问题。所以,如果你的数据量并不大(一般以百万为分界线),那一般应该优先选择RDBMS,毕竟RDBMS支持完善的ACID,SQL,多级索引,各种Join,完善的类型等丰富的特性,这些都是HBase所不具备的。

但如果你的数据量非常大,RDBMS已经无法支撑了,那就可以考虑HBase等分布式数据库了。但如果你的业务离不开RDBMS的一些特性(比如各种Join、SQL、完善的ACID等),那可能就需要考虑类似于GreenPlum这种MPP数据库了。

10. FAQ

  • HBase是否支持ACID?

HBase里面只支持受限的ACID(Atomicity, Consistency, Isolation, and Durability):仅支持行内的ACID,跨行不支持。也就是对于同一行的操作是可以保证ACID的,但是多行操作是不行的。更多信息可参考: ACID in HBase 。

  • HBase和Hive如何选择?

这两个不是一个层级的东西,没有可比性。如果你还在这二者之间纠结,那可能你对它们有些误会,或者还不清楚你自己的需求。非要比的话,那就是HBase一般做实时查询;而Hive一般作为离线数据仓库,Hive后面是MapReduce/Spark,所以无法做到实时。

  • HBase支不支持join?

先说结论: 不支持。HBase读取数据时支持GetScan

Get的后台实现是Scan的一种特殊情况而已)操作,RDBMS里面的join在HBase里面是不支持的,但我们可以在表设计上支持一定程度上的"join"操作,比如将需要join的字段拼接起来作为row key

  • HBase支持SQL吗?

HBase不支持SQL,只提供了各种API。但Apache下有个Phoenix项目,通过该项目可以使用SQL语句操作HBase。

  • HBase的一个region多大合适?

注意,这里说的是region,不是RegionServer。一个region保持在10~50GB比较好。

  • HBase一个表包含多少个region比较好?

一般一个表包含50~100个region和1~2个Column Family比较好。

  • HBase的Cell里面存储的Value有大小限制吗?

没有,但一般不要超过10MB(对于MOB对象不要超过50MB)。如果超过了这个大小,可以将对象存到HDFS上面,然后再HBase里面存储HDFS路径。

  • HBase里面的daughter是什么?

HBase的region分裂的时候,分裂出来的两个新region称为"daughter",原来的称为"parent"。

关于HBase还有两个核心的点:一个是row key的设计,另外一个就是HFile。目前计划后续会分别写两篇文章来介绍这两方面东西,有兴趣的可持续关注。

References:

  1. Apache HBase Reference Guide(Version3.0.0-SNAPSHOT).
  2. HBase:The Definitive Guide.

-->

2.1 HBase里面的"NULL值"处理

2.4 HBase里面的数据是否有类型

3. HBase的逻辑存储和物理存储

4. Tall-Narrow or Flat-Wide表

5. HBase的表和Column Family能不能修改

7. Column Family的数量

8. Value的版本数和TTL

9. 选择HBase还是RDBMS

Hadoop系列五——HBase简介Hadoop系列五——HBase简介


分享到:


相關文章: