初识HBase

从今天我们开始学习HBase。希望大家能够通过这个课程对HBase有一个初步的认识,并且能够使用HBase提供的命令行以及Java API完成一些通用的操作。

今天的内容是一些入门性的知识:包括HBase的简单介绍、HBase的逻辑模型以及HBase的物理模型。

HBase简介

HBase是Google BigTable的开源实现。在05年前后,谷歌发布了三篇非常重要的论文,受这三篇论文的启发才有了Hadoop和HBase。其中启发了HBase的论文就是BigTable。同样受BigTable启发的还有另一个分布式数据库叫做Cassandra(卡珊德拉),不过Cassandra的影响力要比HBase小一些。BigTable直译过来就是大表的意思。BigTable的模型就是将所有的数据放在一张大表里面按列存储。就是这么简单,稍后我们再详细地说明。

HBase是一个分布式的、面向列的数据库。分布式的特性是说HBase将IO访问及存储分散到一个集群的全部节点上,显著提高了集群的处理能力。当现有集群性能不足的时候,可以简单地添加机器来扩充节点。HBase集群对节点机器的要求不是很高,一般普通的PC机就能满足需求。至于面向列,是说HBase中的记录是按列来进行存储和维护的。HBase中也有行的概念,不过与其说是行,倒不如说是Key/Value对更恰当一些。关于面向列这一点我们稍后会仔细地说,现在先继续。

HBase主要适用于高速读写的场景。尤其是在高速写(insert)的场景下,HBase有很大的优势。我工作中的一个项目,高峰时每小时需要入库的数据量达到10个G左右。我们只是用3台服务器组成了一个HBase集群,每个服务器的内存为16G,也没有使用SSD这样比较昂贵的硬件设施,就很轻松得实现了每小时10G数据的支撑。

HBase逻辑模型

前面我们说过,HBase是山寨了Google的BigTable,BigTable的思想是用一个大表来记录所有的数据。我们来看一个例子:

假设我们需要存储员工信息。表的结构是这样子的:员工信息(ID,姓名,性别,年龄)。在关系型数据库中这是一张非常简单的表了。

用BigTable的思想该怎么做呢——我们只需要一张表三个列就可以保存这些信息。该怎样用三个列来记录这些信息呢,大家可以想一下。

我们可以用这样的三个列来保存数据:

  • 第一个列是数据的主键,在这里可以是员工ID;
  • 第二列是属性的名称,比如姓名这个属性、性别这个属性;
  • 第三列是属性的值。

实际上就是将原来关系型数据库中的一行记录转成了多行记录。根据这个思想,任何一张有主键的表都可以转成BigTable模型下的记录。不管是员工信息、部门信息、设备信息、资产信息还是考勤信息都可以放在一张表里面,用三个列来记录。

刚才我们说的是BigTable的思想,那么HBase中是如何做的呢?下面这张表是一份HBase存储的数据的示例,大家看一下:

image

虽然说HBase是山寨了BIgTable,但是HBase和BigTable还是有很多不同之处的,它只是学习了BigTable的一些思想。BigTable是用一张大表存储了所有的记录,在HBase中却是允许有很多张表的。不过每张表的结构和BigTable有些相似,也是由行和列组成的二维表,大家看一下上面这张表:它的第一列是行键,就相当于表的主键(不知道大家有没有注意到这里前两个行键是相同的?我们都知道主键是不应该存在重复的,所以这里是我写错了么,肯定不是的。至于为什么,我们稍后会解释,大家只需要记住这里的行键存在重复就好了);第二列是列名,这里的列名是比较奇怪的——中间有一个冒号分隔。HBase中列的结构就是这样子的:在冒号的前面是列族(columnFamily)名称,冒号后面是列限定符的名称,列族名称+冒号+列限定符合起来构成了HBase中的列。比如第一行记录,列族是basic、列限定符是name;第二行列族是basic,列限定符是age。第三列是一个时间戳,默认是采用这一行记录的创建时间,不过也可以手动指定。第四列,就是列的值了。

HBase的逻辑结构就是这样子的:

  • HBase以表的形式存储数据
  • 表由行与列组成,列划分为若干列族,由行与列确定了HBase最基本的存储单元 (在示例表中为了让大家看着方便,我将主键和时间戳这一列的单元格合并了,实际上在HBase的存储结构中,每一列记录是独立存储的,每一列的记录都可以视为一个Key/Value对,行键+列共同构成了这个Key/Value对的key)
  • 每个存储单元可以存储一份数据的多个版本,由时间戳来标识

我们再来看一些HBase逻辑模型中的关键概念。

