分布式列式数据库-HBase
2015-08-06 15:51:45 阿炯

HBase是一个分布式的、面向列的开源数据库,该技术来源于Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop之上提供了类似于Bigtable的能力。HBase是Apache的Hadoop项目的子项目,不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库,另一个不同的是HBase基于列的而不是基于行的模式。其授权方式与Hadoop相同。


HBase – Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。HBase是Google Bigtable的开源实现,类似Google Bigtable利用GFS作为其文件存储系统,HBase利用Hadoop HDFS作为其文件存储系统;Google运行MapReduce来处理Bigtable中的海量数据,HBase同样利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable利用 Chubby作为协同服务,HBase利用Zookeeper作为对应。

Use Apache HBase™ when you need random, realtime read/write access to your Big Data. This project's goal is the hosting of very large tables -- billions of rows X millions of columns -- atop clusters of commodity hardware. Apache HBase is an open-source, distributed, versioned, non-relational database modeled after Google's Bigtable: A Distributed Storage System for Structured Data by Chang et al. Just as Bigtable leverages the distributed data storage provided by the Google File System, Apache HBase provides Bigtable-like capabilities on top of Hadoop and HDFS.
 
Features

Linear and modular scalability.
Strictly consistent reads and writes.
Automatic and configurable sharding of tables
Automatic failover support between RegionServers.
Convenient base classes for backing Hadoop MapReduce jobs with Apache HBase tables.
Easy to use Java API for client access.
Block cache and Bloom Filters for real-time queries.
Query predicate push down via server side Filters
Thrift gateway and a REST-ful Web service that supports XML, Protobuf, and binary data encoding options
Extensible jruby-based (JIRB) shell
Support for exporting metrics via the Hadoop metrics subsystem to files or Ganglia; or via JMX

HBase位于结构化存储层,Hadoop HDFS为HBase提供了高可靠性的底层存储支持,Hadoop MapReduce为HBase提供了高性能的计算能力,Zookeeper为HBase提供了稳定服务和failover机制。此外,Pig和Hive还为HBase提供了高层语言支持,使得在HBase上进行数据统计处理变的非常简单。Sqoop则为HBase提供了方便的RDBMS数据导入功能,使得传统数据库数据向HBase中迁移变的非常方便。

访问接口

1. Native Java API,最常规和高效的访问方式,适合Hadoop MapReduce Job并行批处理HBase表数据。

2. HBase Shell,HBase的命令行工具,最简单的接口,适合HBase管理使用。

3. Thrift Gateway,利用Thrift序列化技术,支持C++、Python等多种语言,适合其他异构系统在线访问HBase表数据。

4. REST Gateway,支持REST 风格的Http API访问HBase, 解除了语言限制。

5. Pig,可以使用Pig Latin流式编程语言来操作HBase中的数据,和Hive类似,本质最终也是编译成MapReduce Job来处理HBase表数据,适合做数据统计。

6. Hive,当前Hive的Release版本尚没有加入对HBase的支持,但在下一个版本Hive 0.7.0中将会支持HBase,可以使用类似SQL语言来访问HBase。

HTable一些基本概念

Row key
行主键, HBase不支持条件查询和Order by等查询,读取记录只能按Row key(及其range)或全表扫描,因此Row key需要根据业务来设计以利用其存储排序特性(Table按Row key字典序排序如1,10,100,11,2)提高性能。

Column Family(列族)
在表创建时声明,每个Column Family为一个存储单元。这里设计了一个HBase表blog,该表有两个列族:article和author。

Column(列)
HBase的每个列都属于一个列族,以列族名为前缀,如列article:title和article:content属于article列族,author:name和author:nickname属于author列族。Column不用创建表时定义即可以动态新增,同一Column Family的Columns会群聚在一个存储单元上,并依Column key排序,因此设计时应将具有相同I/O特性的Column设计在一个Column Family上以提高性能。同时这里需要注意的是:这个列是可以增加和删除的,这和传统数据库很大的区别,所以适合非结构化数据。

Timestamp
HBase通过row和column确定一份数据,这份数据的值可能有多个版本,不同版本的值按照时间倒序排序,即最新的数据排在最前面,查询时默认返回最新版本。Timestamp默认为系统当前时间(精确到毫秒),也可以在写入数据时指定该值。

Value
每个值通过4个键唯一索引,tableName+RowKey+ColumnKey+Timestamp=>value,例如上例中{tableName=’blog’,RowKey=’1’,ColumnName=’author:nickname’,Timestamp=’ 1317180718830’}索引到的唯一值是“yedu”。

