Hbase 介绍及安装
本文及后续所有文章都以 2.4.2 做为版本讲解和入门学习
# Hbase 是什么
HBase 是一种构建在 HDFS 之上的分布式、面向列的存储系统。在需要实时读写、随机访问超大规模数据集时,可以使用 HBase。
尽管已经有许多数据存储和访问的策略和实现方法,但事实上大多数解决方案,特别是一些关系类型的,在构建时并没有考虑超大规模和分布式的特点。许多商家通过复制和分区的方法来扩充数据库使其突破单个节点的界限,但这些功能通常都是事后增加的,安装和维护都和复杂。同时,也会影响 RDBMS 的特定功能,例如联接、复杂的查询、触发器、视图和外键约束这些操作在大型的 RDBMS 上的代价相当高,甚至根本无法实现。
HBase 从另一个角度处理伸缩性问题。它通过线性方式从下到上增加节点来进行扩展。HBase 不是关系型数据库,也不支持 SQL,但是它有自己的特长,这是 RDBMS 不能处理的,HBase 巧妙地将大而稀疏的表放在商用的服务器集群上。
HBase 是 Google Bigtable 的开源实现,与 Google Bigtable 利用 GFS 作为其文件存储系统类似, HBase 利用 Hadoop HDFS 作为其文件存储系统;Google 运行 MapReduce 来处理 Bigtable 中的海量数据, HBase 同样利用 Hadoop MapReduce 来处理 HBase 中的海量数据;Google Bigtable 利用 Chubby 作为协同服务, HBase 利用 Zookeeper 作为对应。
# Hbase 表结构
Hbase 以表的形式存储数据,表有行(row)和列族(column-family)组成,列族划分若干个个列(column)。
# row-key
Hbase 本质上也是一种 key-value 的存储系统。key 相当于 row-key,value 相当于列族的集合。与 noSql 数据库一样,row-key 是用来检索记录的主键。
访问 Hbase 中的行,只有三种方式:
- 通过单个 row-key 访问
- 通过 row-key 的 range 访问
- 全表扫描
row-key 行键(Row key)可以是任意字符串 (最大长度是 64KB,实际应用中长度一般为 10-1000bytes),在 Hbase 内部,row-key 保存为字节数组。存储时,数据按照 row-key 的字典序 (byte order) 排序存储。设计 key 时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)
# columns-family 列族
Hbase 表中的每个列,都归属于某个列族。列族是表的 schema 的一部分 (而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如 courses:history,courses:math 都属于 courses 这个列族。
访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能帮助我们管理不同类型的应用,如:我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建集成的列族、一些应用则是允许浏览数据。
最好将具有相近 IO 特性的 Column 存储在一个 ColumnFamily,以实现高效的读取(数据局部性原理,可以提高混村的命中率)
# cell 与时间戳
由 {row-key,column,version} 唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存储。每个 cell 都保存着同一份数据的多个版本,版本通过时间戳来索引。
# 行列读写对比
# 写入
行存储的写入是一次完成,数据的完整性因此可以确定。列存储需要把一行记录拆分成单列保存,写入次数明显比行存储多。行存储在写入上占有很大的优势
# 数据修改
行存储是在指定位置写入一次,列存储是将磁盘定位到多个列上分别写入。行存储在数据修改也是占优的
# 数据读取
行存储通常将一行数据完全读出,如果只需要其中几列数据,就会存在冗余列。列存储每次读取的数据是集合中的一段或者全部。由于列储存的数据是同质的,这种情况使数据解析变得容易。行存储则复杂的多,因为在一行记录中保存了多种类型的数据,数据解析需要在多种数据类型之间频繁转换,这个操作很消耗 cpu,所以列存储的解析过程中更有利于分析大数据
显而易见,两种存储格式都有各自的优缺点:行存储的写入是一次性完成,消耗的时间比列存储少,并且能够保证数据的完整性,缺点是数据读取过程中会产生冗余数据,如果只有少量数据,此影响可以忽略;数量大可能会影响到数据的处理效率。列存储在写入效率、保证数据完整性上都不如行存储,它的优势是在读取过程,不会产生冗余数据,这对数据完整性要求不高的大数据处理领域,比如互联网,犹为重要。
# Hbase 存储架构
Hbase 里的一个 Table 在行的方向上分割为多个 Hregion,Hregion 可以动态扩展并且 Hbase 保证 Hregion 的负载均衡。Hregion 实际上是行键排序后的按规则分割的连虚的存储空间。
一张 Hbase 表,可能有多个 HRegion,每个 Hregion 达到一定的大小(默认 10G)时,进行分裂。
Hregion 是按大小分割的,每个表以开始只有一个 Hregion,随着数据不断插入表,Hregion 不断增大,当增大到一个阀值得时候,Hregion 就会等分两个新的 Hregion。当 table 中的行不断增多,就会有越来越多的 Hregion。
Hregion 默认 10GB,可以通过如下参数指定:
# 修改 hbase/hbase-site.xml 文件,单位为字节
hbase.hregion.max.filesize
2
Hregion 的拆分和转移是又 Hbase 自动完成的,用户感知不到。
Hregion 是 Hbase 中分布式存储和负载均衡的最小单元,但不是存储的最小单元,事实上,Hregion 由一个或多个 HStore 组成,每个 HStore 保存一个 ccolumnsFamily。HStore 由一个 memStore(写缓存,默认 128M)和零至多个 StoreFile 组成。StoreFile 以 HFile 格式保存在 HDFS 上。
Hregion 是分布式的存储最小单位,StoreFile(HFile)是存储最小单位。
# Hbase Hadoop 体系
# HMaster
HMaster 没有单点故障的问题,可以启动多个 HMaster,通过 Zookeeper 的 MasterElection 机制保证同时只有一个 HMaster 处于 Active 状态,其他的 HMaster 则处于热备份状态。一般情况下会启动两个 HMaster,非 Active 的 HMaster 会定期的和 Active Hmaster 通信以获取其最新的状态,从而保证它时实时更新的,因而如果启动了多个 Hmaster 反而会增加了 Active HMaster 的负担。
# HMaster 的主要主责由两个方面
协调 HregionServer:启动时 Hregion 的分配,以及负载均衡和修复时 Hregion 的重新分配;监控集群中所有的 HregionServer 的状态(通过 Heartbeat 和监听 zookeeper 中的状态)
Admin 直能:创建、删除、修改 Table 的定义。
# 作用
- 管理 HregionServer,实现负载均衡。
- 管理和分配 Hregion,比如在 Hregion Split 时分配新的 Hregion;在 HregionServer 退出时,迁移其内的 Hregion 到其他 HregionServer 上。
- 实现 DDL 操作 (namespace 和 table 的增删改,column family 的增删改)
- 管理 namespace 和 table 的元数据(实际存储在 HDFS 上)
- 权限空(ACL)
# HregionServer
Hbase 使用 row-key 将表水平切割成多个 Hregion,从 HMaster 的角度,每个 Hregion 都记录了它的 startKey 和 endKey,由于 row-key 是排序的,因而 Client 可以通过 HMaster 快速的定位每个 row-key 在哪个 Hregion 中。Hregion 由 HMaster 分配到相应的 HregionServer 中,然后由 HregionServer 负责 Hregion 的启动和管理,和 Client 的通信,负责数据的读(使用 HDFS)。每个 HregionServer 可以同时管理 1000 个左右的 Hregion。
- 存放和管理本地 Hregion。
- 读写 HDFS,管理 Table 中的数据。
- Client 直接通过 HregionServer 读写数据(从 HMaster 中获取元数据,找到 row-key 所在的 Hregion/HregionServer 后)。
# zookeeper
zookeeper 为 Hbase 集群提供协调服务,它管理折 HMaster 和 HregionServer 的状态(available/alive 等),并且会在其他们宕机时通知到给 HMaster,从而 HMaster 可以实现 HMaster 之间的 failover(失败恢复),或对宕机的 HregionServer 中的 Hregion 集合的修复(将它们分配给其他的 HregionServer)。
HMaster 通过监听 zookeeper 中的 Ephemeral 节点(默认:/hbase/re/*)来监控 HregionServer 的加入和宕机。在第一个 HMaster 连接到 zookeeper 时会创建 Ephemeral(短暂) 节点(默认:/hbase/master)来表示 active 的 HMaster,其后加进来的 HMaster 则监听该 Ephemeral 节点,如果当前 Active 的 HMaster 宕机,则该节点消失,因而其他的 HMaster 得到通知,而将自身转换成 Active 的 HMaster,在变为
Active 的 HMaster 之前,它会创建在 /hbase/back-masters/ 下创建自己的 Ephemeral 节点。
- 存放整个 Hbase 集群的元数据以及集群的状态信息,以及 HregionServer 服务器的运行状态
- 实现 HMaster 主备节点的 failover
# HregionServer 解析
HregionServer 一般和 DataNode 在同一台机器上运行,实现数据本地性,避免网络传数据。HregionServer 存活和管理多个 Hregion,由 WAL(HLog)、BlockCache、MemStore、HFile 租场。
Hlog 即 WAL(新版本叫法)write Ahead Log, 写之前日志,是 HDFS 上的一个文件,会先把写操作数据写到日志里,在写到 memStore 缓存里,最后写到 HFile 中。采用这种模式 HregionServer 宕机后,我们依然可以从该 Log 文件中恢复数据,Replay 所有的操作,而不至于数据丢失。
BlockCache 是一个读缓存,即 “引用全局性” 原理(也应用于 CPU,分空间局部性和时间局部性,空间局部性是指 CPU 在某一时刻需要某个数据,那么有很大的概率在下一时刻它需要的数据在其附近;时间局部性是指某个数据在被访问过一次后,他有很大的概率在不久的将来会被再次的访问),将数据预读取到内存中,以提升读的性能。Hbase 中默认 on-heap LruBlockCache 策略来清除 读缓存。
MemStore 是一个写缓存,所有数据的写在完成 WAL 日之后,会写入 MemStore 中,由 MemStore 根据一定的算法(LSM-TREE 算法,这个算法的作用是将数据顺序写磁盘,而不是随机写,减少磁盘头调度时间,从而提高写入性能)将数据 Flush 到底层的 HDFS 文件中(HFile),通常每个 Hregion 中的每个 ColumnFamily 有一个自己的 MemStore
Hfile(StoreFile)用于存储 Hbase 的数据(Cell/KeyValue)。在 HFile 中的数据是按 row-key、ColumnFamily、Column 排序,对相同的 Cell(即这三个值都一样),则按 timestamp 倒叙排列。因为 Hbase 的 HFile 是存到 HDFS 上,所以 Hbase 实际上是具备数据的副本冗余机制的。
缓存回收测策略:
- LRU 最近最少使用的:移除最长时间不被使用的对象
- FIFO 先进显出:按对象进入缓存的顺序来移除它们。
- SOFT 软引用:移除基于垃圾回收状态和软引用。
- WEAK 弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象。
# 读写数据流程
读写都会执行以下方式,去确定 到底往哪个 HRegionServer 读写:
- Client 访问 zookeeper,获取 hbase:meta 所在 HRegionServer 的节点信息
- Client 访问 hbase:meta 所在的 HRegionServer,获取 hbase:meta 记录的元数据后先加载到内存中,然后再从内存中根据需要查询的 RowKey 查询出 RowKey 所在的 HRegion 的相关信息(Region 所在 RegionServer)
- Client 访问 RowKey 所在 Region 对应的 HRegionServer,发起数据读取请求
# 读流程
都流程以以上图的方式,总结是:Client->Zookeeper->Meta RS->RS->BlockCache->MemStore->HFile (index+BloomFilter)
相同 Cell(数据)可能存在三个地方:首先对新写入的 Cell,它会存在于 MemStore 中;然后对之前已经 Flush 到 HFile 中的 Cell,它会存在于某个或某些 HFile 中;最后对刚读取过的 Cell,它可能存在于 BlockCache 中。既然相同的 Cell 可能存在三个地方,在读取的时候只需要扫描三个地方,然后将结果合并即可(Merge Read),在 HBase 中扫描的顺序一次是:BlocalCache、MemStore、HFile。其中 HFile 的扫描先会使用 Bloom Filter 过滤那些不可能符合条件的 HFile,然后使用 Block Index 快速定位 Cell,并将其加载到 BlockCache 中,然后从 BlockCache 中读取。我们直到一个 HStore 可能存在多个 HFile,此时需要扫描多个 HFile,如果 HFile 过多是会引起性能问题。
# 写流程
写流程和都流程不太一样,总结是:Client->Zookeeper->RS->WAL->MemStore->HFile->HDFS
当确定了写到哪个 HRegionServer 后,client 发起请求,会先在该 HRegionServer 的日志(WAL)记录,然后写入 MemStore 缓存,在缓存中进行排序(LSM-TREE),当缓存达到三种情况触发 Flush 到 HDFS(HFile)里。
- 当一个 Hregion 中的 MemStore 的大小超过了默认 128M 内存使用量,此时当前的 MemStore 会 Flush 到 HFile 中。
hbase.hregion.memstore.flush.size=128MB
- 当 HregionServer 服务器上所有的 MemStore 的大小超过了 本机内存 35% 的内存使用量,此时当前的 HregionServer 中所有 Hregion 中的 MemStore 可能都会 Flush。从最大的 MemStore 开始 Flush。
hbase,refionserver.global.memstore.upperLimit=35
- 当前 HRegionServer 中 WAL 的带下超过了 1GB,hbase.regionserver.hlog.blocksize (32MB) * hbase.regionserver.max.logs (32 个) 的数量,当前 HRegionServer 中所有 HRegion 中的 MemStore 都会 Flush。
# Compaction(合并) 机制
MemStore 每次 Flush 会创建新的 HFile,而过多的 HFile 会引起读的性能问题。对于这一问题,Hbase 采用 Compaction 机制来解决这个问题。在 Hbase 中 Compaction 分为两种:Minor Compaction 和 Major Compaction。
Minor Compaction 是指选取一些小的、相邻的 StoreFile 将他们合并成一个更大的 StoreFile,在这个过程中不会处理已经 Deleted 或 Expired 的数据。一次 Minor Compaction 的结果是更少并且更大的 StoreFile。
Major Compaction 是指将所有的 StoreFile 合并成一个 StoreFile,在这个过程中,标记为 Deleted 的数据会被删除,而那些已经 Expired 的数据会被丢弃,那些已经超过最大版本的数据会被丢弃,一次 Major Compaction 的结果是一个 HStore 只有一个 StoreFile 存在。Major Compaction 可以手动或自动触发,然而由于他会引起性能问题,因而它一般会被安排在周末、凌晨等集群比较闲的时间。
代码方式
// minor compact
hbaseTemplate.getAdmin().compact(TableName.valueOf("table_name"));
// major compact
hbaseTemplate.getAdmin().majorCompact(TableName.valueOf("table_name"));
2
3
4
命令方式
compact('table_name')
major_compact('table_name')
2
# Hbase 表设计
# row-key 设计
row-key 是不可分割的字节数,按字典排序由低到高存储在表中。在设计 Hbase 表时,row-key 设计是最重要的事情,应该基于预期的访问模式来为 row-key 建模。row-key 决定了访问 Hbase 表时可以得到的性能,原因有两个:
- HRegion 基于 row-key 为一个区间的行提供服务,并且负责区间每一行;
- HFile 在硬盘上存储有序的行。
这两个因素是相互关联的。当 HRegion 将内存中数据 flush 为 HFile 时,这些行已经排过序,也会有序地写到硬盘上。row-key 的有序特性和底层存储格式可以保证 Hbase 表在设计 row-key 之后的良好性能。
关系型数据库可以在多列上建立索引,但是 Hbase 只能在 row-key 上建立索引(可以通过 ES 为 Hbase 的列建立索引)。而设计 row-key 有多种技巧,而且可以针对不同访问模式进行优化。
- 将 row-key 以字典顺序从大到小排序:原生 Hbase 只支持从小到大的排序,但是现在有个需求想展现影片热度排行榜,这就要求实现从大到小排列,针对这种情况可以采用 Rowkey=Integer.MAX_VALUE-Rowkey 的方式将 row-key 进行转换,最大的变最小,最小的变最大,在应用层再转回来即可完成排序需求。
- row-key 尽量散列设计:最终要的是保证散列,这样就会保证所有的数据都不是在一个 Hregion 上,从而避免读写的时候负载会集中在个别 Hregion 上。Rowkey = 业务数 - 随机数
- row-key 的长度尽量短:如果 row-key 太长,第一 存储开销会增加,影响存储效率;第二 内存中 row-key 字段过长,会导致内存的利用率降低,进而降低索引命中率。row-key 是一个二进制码流,row-key 的长度建议在 16 个字节以内。原因如下:
- 数据的持久化文件 HFile 中是按照 keyValue 存储的,如果 row-key 太长比如 100 个字节,1000 万列数据光 row-key 就要占用 100*1000 万 = 10 以个字节,将近 1G 数据,这会极大影响 HFile 的存储效率。
- MemStore 将缓存部分数据到内存,如果 row-key 字段过长,内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。
- row-key 唯一
- row-key 建议使用 string 类型:虽然行键在 Hbase 中是以 byte [] 字节数组的形式存储的,但是建议在系统开发过程中将其数据类型设置为 String 类型,保证通用性。
- row-key 建议设计的有意义:row-key 的主要作用是为了进行数据记录唯一性标示,但是唯一性并不是其全部,具有明确意义的行键对于应用开发、数据检索等都具有特殊意义。
- 具有定长性:行键具有有序性的基础便是定长,譬如 20140512080500、20140512083000,这两个日期形式的字符串是递增的,不管后面的秒数是多少,我们都将其设置为 14 位数字形式,如果我们把后面 0 去除了,那么 201405120805 将大于 20140512083,其有序性发生了变更。所以建议,行键一定要设计成定长的。最好是 8 字节的倍数
# 列族的设计
在设计 Hbase 表的时候,列族不宜过多,尽量的要少使用列族。经常要在一起查询的数据最好放在一个列族中,尽量减少跨列族的数据访问。
# 安装
wget https://mirrors.bfsu.edu.cn/apache/hbase/2.4.2/hbase-2.4.2-bin.tar.gz
解压
tar -xvf hbase-2.4.2-bin.tar.gz
# 修改配置文件
配置环境变量
export HBASE_HOME=/opt/software/hbase-2.4.2
export PATH=$PATH:$HBASE_HOME/bin
2
修改 /hbase-2.4.2/conf hbase-env.sh,添加如下
export JAVA_HOME=/opt/software/java8
# 不启用自己携带的 zookeeper
export HBASE_MANAGERS_ZK=false
2
3
修改 /hbase-2.4.2/conf hbase-site.xml
<!-- 存储数据目录路径 -->
<property>
<name>hbase.rootdir</name>
<!-- 可以指定本地存储,也可以指定 hdfs -->
<value>file:///opt/software/hbase-2.4.2/tmp</value>
</property>
<!-- zookeeper的地址 -->
<property>
<name>hbase.zookeeper.quorum</name>
<value>node113,node103,node104</value>
</property>
<!-- zookeeper的快照的存储位置 -->
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/opt/software/hbase-2.4.2/zookeeper-data</value>
</property>
<!-- Hbase 的运行模式,false是单机,true是分布式模式,若为false,hbase和zookeeper会运行再同一个jvm里面 -->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!-- 本地文件系统的临时文件夹。可以修改到一个更为持久的目录上 -->
<property>
<name>hbase.tmp.dir</name>
<value>./tmp</value>
</property>
<!-- v2.1版本,再分布式情况下,设置为false -->
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>false</value>
</property>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
拷贝 jar 到 lib 目录
cp $HBASE_HOME/lib/client-facing-thirdparty/htrace-core4-4.2.0-incubating.jar $HBASE_HOME/lib/
# 集群配置
只需要在 /hbase-2.4.2/conf 修改 regionservers,添加集群节点。默认是 localhost,可以单机使用
node113
node103
node104
2
3
然后把 以上配置在其他节点保持一样,集群即可成功。
# 启动
/hbase-2.4.2/bin
./start-hbase.sh
jps 验证启动,集群中,只有主节点才有 HMaster
54941 HRegionServer
54808 HMaster
2
也可以查看 WebUI,端口默认 16010
hbase 客户端
./hbase shell
status 查看状态,1 个可用的 master,0 个备份,共 1 个服务(没有从节点一说,都计算到 servers),0 宕机,2 负载
hbase:001:0> status
1 active master, 0 backup masters, 1 servers, 0 dead, 2.0000 average load
Took 1.8292 seconds
2
3
# 命令
创建 table_name 表,c1,c2 为族,一张表中,族创建越少越好(一般 2~3 个)
create 'table_name','c1','c2'
创建 table_name 表,指定 c1,c2 族的可保留版本
create 'table_name',{NAME='c1',VERSION=>3},{NAME='c2',VERSION=>3}
查看所有表
list
给 table_name 插入数据 row1 (行标识) 行,c1 族添加 name 列,name 列的值是 tom,如果改行该列已经有值,继续执行会覆盖。
put 'table_name','row1','c1:name','tom'
获取 table_name 的 row1 (标识) 行数据。
get 'table_name','row1'
获取 table_name 的 row1 (标识) 行,c1,c2... 族的数据
get 'table_name','row1','c1','c2'
获取 table_name 的 row1 (标识) 行,c1 族 name 列的数据
get 'table_name','row1','c1:name'
查看 table_name 表的所有数据
scan 'table_name'
查看 table_name 表的 c1,c2 ... 族的所有数据
scan 'table_name',{COLUMNS=>['c1','c2']}
查看 table_name 表的 c1 族 name 列的所有数据
scan 'table_name',{COLUMNS=>['c1:name']}
查看 table_name 表的历史数据 (被修改过的值也会被查到),RAW 开启对历史版本的查询,VERSION 指定查询最新的几个版本的数据
scan 'table_name',{RAW=true,VERSION>=3}
删除 table_name 和 row1 (行标识) 删除整行数据
deleteall 'table_name','row1'
删除表
disable 'table_name'
drop 'table_name'
2