本文是对Hbase组件的一个学习总结,共包括如下章节的内容:
- Hbase是什么
- Hbase的数据模型
- Hbase体系结构
- 安装和运行Hbase
- Hbase命令行
参考资料:
1、如果Hbase的数据要保存到hdfs上(一般生产环境下如此),需要确保有可用的hadoop运行环境,可参见《Hadoop运行环境搭建》一文。
2、因为Hbase运行依赖zookeeper服务,Hbase本身内置了zookeeper程序(往往用于开发和测试环境),如果希望独立部署zookeeper服务(一般生产环境下如此),可参见《Zookeeper学习笔记》一文。
一、Hbase是什么
HBase是Google Bigtable的克隆版。它是一个针对结构化数据的可伸缩、高可靠、高性能、分布式和面向列的动态模式数据库。和传统关系数据库不同,HBase采用了BigTable的数据模型:增强的稀疏排序映射表(Key/Value),其中,键由行关键字、列关键字和时间戳构成。HBase提供了对大规模数据的随机、实时读写访问,同时,HBase中保存的数据可以使用MapReduce来处理,它将数据存储和并行计算完美地结合在一起。
二、Hbase的数据模型
Hbase的数据逻辑上是存储在一张张表中。Hbase的表由行和列组成,行和列的交叉就是单元格,数据是存在单元格中的,单元格是有版本的,即一个单元格可存储多个版本的数据。
有几个重要的概念:
1、行键:
Hbase表的每行都有一个行健(类似关系表的主键),表中的行按照行键进行排序,对表中行的访问也是依据行健来访问的。
2、列族
行中的列被分成“列族”(column family)。同一个列族的所有成员(即列)具有相同的前缀。
表的列族是表模式的一部分,需要预先定义。但是列不是表模式的一部分。只要列族存在,客户端可以在更新时提供新的列,并存储它的值。
列名都以列族作为前缀,例如courses:history , courses:math 都属于 courses 这个列族。
这点与关系表不一样,关系表的列是表模式的一部分,关系表没有列族的概念。对于关系表,每行数据都有相同的列。但对于hbase表,每行数据都有相同的列族,但不一定有相同的列。
3、时间戳
HBase中通过行键和列确定的一个存贮单元称为单元格(cell)。每个 cell都保存着同一份数据的多个版本。默认情况下,版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由HBase (在数据写入时 )自动赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,HBase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。
说明:创建表时,默认情况下,每个列族只支持存储一个版本的数据。这个可以通过alter语句来更改。
4、单元(cell)
一个单元由{row key, column( =<family> + <label>), version} 唯一确定。cell中的数据是没有类型的,全部是字节码形式存储。
下面我们看一个例子。
假设有一张Hbase表,表名为webtable,该表有名为 contents、anchor 和 people 的三个列族。该表有两行数据,行健分别为cn 和 en。
对于第一行(cnn ),contents 列族包含一列(contents:html),anchor 包含两列(anchor:a,anchor:b),且第一行有5个版本。对于第二行(example),只有1个版本,contents 列族包含一列(contents:html),people列族包含一列(people:author)。
表中有些数据,下面通过表格的方式来展示下该表的数据。
此表中显示为空的单元格在 HBase 中不占用空间或实际上存在。这正是使 HBase “稀疏”的原因。表格视图并不是查看 HBase 数据的唯一可能的方法,对于 “稀疏”表,采用表格视图有时反而不是很方便。下面以类似json格式的视图来展示上面数据。
{
"cn": {
contents: {
t6: contents:html: "内容"
t5: contents:html: "内容"
t3: contents:html: "内容"
}
anchor: {
t9: anchor:a = "x"
t8: anchor:b = "y"
}
people: {}
}
"en": {
contents: {
t5: contents:html: "内容"
}
anchor: {}
people: {
t5: people:author: "tom"
}
}
}
对于Hbase表,可以看作是key/value集合的nosql数据库,其中的key就是行键,value是一个单元格(cell)的列表,单元格通过“列族+列+版本”来唯一标识。
三、Hbase的物理存储结构
Hbase表中的所有行都按照行键的字典序排列,表在行的方向上分割为多个HRegion。如下图所示。
HRegion按大小分割。每一个表一开始只有一个HRegion,随着数据不断插入表,HRegion不断增大,当增大到一定阈值的时候,HRegion就会等分为两个新的HRegion。当表中的行不断增多,就会有越来越多的HRegion。
HRegion是HBase中分布式存储和负载均衡的最小单元。最小单元就表示不同的HRegion可以分布在不同的HRegion Server上。但是一个HRegion是不会拆分到多个HRegion Server上。
HRegion虽然是分布式存储的最小单元,但并不是存储的最小单元。事实上,HRegion由一个或者多个Store组成,每一个Store都保存一个列族。每一个Store又由一个memStore和多个StoreFile组成。StoreFile以HFile格式保存在HDFS上。如下图所示。
memStore位于HRegion Server的内存中,数据写入时,首先会写入到memStore中,然后当到达一定的阀值的时候,Memstore中的数据会被刷到HFile中。
四、Hbase体系结构
Hbase的架构与hdfs,yarn类似,采用主从的架构,即一个Hbase系统包含一个master服务,若干个regionserver服务(分布在集群不同的机器上)。另外因为Hbase依赖zookeeper,所以一个Hbase系统中还需要包含zookeeper服务(相对zookeeper服务来说,Hbase服务是zookeeper的客户端)。当然还有Hbase的客户端(终端用户或其它系统访问Hbase服务的界面)。
下图是Hbase集群系统的体系结构示意图。
因为Hbase的主节点master只有一个,存在单点故障风险。在实际的环境下,往往会给mater节点配置HA。
五、安装和运行Hbase
安装
1、下载Hbase安装压缩包,本文下载的是hbase-2.0.2-bin.tar.gz
2、解压到某个目录下,我们这里解压的位置是:
/home/hadoop/hbase2.0.2
3、修改hadoop用户的profile文件,设置环境变量
export HBASE_HOME=/home/hadoop/hbase-2.0.2
PATH=$PATH:$HBASE_HOME/bin
说明:Hbase的运行依赖java环境,需要确保安装运行Hbase的机器上有正常可用的java环境。
Hbase有三种运行模式:独立模式;伪分布式模式;分布式模式。下面分别介绍。
(一)独立模式
独立模式下,HBase系统的所有服务程序,Master、RegionServers 以及依赖的ZooKeeper服务都运行在一个jvm进程中,Hbase不依赖hdfs,其数据存在本地文件中。该模式,只能用于开发和测试环境,不能用于生产环境。
独立模式下,如果不修改Hbase的配置文件,Hbase和zookeeper数据会默认存储到本地的/tmp/hbase-当前用户名 目录下。但是如果所运行的机器上配置了hadoop环境,Hbase还是会去连接hdfs(这样如果Hadoop未启动,会报错)。所以最好能显示的进行配置。
这时修改Hbase安装目录下conf目录下的hbase-site.xml文件,配置文件的内容设置如下:
<configuration>
<property>
<name>hbase.rootdir</name>
<value>file:///home/hadoop/hbasedata/hbase</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/home/hadoop/hbasedata/zk</value>
</property>
</configuration>
配置文件中的habse.rootdir属性设置的是Hbase数据在本地的存储目录(使用本地磁盘存储,需要加file://前缀);habse.zookeeper.property.dataDir属性设置的是zookeeper数据的存储目录(因为zk的数据本身就是存储在本地,所以不用加file://前缀)。
然后在控制台运行 start-hbase.sh 脚本就可以启动Hbase服务,这时我们用jps命令去查看,可以看到有一个名称为HMaster的java进程(即包含hbase master,regionserver,zookeeper三个服务的合一进程)。如:
[hadoop@localhost ~]$ jps
......
24060 HMaster
.......
说明独立模式下的Hbase服务启动成功。
停止Hbase服务可以执行 stop-hbase.sh脚本。
实际上,在独立模式下,Hbase的数据也可以不存储在本地,而是存储到hdfs上,这时配置文件配置如下:
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://namenode地址:8020/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>false</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/home/hadoop/hbasedata/zk</value>
</property>
</configuration>
这种情况下,Hbase的各服务(包括zk服务)依然运行在同一个jvm进程中。只是Hbase的数据存储到hdfs文件系统中。配置文件中的hbase.rootdir属性的值是一个hdfs系统的目录。当然,这种情况下,要Hbase服务能正常运行,相应的hdfs服务得正常可用。
(二)伪分布式模式
这类似hadoop的伪分布式模式,所有的Hbase服务以多个进程的方式运行,但是运行在一台机器上。这种模式一样只适合开发和测试,不适合生产环境。
伪分布式模式的配置,只需将hbase.cluster.distributed属性值设为true。而Hbase的数据既可以设置存储在本地,也可以保存到hdfs上。如下面的配置(这里Hbase数据存储在本地):
<configuration>
<property>
<name>hbase.rootdir</name>
<value>file:///home/hadoop/hbasedata/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/home/hadoop/hbasedata/zk</value>
</property>
</configuration>
在伪分布式模式下,通过bin目录下的hbase-daemon.sh来分别启动各个服务进程,操作如下:
hbase-daemon.sh start zookeeper
hbase-daemon.sh start master
hbase-daemon.sh start regionserver
通过上面的操作分别启动zookeeper服务进程,HBASE mater服务进程,HBASE regionserver服务进程。这时我们执行jps命令,会看到3个相关的进程:
[hadoop@localhost ~]$ jps
17585 HRegionServer
17349 HMaster
17212 HQuorumPeer
如果要关闭各个服务,可以依次执行下面三个操作:
hbase-daemon.sh stop regionserver
hbase-daemon.sh stop master
hbase-daemon.sh stop zookeeper
在伪分布式下,我们也可以不用Hbase自带的zookeeper,而是使用独立安装的zookeeper。关于独立zookeeper的安装,本文不再介绍,可参见《Zookeeper学习笔记》一文。
(三)分布式模式
在实际生产环境下,会采用分布式模式来启动Hbase。在分布式模式下,HBase守护进程的多个实例在集群中的多个服务器上运行。分布式的配置要求将hbase.cluster.distributed属性设置为true。通常情况下,hbase.rootdir被配置为指向高可用性的HDFS文件系统。这时集群系统将包含多个运行在不同服务器上的RegionServer,以及主要和备份Master和ZooKeeper守护程序(也是以集群方式部署)。
关于集群环境下的Hbase分布式部署,本文不作详细介绍。
六、Hbase命令行
Hbase服务启动后(最简单的方式就是使用本地文件系统以独立模式启动Hbase),就可以利用Hbase客户端程序访问Hbase服务了。
Hbase安装包中提供了一个命令行脚本hbase,位于bin目录下。在控制台下运行 hbase shell ,会出现交互式命令行程序(时间可能有点长,实际是启动了一个java进程),可以执行操作Hbase的各种命令,如:
[hadoop@localhost ~]$ hbase shell
...................................
Took 0.0042 seconds
hbase(main):001:0>
在提示符下输入help命令可以查看帮助信息。
1、查看用户空间下的所有表:
hbase(main):018:0> list
TABLE
0 row(s)
Took 0.0042 seconds
2、创建表:
hbase(main):063:0> create 'test','cf1','cf2'
Created table test
Took 0.7255 seconds
上面create命令创建了一个表,表名叫test,有2个列族cf1和cf2,属性采用默认的。注意hbase命令后的参数要用引号括起来。
这时我们如果再执行list命令,就能查到刚创建的test表。
3、查看表的信息
hbase(main):064:0> desc 'test'
Table test is ENABLED
test
COLUMN FAMILIES DESCRIPTION
{NAME => 'cf1', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DAT
A_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW',
CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION =>
'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}
{NAME => 'cf2', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DAT
A_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW',
CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION =>
'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}
2 row(s)
Took 0.0245 seconds
4、往表中插入数据:
hbase(main):066:0> put 'test','row1','cf1:a', 'a123'
Took 0.0244 seconds
hbase(main):067:0> put 'test','row1','cf1:b', 'b123'
Took 0.0045 seconds
上面操作,插入了两个数据,插入的是一行数据,行的关键字值为row1,列族cf1有两个成员,标示符分别是a和b。可以看出,put命令实际是往单元格中插入值。hbase表的列只是在数据插入时才确定。
5、查看表中所有数据
hbase(main):068:0> scan 'test'
ROW COLUMN+CELL
row1 column=cf1:a, timestamp=1544099621874, value=a123
row1 column=cf1:b, timestamp=1544099630279, value=b123
1 row(s)
Took 0.0099 seconds
6、查看表中指定行、列族、列的数据
hbase(main):069:0> get 'test','row1'
COLUMN CELL
cf1:a timestamp=1544099621874, value=a123
cf1:b timestamp=1544099630279, value=b123
1 row(s)
Took 0.0162 seconds
hbase(main):070:0>
hbase(main):071:0* get 'test','row1','cf1'
COLUMN CELL
cf1:a timestamp=1544099621874, value=a123
cf1:b timestamp=1544099630279, value=b123
1 row(s)
Took 0.0118 seconds
hbase(main):072:0> get 'test','row1','cf1:b'
COLUMN CELL
cf1:b timestamp=1544099630279, value=b123
1 row(s)
Took 0.0063 seconds
7、查看多版本数据
因为hbase表中的单元格可以允许存储多版本数据,但默认创建表的情况下,各列族允许的存储版本数的个数是1,可以通过desc命令能查看到。可以通过alter命令来重新修改,语法如:
alter '表名',{NAME=>'列族名',VERSIONS=>允许的版本数}
如下面例子:
hbase(main):073:0> alter 'test',{NAME=>'cf2',VERSIONS=>3}
Updating all regions with the new schema...
1/1 regions updated.
Done.
Took 1.8416 seconds
hbase(main):074:0> put 'test','row1','cf2:a','data1'
Took 0.0079 seconds
hbase(main):075:0> put 'test','row1','cf2:a','data2'
Took 0.0035 seconds
hbase(main):076:0> put 'test','row1','cf2:a','data3'
Took 0.0034 seconds
hbase(main):082:0> get 'test','row1','cf2:a'
COLUMN CELL
cf2:a timestamp=1544104507106, value=data3
1 row(s)
Took 0.0143 seconds
hbase(main):084:0> get 'test','row1',{COLUMN=>'cf2:a',VERSIONS=>3}
COLUMN CELL
cf2:a timestamp=1544104507106, value=data3
cf2:a timestamp=1544104503371, value=data2
cf2:a timestamp=1544104498175, value=data1
1 row(s)
Took 0.0091 seconds
可以看出,在get命令后加上 {COLUMN=>'cf2:a',VERSIONS=>3}参数可以返回多个版本的值。
8、删除数据
删除指定的行下的某个列族下的列(即某个单元格)
delete 'test','row1','data:a'
删除整行
deleteall 'test','row1'
9、禁用表
hbase(main):012:0> disable 'test'
Took 0.8346 seconds
在hbase中,删除表之前,必须先禁用表后才能删除。
10、删除表
hbase(main):012:0> drop 'test'
Took 0.4346 seconds
在实际应用中,我们一般会通过编写程序的方式去使用Hbase。最常见的是使用Hbase提供的java api来编写Hbase客户端程序。关于Hbase的java api的使用,在后面文章进行介绍。