存储类型
TableName 是字符串
RowKey 和 ColumnName 是二进制值(Java 类型 byte[])
Timestamp 是一个 64 位整数(Java 类型 long)
value 是一个字节数组(Java类型 byte[])。

数据模型Table与Column Family

Row Key: 行键,Table的主键,Table中的记录默认按照Row Key升序排序。

Timestamp:时间戳,每次数据操作对应的时间戳,可以看作是数据的version number。

Column Family:列簇,Table在水平方向有一个或者多个Column Family组成,一个Column Family中可以由任意多个Column组成,即Column Family支持动态扩展,无需预先定义Column的数量以及类型,所有Column均以二进制格式存储,用户需要自行进行类型转换。

下面以示例的方式来描述HBase的表结构

HBase以表的形式存储数据,表有行和列组成,列划分为若干个列族/列簇(column family)。


如上图所示,key1,key2,key3是三条记录的唯一的row key值,column-family1,column-family2,column-family3是三个列族,每个列族下又包括几列。比如 column-family1这个列族下包括两列,名字是column1和column2,t1:abc,t2:gdxdf是由row key1和column-family1-column1唯一确定的一个单元cell。这个cell中有两个数据,abc和gdxdf。两个值的时间戳不 一样,分别是t1,t2, hbase会返回最新时间的值给请求者。

这些名词的具体含义如下:

1)、Row Key

与nosql数据库们一样,row key是用来检索记录的主键。访问hbase table中的行,只有三种方式:

1.1) 通过单个row key访问

1.2) 通过row key的range

1.3) 全表扫描

Row key行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。

存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起(位置相关性)。行的一次读写是原子操作 (不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。

2)、列族 column family

hbase表中的每个列,都归属与某个列族。列族是表的chema的一部分(而列不是),必须在使用表之前定义,列名都以列族作为前缀。例如courses:history , courses:math 都属于 courses 这个列族。

访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据)。

3)、单元 Cell

HBase中通过row和columns确定的为一个存贮单元称为cell,由{row key, column(=<family> + <label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。

4)、时间戳 timestamp

每个cell都保存着同一份数据的多个版本。版本通过时间戳来索引,时间戳的类型是 64位整型,时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间,时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。

为了避免数据存在过多版本造成的的管理(包括存贮和索引)负担,hbase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天),用户可以针对每个列族进行设置。


Table 与 Region

当Table随着记录数不断增加而变大后,会逐渐分裂成多份splits,成为regions,一个region由[startkey,endkey)表示,不同的region会被Master分配给相应的RegionServer进行管理:


-ROOT- && .META. Table

HBase中有两张特殊的Table,-ROOT-和.META.

.META.:记录了用户表的Region信息,.META.可以有多个regoin。

-ROOT-:记录了.META.表的Region信息,-ROOT-只有一个region。

Zookeeper中记录了-ROOT-表的location。

Client访问用户数据之前需要首先访问zookeeper,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过client端会做cache缓存。

HBase系统架构


HBase Client使用HBase的RPC机制与HMaster和HRegionServer进行通信,对于管理类操作,Client与HMaster进行RPC;对于数据读写类操作,Client与HRegionServer进行RPC调用。

Zookeeper
Zookeeper Quorum中除了存储了-ROOT-表的地址和HMaster的地址,HRegionServer也会把自己以Ephemeral方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的健康状态。此外,Zookeeper也避免了HMaster的单点问题,见下文描述。

HMaster
HMaster没有单点问题,HBase中可以启动多个HMaster,通过Zookeeper的Master Election机制保证总有一个Master运行,HMaster在功能上主要负责Table和Region的管理工作:
1. 管理用户对Table的增、删、改、查操作
2. 管理HRegionServer的负载均衡,调整Region分布
3. 在Region Split后,负责新Region的分配
4. 在HRegionServer停机后,负责失效HRegionServer 上的Regions迁移

HRegionServer
HRegionServer主要负责响应用户I/O请求,向HDFS文件系统中读写数据,是HBase中最核心的模块。HRegionServer内部管理了一系列HRegion对象,每个HRegion对应了Table中的一个Region,HRegion中由多个HStore组成。每个HStore对应了Table中的一个Column Family的存储,可以看出每个Column Family其实就是一个集中的存储单元,因此最好将具备共同IO特性的column放在一个Column Family中,这样最高效。