1. 行键(RowKey)

  • 行键是一份数据的唯一标识。(既然行键是唯一索引,为什么我们前面的那个示例表中相同的行键会出现两次呢,这是因为出现了数据的更新。HBase是允许更新和删除数据的,但是它的更新比较奇怪,是再插入一条新的纪录,同时旧的记录也会保存。也就是说对一行记录进行一次更新以后,这行记录会在HBase中保存两个版本。前面我们也提过这个版本是用记录的时间戳来区分的,我们默认取时间戳最新的版本。具体可以有多少个版本是在建表时进行设置的。当一行记录的版本超过设置的数量以后,最早的记录就会被删除掉。HBase中的删除也是比较奇怪的,它没办法立即删除一条记录,只是为这个版本的记录打上一个删除标记。因为HBase一直在不停地执行一个将小的数据文件文件合并成大的数据文件的操作,这个操作叫做compact,以后我们会详细说一下。在compact的时候遇到了打上了删除标记的记录,就会放弃将它合并到大文件,从而实现了真正的删除)
  • 行键是字节数组,这意味着我们不仅可以使用字符串或数字来做为行键,还可以使用一些非可视化字符作为行键,适当选择行键的组成是很关键的,以后我们会找一节课专门说说如何设计行键;
  • 表中的行根据行键进行排序,数据按照Row key的字节序(byte order)排序存储,大家记住这一点,我们以后会提到一个预分区的概念,“预”是预先的预,意思是提前做好分区,做预分区的时候必须要考虑到行键是如何排序的;
  • 所有对表的访问都要通过行键 (我们可以认为行键是HBase表的默认的唯一索引,不过我们也可以通过冗余存储的方式实现二级索引,在查询时利用好行键索引能起到事半功倍的效果)(HBase中的行键查询有三种,单个RowKey访问,就是Get操作,或RowKey范围访问,就是指定范围的Scan操作,或全表扫描,全表扫描就是不设范围的scan操作)

2. 列族(ColumnFamily)

  • 列族需要在表定义时给出(这里需要解释下,在表定义的时候,必需至少提供一个列族,表创建成功后还可以添加更多的列族,但是添加前需要先禁用表。更规范一些的说法是,列族是表结构的一个定义。注意,在HBase中列族是表结构的定义,而列不是),且必须是可打印字符(这一点是HBase中非常特殊的一点,HBase极少要求某些属性必须是可打印字符,它不要求行键是可打印字符,也不要求列限定符是可打印字符,唯独要求列族名称是可打印字符)
  • 每个列族可以有一个或多个列成员(ColumnQualifier,这个可以是任意二进制数组),列成员不需要在表定义时给出,新的列族成员可以在写入数据的时候动态添加。再看看我们那张表,我们这张表里只有一个列族basic,同时可以看到有三个已知的列(name,gender和age),我们在写入数据的时候,可以随时在列族basic中添加新的列,比如我们可以添加一个新的列“联系方式”,但是我们不能在写入数据时随意添加新的列族。
  • 数据按列族分开存储,HBase所谓的列式存储就是根据列族分开存储(每个列族对应一个Store实例)。这种设计非常适合于数据分析的情形,因为一个列族中的列通常有相同的使用场景,放在一起有利于查询、压缩和存储。

3. 时间戳(TimeStamp)

  • 可以由系统自动生成,也可以显式赋值。系统自动生成的时候对应每次数据操作的时间。
  • 每个Cell可能又多个版本,它们之间用时间戳区分。
  • 时间戳还关系到了HBase中数据的回收。HBase中数据回收有两种方式:一种是按数据的保留时间来回收(这有些类似于缓存的TTL,保留时间是怎么算的呢,就是当前时间-时间戳),一种是按版本数量来回收(在建表的时候指定版本数量,前面我们提到过了,时间戳的一个作用就是分别版本)。

关于时间戳还有一点:通常我们查询数据直接获得的都是最新版本的记录,当然也可以选择获取全部版本或指定时间最新版本的记录。

4. 单元格(Cell)

  • Cell 由行键,列族:限定符,时间戳唯一决定
  • Cell中的数据是没有类型的,全部以字节码形式存贮

HBase的物理模型

接下来我们看下HBase的物理模型。我们先看一张图:

hbase-structure

这张图是网上流传的HBase的存储架构图,大家仔细看一会儿,我们接下来的内容主要围绕这张图展开。

从这张架构图上可以看到这样几个概念:HMaster、HRegionServer、HRegion、Store、HFile等等。

我们先了解下这些概念,稍后再串起来从整体认识下HBase的数据具体是如何存储的。

ZooKeeper

Zookeeper是一个分布式应用程序协调服务,作用是为分布式应用提供一致性服务,比如检查集群上某些节点是否失效,实现负载均衡等等。HBase就是依赖ZooKeeper来实现集群节点的协调和HMaster的选举。具体作用是这样子的:

  • 保证任何时候只有一个HMaster;
  • 存储所有Region的入口信息;
  • 实时监控RegionServer的状态,将RegionServer的上线和下线信息及时通知给HMaster。

从这里列出的ZooKeeper的作用可以看出ZooKeeper通常适合做哪些工作,比如第一点“保证只有一个HMaster”是实现服务的一致性;第二点“存储Region的入口信息”说明Zookeeper有一定的存储功能;第三点“实时监控RegionServer”的状态,说明Zookeeper在某些场景下的角色是观察者。

HMaster