HStore存储是HBase存储的核心了,其中由两部分组成:一部分是MemStore,一部分是StoreFiles。MemStore是Sorted Memory Buffer,用户写入的数据首先会放入MemStore,当MemStore满了以后会Flush成一个StoreFile(底层实现是HFile),当StoreFile文件数量增长到一定阈值,会触发Compact合并操作,将多个StoreFiles合并成一个StoreFile,合并过程中会进行版本合并和数据删除,因此可以看出HBase其实只有增加数据,所有的更新和删除操作都是在后续的compact过程中进行的,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O的高性能。当StoreFiles Compact后,会逐步形成越来越大的StoreFile,当单个StoreFile大小超过一定阈值后,会触发Split操作,同时把当前Region Split成2个Region,父Region会下线,新Split出的2个孩子Region会被HMaster分配到相应的HRegionServer上,使得原先1个Region的压力得以分流到2个Region上。


在理解了上述HStore的基本原理后,还必须了解一下HLog的功能,因为上述的HStore在系统正常工作的前提下是没有问题的,但是在分布式系统环境中,无法避免系统出错或者宕机,因此一旦HRegionServer意外退出,MemStore中的内存数据将会丢失,这就需要引入HLog了。每个HRegionServer中都有一个HLog对象,HLog是一个实现Write Ahead Log的类,在每次用户操作写入MemStore的同时,也会写一份数据到HLog文件中(HLog文件格式见后续),HLog文件定期会滚动出新的,并删除旧的文件(已持久化到StoreFile中的数据)。当HRegionServer意外终止后,HMaster会通过Zookeeper感知到,HMaster首先会处理遗留的 HLog文件,将其中不同Region的Log数据进行拆分,分别放到相应region的目录下,然后再将失效的region重新分配,领取到这些region的HRegionServer在Load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到MemStore中,然后flush到StoreFiles,完成数据恢复。

存储格式

HBase中的所有数据文件都存储在Hadoop HDFS文件系统上,主要包括上述提出的两种文件类型:
1. HFile,HBase中KeyValue数据的存储格式,HFile是Hadoop的二进制格式文件,实际上StoreFile就是对HFile做了轻量级包装,即StoreFile底层就是HFile。

2. HLog File,HBase中WAL(Write Ahead Log) 的存储格式,物理上是Hadoop的Sequence File。

HFile
HFile的存储格式:首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo,Trailer中有指针指向其他数据块的起始点。File Info中记录了文件的一些Meta信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等。Data Index和Meta Index块记录了每个Data块和Meta块的起始点。


Data Block是HBase I/O的基本单元,为了提高效率,HRegionServer中有基于LRU的Block Cache机制。每个Data块的大小可以在创建一个Table的时候通过参数指定,大号的Block有利于顺序Scan,小号Block利于随机查询。每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成, Magic内容就是一些随机数字,目的是防止数据损坏。

HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构。我们来看看里面的具体结构:开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定长度的数值,表示RowKey的长度,紧接着是RowKey,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。

HLogFile
HLog文件就是一个普通的Hadoop Sequence File,Sequence File 的Key是HLogKey对象,HLogKey中记录了写入数据的归属信息,除了table和region名字外,同时还包括 sequence number和timestamp,timestamp是“写入时间”,sequence number的起始值为0,或者是最近一次存入文件系统中sequence number。
HLog Sequece File的Value是HBase的KeyValue对象,即对应HFile中的KeyValue

Hbase集群的组成简介

HBase的运行时有三个集群:
Zookeeper集群,负责HBase(当然还有HDFS)集群的协调服务和配置服务,其中的节点是ZooKeeper服务器。

HDFS集群,是一个分布式文件系统,有单一的名字空间。其中的节点有NameNode和DataNode,前者负责存储文件系统的元数据(包括目录名,文件名等),后则负责存储实际文件的块。

HBase集群,其中的节点有Master和Region Server。

HDFS存储
在HBase中,关于数据的元数据也是存储在HBase的表中的,而元数据和数据都通过HDFS文件系统存储。在HDFS中,HBase的文件如下所示:
/hbase,hbase的根目录
/hbase/weblogs,一个名为weblogs的表的目录
/hbase/weblogs/.tableinfo.0000000001,表的元数据信息
/hbase/weblogs/e048f132e3c5ac596e4f000486c0ef6e,表的一个Region目录
/hbase/weblogs/e048f132e3c5ac596e4f000486c0ef6e/.regioninfo,Region的元数据信息
/hbase/weblogs/e048f132e3c5ac596e4f000486c0ef6e/pageviews,Region的一个Column Family目录
/hbase/weblogs/e048f132e3c5ac596e4f000486c0ef6e/pageviews/837a8a7bbf044ed3849b77881a308808,Region的HFile

Zookeeper存储
不但在HDFS中存储信息,HBase还在Zookeeper中存储信息,其中的znode如下所示:
/hbase/root-region-server,Root region的位置
/hbase/table/-ROOT-,根元数据信息
/hbase/table/.META,元数据信息
/hbase/master,当选的Mater
/hbase/backup-masters,备选的Master
/hbase/rs,Region Server的信息
/hbase/unassigned,未分配的Region