HMaster可以说是HBase的总控节点。可以同时启动多个HMaster,不过ZooKeeper的Leader Selection机制会保证同时一个Master发挥作用。HMaster的作用是维持HBase集群的稳定和负载均衡,以及HBase的元数据维护。具体是这些:

  • 对Region的分配(比如在产生新的Region以后决定将其分配给哪些RegionServer,发现RegionServer失效以后并重新分配其上的Region);
  • 负责RegionServer的负载均衡;
  • 对HBase上的Table的DDL操作(DDL就是对表的schema的更新,比如新增列族、调整表的压缩方式等等)。

此外HMaster还有一点作用就是回收在HDFS上的HBase的垃圾文件。这个不是很重要,大家知道有这个功能就行了。

HRegionServer

HRegionServer是实际和HBase中存储的数据打交道的节点。客户端通过HRegionServer才完成了对数据新增、删除和查询操作。它的具体作用如下:

  • 维护HMaster分配给它的Region,管理对这些Region的IO请求;
  • 切分在运行过程中变得过大的Region(这个是一个split操作,如果在建表的同时没有对一个表做预分区,那么所有的数据都会写入到一个Region中去,当这个Region过大以后就需要将之拆分成两个,随后的数据就会根据行键分别写入到这两个分区中,这个过程叫就做split)。

我们需要回过头来再看一下那张HBase存储结构图,我们看一下这张图:客户端访问HBase上存储的数据时候是通过ZooKeeper得到具体应该访问哪个RegionServer,然后再和RegionServer进行数据的交互。这期间并不需要HMaster参与。HMaster只是负责维护table和Region的元数据信息。所以HMaster的负载是很低的。

HRegionServer存取一个子表时,会创建一个HRegion对象,然后对表的每个列族创建一个Store实例,每个Store都会有一个MemStore和0个或多个StoreFile与之对应,每个StoreFile都会对应一个HFile, HFile就是实际的存储文件。因此,一个HRegion有多少个列族就有多少个Store。

可以记一下这里的两个对应关系:每个表对应着一个或多个HRegion实例、一个列族对应着一个Store实例。

两步定位Region

HBase中有一张特殊的表meta表,表中记录了用户表的Region信息,Meta表只有一个Region。

这里需要解释一下,在HBase的0.96版本以前还有一张特殊的表,就是root表。在root表中记录了.META.表的Region信息。不过在0.96及以后的版本中,为了简化寻找Region的流程,root表被取消了,我们可以认为以前存在root表上的内容被移到了zookeeper上。

根据目前我们掌握的信息,克制客户端在访问HBase时,定位到Region只需要两步:

  • 第一步、 通过ZooKeeper存储的信息得到Meta表的位置(并添加到缓存中)
  • 第二步、访问meta表,根据行键获取region的位置信息(并添加到缓存中)

我们在执行这两步的时候都做了写入缓存的操作,这样以后再做查询的时候都会优先查询缓存,在缓存中找不到才执行上面的步骤。

定位到Region以后,RegionServer会根据行键访问Region中存储的数据。

客户端访问HBase的流程

概念说的差不多了,可以回过头来看看HBase存储数据的流程了。

HBase依赖HDFS实现数据的存储。它通过HDFS客户端来访问HDFS的DataNode(关于HDFS及它的DataNode相关的内容大家可以查找些Hadoop相关的资料,这个在网上还是很多的)。

当客户端要向HBase中存储数据的时候,它会通过zookeeper找到meta表,而后通过行键在meta表中找到region所在的RegionServer;然后将数据发送给RegionServer。

RegionServer会将数据先写入到memstore。memStore 是放在内存里的。当memStore的大小达到一个阀值(默认64MB)时,memStore会被flush到文件。目前hbase有一个线程来负责memStore的flush操作。memStore中的数据写到文件后就是StoreFile,StoreFile底层是以HFile的格式保存在HDFS上。

查询数据没有写入数据这么麻烦,但是步骤大体上也差不多,也是定位到Region、先查询MemStore,MemStore中找不到再去StoreFile中找。

今天要讲的内容就是这样了。

额,这一节是要说HBase的物理结构来着,还是勉强总结下吧:

  • HBase表中的数据按照行键分别存储到一个或多个Region中
  • Region中的数据按列族存放在不同的Store中,每个列族对应一个Store
  • Store中的数据在HDFS中是以HFile的格式存储的

这些内容前面多少都提过了,就不详细解释了。

今天的内容就是这样,基本上都是一些务虚的内容。不过这里提到的这些概念会在以后的内容中频繁用到。如果大家想要继续深入学下去的话,这里提到的一些理论性的内容算是为大家打开了一扇窗子,让大家在以后的学习中做到有的放矢。

再见!

参考文档

http://blog.csdn.net/zhouzhaoxiong1227/article/details/46778033

https://yq.aliyun.com/articles/26336

https://my.oschina.net/u/2000675/blog/663852

http://blog.csdn.net/map_lixiupeng/article/details/40857825

########################

posted @ 2017-05-04 07:15  robin·张  阅读(593)  评论(0编辑  收藏  举报