也就是说,由Zookeeper保持了集群的状态信息。

Region Server
在来看Region Server,如下所示:


Region Server存储了三部分信息:
HFile,数据文件,存储在HDFS上。
Write-Ahead Log,重做日志,类似于Oracle的Redo Log和MySQL的Binlog,也存在HDFS上。
Memstore,内存中的数据缓存,类似Oracle的Buffer Cache。

Region Server fail时重新分配Region
其中HFile和WAL都存储在HDFS上,当Region Server fail的时候,数据是不会丢失的,丢失的只是Memstore中尚没有写入HFile的部分。Region Server宕机后,该服务器所负责的Region就会变成不可用,HMaster就会根据集群的负载状况,将这些Region分配给其他Region Server。

接手的Region Server从Zookeeper上,以及ROOT表和META表上可以找到充分的元数据信息,数据又是存在HDFS上,由于HDFS会保持多个副本,数据也不会丢,那么Region Server只需要根据这些信息组织起内存结构,并获取WAL文件进行重放,就可以最大程度的恢复到原来那台Region Server宕机前的状态。

当然由于Region Server大多和Data Node在一台服务器上,在写入时,也会调用本地的DFSClient写入,数据块会在本机有一个副本,在另外的机架上有一个副本,还有另外一个随机分配的副本,这样在读写时可以保证Data Locality。如果Region Server接手了一个Region,那么大多数数据块不在本地的Name Node上,读写性能会有下降,但会在后续的Compact过程中逐渐将文件写入本地的Data Node,从而恢复Data Locality。

当然由于HDFS少了一个Data Node,改Data Node上的数据块都少了一个副本,HDFS也会在其他的Data Node上重新建立这些副本,以保证可靠性。

什么情况需要HBase

半结构化或非结构化数据
对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取的数据适合用HBase。当业务发展需要存储其它字段信息时RDBMS需要停机维护,而HBase支持动态增加。

记录非常稀疏
RDBMS的行有多少列是固定的,为null的列浪费了存储空间。HBase为null的Column不会被存储,这样既节省了空间又提高了读性能。

多版本数据
根据Row key和Column key定位到的Value可以有任意数量的版本值,因此对于需要存储变动历史记录的数据,用HBase就非常方便了。比如上例中的author的Address是会变动的,业务上一般只需要最新的值,但有时可能需要查询到历史值。

超大数据量
当数据量越来越大,RDBMS数据库撑不住了,就出现了读写分离策略,通过一个Master专门负责写操作,多个Slave负责读操作,服务器成本倍增。随着压力增加,Master撑不住了,这时就要分库了,把关联不大的数据分开部署,一些join查询不能用了,需要借助中间层。随着数据量的进一步增加,一个表的记录越来越大,查询就变得很慢,于是又得搞分表,比如按ID取模分成多个表以减少单个表的记录数。经历过这些事的人都知道过程是多么的折腾。采用HBase就简单了,只需要加机器即可,HBase会自动水平切分扩展,跟Hadoop的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce)。


最新版本:2.0
Apache HBase 2.0.2 发布了。HBase 2.0.2 是 HBase 2.0.x 系列中的第二个补丁版本,旨在实现提高 HBase 的稳定性和可靠性。 此版本包含自 2.0.1 以来大约 100 多个问题修复。 所有hbases-2.0.x 用户都应该升级到这个新版本。此次更新信息如下:
允许通过 RpcServer 中的“hbase.ipc.trace.param.size”配置在 TRACE 级别打印到日志的 RPC 消息的长度。
之前使用前缀树编码的用户现在可以检查他们现有的 HFile 是否不再包含使用额外的 preupgrade check 命令的数据。
添加 HBCK 服务和强制更改表状态的方法,供 HBCK 客户端使用,对发生故障的 HBase 进行“修复”。

详细更新内容请查看更新日志发行说明


最新版本:2.3
HBase 2.3.0 是 HBase 2.x 系列中的第四个次要版本,旨在提升 HBase 的稳定性和可靠性。值得关注的新功能包括:
对 JDK 11 的初步支持(要求 Hadoop 3.2.0+)
Hadoop 版本增加到 2.10.0 和 3.1.2
ZooKeeper 版本增加到 3.5.7
围绕 HBCK2 的许多改进
HBase 客户端与 ZooKeeper 连接现在是可选的
基于 TinyLFU 的 BlockCache
持久性存储器上的 Bucket 缓存
改进 MTTR:将 WAL 拆分为 HFile

详情查看发行公告。


项目主页:http://hbase.apache.org/