1 Hbase是什么
Hbase它是一个数据库,一个支持大容量存储且支持高效率实时查询的数据库。在这个数据爆炸的时代,面临海量数据实时高效查询是不可避免的,Hadoop支持海量数据存储,但它不支持低延迟高效查询,于是乎有了Hbase。
专业一点定义,HBase是Google BigTable的开源实现版,是建立在Hadoop HDFS之上,提供高可靠性、高性能、列存储、可伸缩、实时读写的数据库系统。它介于NoSql和RDBMS之间,仅能通过主键(rowKey)和主键的range来检索数据,仅支持单行事务,主要用来存储非结构化和半结构化的松散数据。
与hadoop一样,Hbase可以依靠横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力。
2 Hbase特点
2.1 优点
- 大:一个表可以有上亿+、10亿+行,上百万列;
- 写入性能高,适用于插入比查询操作更频繁的情况;
- 查询性能好,海量数据下(100TB级别表)的查询(rowKey)依然能保持在ms级别;
- 面向列:面向列表(簇)的存储和权限控制,列(簇)独立检索。
- 稀疏:对于为空(NULL)的列,并不占用存储空间,因此,表可以设计的非常稀疏;
- 无模式:每一行都有一个可以排序的主键和任意多的列,列可以根据需要动态增加,同一张表中不同的行可以有截然不同的列;
- 数据多版本:每个单元中的数据可以有多个版本,默认情况下,版本号自动分配,版本号就是单元格插入时的时间戳;
- 数据类型单一:HBase中的数据都是字符串,没有类型。
2.2 缺点
- 只支持单一rowKey或rowKey range查询,不支持多条件复杂查询;
- 不适合于大范围扫描查询
- 不直接支持 SQL 的语句查询,学习和开发成本高;
- 不支持索引
对于3)、4)缺陷后面文《phoenix使用详解》中将给出解决方案 - 并不能保证100%时间可用,宕机恢复时间根据写入流量不同在几秒到几十秒甚至更高
- 可能存在数据丢失
3 Hbase架构
3.1 架构图
3.2 基础组件说明
3.2.1 Client
客户端包含访问Hbase的接口,同时在缓存中维护着已经访问过的Region位置信息,用来加快后续数据访问过程。
- HBase 有两张特殊表:
.META.:记录了用户所有表拆分出来的的 Region 映射信息,.META.可以有多个 Regoin
-ROOT-:记录了.META.表的 Region 信息,-ROOT-只有一个 Region,无论如何不会分裂 - Client 访问用户数据前需要首先访问 ZooKeeper,找到-ROOT-表的 Region 所在的位置,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过 client 端会做 cache 缓存。
3.2.2 Zookeeper
HBase 通过 Zookeeper 来做 Master 的高可用、RegionServer 的监控、元数据的入口以及集群配置的维护等工作。
- ZooKeeper 为 HBase 提供 Failover 机制,选举 Master,避免单点 Master 单点故障问题;
- 存储所有 Region 的寻址入口:-ROOT-表在哪台服务器上,-ROOT-这张表的位置信息;
- 实时监控Region Server的状态,将Region server的上线和下线信息实时通知给Master;
- 存储Hbase的schema,包括有哪些table,每个table有哪些column family。
3.2.3 Master:(是所有 Region Server 的管理者)
- 为 RegionServer 分配 Region
- 负责 RegionServer 的负载均衡
- 发现失效的 RegionServer 并重新分配其上的 Region
- HDFS 上的垃圾文件(HBase)回收
- 处理 Schema 更新请求(表的创建,删除,修改,列簇的增加等等)
3.2.4 RegionServer:(为 Region 的管理者)
- RegionServer 维护 Master 分配给它的 Region,处理对这些 Region 的 IO 请求
- RegionServer 负责 Split 在运行过程中变得过大的 Region,负责 Compact 操作
- 刷新缓存到HDFS
- 维护Hlog
可以看到,client 访问 HBase 上数据的过程并不需要 master 参与(寻址访问 zookeeper 和 RegioneServer,数据读写访问 RegioneServer),Master 仅仅维护者 Table 和 Region 的元数据信息,负载很低。
.META. 存的是所有的 Region 的位置信息,那么 RegioneServer 当中 Region 在进行分裂之后的新产生的 Region,是由 Master 来决定发到哪个 RegioneServer,这就意味着,只有 Master 知道 new Region 的位置信息,所以,由 Master 来管理.META.这个表当中的数据的 CRUD
所以结合以上两点表明,在没有 Region 分裂的情况,Master 宕机一段时间是可以忍受的。
3.2.5 Region
table在行的方向上分隔为多个Region。Region是HBase中分布式存储和负载均衡的最小单元(但不是存储的最小单元),即不同的region可以分别在不同的RegionServer上,但同一个Region是不会拆分到多个server上。
Region按大小分隔,每个表开始只有一个region。随着数据不断插入表,region不断增大,当region的某个列族达到一个阈值时就会分成两个新的region。
每个region由以下信息标识:< 表名,startRowkey,创建时间>
由目录表(-ROOT-和.META.)记录该region的endRowkey。
3.2.6 Store
每一个region由一个或多个store组成,至少是一个store,hbase会把一起访问的数据放在一个store里面,即为每个 ColumnFamily建一个store,如果有几个ColumnFamily,也就有几个Store。一个Store由一个memStore和0或者多个StoreFile组成。 HBase以store的大小来判断是否需要切分region。
3.2.7 MemStore
memStore 是放在内存里的。保存修改的数据即keyValues。当memStore的大小达到一个阀值(默认128MB)时,memStore会被flush到文件,即生成一个快照。目前hbase 会有一个线程来负责memStore的flush操作。
3.2.8 StoreFile
memStore内存中的数据写到文件后就是StoreFile,StoreFile底层是以HFile的格式保存。当storefile文件的数量增长到一定阈值后,系统会进行合并(minor、major compaction),在合并过程中会进行版本合并和删除工作(majar),形成更大的storefile。
3.2.9 HFile
HBase中KeyValue数据的存储格式,HFile是Hadoop的二进制格式文件,实际上StoreFile就是对Hfile做了轻量级包装,即StoreFile底层就是HFile。
3.2.10 HLog
HLog(WAL log):WAL意为write ahead log,用来做灾难恢复使用,HLog记录数据的所有变更,一旦region server 宕机,就可以从log中进行恢复。
HLog文件就是一个普通的Hadoop Sequence File, Sequence File的key是HLogKey对象,其中记录了写入数据的归属信息,除了table和region名字外,还同时包括sequence number和timestamp,timestamp是写入时间,sequence number的起始值为0,或者是最近一次存入文件系统中的sequence number。 Sequence File的value是HBase的KeyValue对象,即对应HFile中的KeyValue。
4 Data Model数据模型
简单来说,应用程序是以表的方式在HBase存储数据的。表是由行和列构成的,所有的列是从属于某一个列族的。行和列的交叉点称之为cell,cell是版本化的。cell的内容是不可分割的字节数组。表的行键也是一段字节数组,所以任何东西都可以保存进去,不论是字符串或者数字。HBase的表是按key排序的,排序方式之针对字节的。所有的表都必须要有主键-key.
4.1 逻辑结构图
4.2 物理存储结构
1. Name Space
命名空间,类似于关系型数据库的 DatabBase 概念,每个命名空间下有多个表。HBase有两个自带的命名空间,分别是 hbase 和 default,hbase 中存放的是 HBase 内置的表,default 表是用户默认使用的命名空间。
2. Row
HBase 表中的每行数据都由一个 RowKey 和多个 Column(列)组成,数据是按照 RowKey的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要。
3. Column
Hbase表中的每个列都属于某个列族,列族必须作为表模式定义的一部分预先给出。如:create ‘table’, ‘family’。
列名以列族作为前缀,每个"列簇"都可以是多个列成员,如course:math, course:english。
权限控制、存储、调优都是在列簇层面进行。
Hbase把同一列簇里面的数据存储在同一个目录下
4. TimeStamp
用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会自动为其加上该字段,其值为写入 HBase 的时间。
5. Cell
由{rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存贮(byte[]数组)。
5 Hbase工作流程
5.1 Region 寻址
1、老的 Region 寻址方式
在 HBase-0.96 版本以前,HBase 有两个特殊的表,分别是-ROOT-表和.META.表,其中-ROOT的位置存储在 ZooKeeper 中,-ROOT-本身存储了.META. Table 的 RegionInfo 信息,并且-ROOT不会分裂,只有一个 Region。而.META.表可以被切分成多个 Region。读取的流程如下图所示:
详细步骤:
第 1 步:Client 请求 ZooKeeper 获得-ROOT-所在的 RegionServer 地址
第 2 步:Client 请求-ROOT-所在的 RS 地址,获取.META.表的地址,Client 会将-ROOT-的相关 信息 cache 下来,以便下一次快速访问
第 3 步:Client 请求.META.表的 RegionServer 地址,获取访问数据所在 RegionServer 的地址, Client 会将.META.的相关信息 cache 下来,以便下一次快速访问
第 4 步:Client 请求访问数据所在 RegionServer 的地址,获取对应的数据
从上面的路径我们可以看出,用户需要 3 次请求才能直到用户 Table 真正的位置,这在一定 程序带来了性能的下降。在 0.96 之前使用 3 层设计的主要原因是考虑到元数据可能需要很大。但是真正集群运行,元数据的大小其实很容易计算出来。
2、新的 Region 寻址方式
如上面,2 层结构其实完全能满足业务的需求,因此 0.96 版本以后将-ROOT-表去掉了。 如下图所示:
访问路径变成了 3 步:
第 1 步:Client 请求 ZooKeeper 获取.META.所在的 RegionServer 的地址。
第 2 步:Client 请求.META.所在的 RegionServer 获取访问数据所在的 RegionServer 地址,Client 会将.META.的相关信息 cache 下来,以便下一次快速访问。
第 3 步:Client 请求数据所在的 RegionServer,获取所需要的数据。
总结去掉-ROOT-的原因有如下 2 点:
其一:提高性能
其二:2 层结构已经足以满足集群的需求
这里还有一个问题需要说明,那就是 Client 会缓存.META.的数据,用来加快访问,既然有缓存,那它什么时候更新?如果.META.更新了,比如 Region1 不在 RerverServer2 上了,被转移 到了 RerverServer3 上。Client 的缓存没有更新会有什么情况?
其实,Client 的元数据缓存不更新,当.META.的数据发生更新。如上面的例子,由于 Region1 的位置发生了变化,Client 再次根据缓存去访问的时候,会出现错误,当出现异常达到重试次数后就会去.META.所在的 RegionServer 获取最新的数据,如果.META.所在的 RegionServer 也变了,Client 就会去 ZooKeeper 上获取.META.所在的 RegionServer 的最新地址。
5.2 读请求过程
-
客户端访问ZooKeeper 获取-ROOT-表或.META.表找到目标数据所在的 RegionServer(就是数据所在的 Region 的主机地址)
-
联系 RegionServer 查询目标数据
-
RegionServer 定位到目标数据所在的 Region,发出查询请求
-
Region 先在 Memstore 中查找,命中则返回
-
如果在 Memstore 中找不到,则在 Storefile 中扫描 为了能快速的判断要查询的数据在不在这个 StoreFile 中,应用了 BloomFilter
(BloomFilter,布隆过滤器:迅速判断一个元素是不是在一个庞大的集合内,但是他有一个弱点:它有一定的误判率)
(误判率:原本不存在与该集合的元素,布隆过滤器有可能会判断说它存在,但是,如果布隆过滤器,判断说某一个元素不存在该集合,那么该元素就一定不在该集合内)
5.3 写请求过程
HBase数据写入序列图
-
Client访问ZooKeeper 获取-ROOT-表或.META.表,根据 RowKey 找到对应的 Region 所在的 RegionServer
-
Client 向 RegionServer 提交写请求
-
RegionServer 找到目标 Region
-
Region 检查数据是否与 Schema 一致
-
如果客户端没有指定版本,则获取当前系统时间作为数据版本
-
将更新写入HLog
-
将更新写入 Memstore
-
如果 Hlog 和 Memstore 均写入成功,则这条数据写入成功。在此过程中,如果 Memstore达到阈值,会新开启一个Memstore,把旧的Memstore 中的数据放到一个队列(MapReduce中的实现是环形缓冲区)里面,按先进先出的原则将旧的Memstore依次flush 到 StoreFile(底层是HFile)中。
-
当 Storefile 越来越多,会触发 Compact 合并操作,把过多的 Storefile 合并成一个大的Storefile。当 Storefile 越来越大,Region 也会越来越大,达到阈值后,会触发 Split 操作,将 Region 一分为二。
StoreFile 是只读的,一旦创建后就不可以再修改。因此 HBase 的更新/修改其实是不断追加 的操作。
Client 写入 -> 存入 MemStore,一直到 MemStore 满 -> Flush 成一个 StoreFile,直至增长到 一定阈值 -> 触发 Compact 合并操作 -> 多个 StoreFile 合并成一个 StoreFile,同时进行版本 合并和数据删除 -> 当 StoreFiles Compact 后,逐步形成越来越大的 StoreFile -> 单个 StoreFile 大小超过一定阈值后,触发 Split 操作,把当前 Region Split 成 2 个 Region,Region 会下线, 新 Split 出的 2 个孩子 Region 会被 Master 分配到相应的 RegionServer 上,使得原先 1 个 Region 的压力得以分流到 2 个 Region 上由此过程可知,HBase 只是增加数据,有所得更新和删除操作,都是在 Compact 阶段做的,所以,用户写操作只需要进入到内存即可立即返回,从而保证 I/O 高性能。
6 使用Hbase注意点
6.1 Hbase的一些局限性
- 在hbase的region迁移过程中,如果发生在hbase节点宕机或者负载均衡时,region从一个region server迁移到另外一个region server,在这个过程中该region不可用。
- Hbase发生永久RIT问题
Hbase节点异常关机导致hdfs上的文件块损坏和丢失,大量Regions 处于Offline状态,无法上线。比较暴力的解决方式是将损坏的文件删除,对系统进系统进行恢复。 - hbase的region拆分操作(split)
region发生split操作时,当前的region会关闭,处于offline状态,客户端到该region的请求都会失败。 - 单个region数据过热
某个region数据被频繁访问,造成单节点资源消耗过高,影响查询性能。
6.2 使用hbase需要注意点
- hbase适用的场景通常为非结构化的日志记录,通常写多读少;
- 合理设计rowKey和进行预分区,避免单个region数据过热或者频繁的compact和split操作,影响整体性能;
- 对于重要的业务数据建议不要只放在Hbase里面,可以在关系型数据库或者ES、Hive里面也存一份,保证数据不丢;
- 查询时,禁止使用全表scan,全表scan容易导致内存和cup高升,使节点不可响应甚至异常宕机,亲踩坑,血的教训;
- 在做大量数据的插入操作,避免出现递增 rowkey 的 put 操作,如果 put 操作的所有 RowKey 都是递增的,那么试想,当插入一部分数据的时候刚好进行分裂,那么之后的所有数据都开始往分裂后的第二个 Region 插入,就造成了数据热点现象。
7 附录Hbase Shell语法
Hbase Shell
命名 | 描述 | 语法 |
---|---|---|
help‘命名名’ | 查看命令的使用描述 | help ‘命令名’ |
whoami | 我是谁 | whoami |
version | 返回hbase版本信息 | version |
status | 返回hbase集群的状态信息 | |
table_help | 查看如何操作表 | table_help |
create | 创建表 | create ‘表名’, ‘列族名1’, ‘列族名2’, ‘列族名N’ |
alter | 修改列族 | 添加一个列族:alter ‘表名’, ‘列族名’ 删除列族:alter ‘表名’, {NAME=> ‘列族名’, METHOD=> ‘delete’} |
describe | 显示表相关的详细信息 | describe ‘表名’ |
list | 列出hbase中存在的所有表 | list |
exists | 测试表是否存在 | exists ‘表名’ |
put | 添加或修改的表的值 | put ‘表名’, ‘行键’, ‘列族名’, ‘列值’ put ‘表名’, ‘行键’, ‘列族名:列名’, ‘列值’ |
scan | 通过对表的扫描来获取对用的值 | scan ‘表名’ 扫描某个列族: scan ‘表名’, {COLUMN=>‘列族名’} 扫描某个列族的某个列: scan ‘表名’, {COLUMN=>‘列族名:列名’} 查询同一个列族的多个列: scan ‘表名’, {COLUMNS => [ ‘列族名1:列名1’, ‘列族名1:列名2’, …]} |
get | 获取行或单元(cell)的值 | get ‘表名’, ‘行键’ get ‘表名’, ‘行键’, ‘列族名’ |
ount | 统计表中行的数量 | count ‘表名’ |
incr | 增加指定表行或列的值 | incr ‘表名’, ‘行键’, ‘列族:列名’, 步长值 |
get_counter | 获取计数器 | get_counter ‘表名’, ‘行键’, ‘列族:列名’ |
delete | 删除指定对象的值(可以为表,行,列对应的值,另外也可以指定时间戳的值) | 删除列族的某个列: delete ‘表名’, ‘行键’, ‘列族名:列名’ |
deleteall | 删除指定行的所有元素值 | deleteall ‘表名’, ‘行键’ |
truncate | 重新创建指定表 | truncate ‘表名’ |
enable | 使表有效 | enable ‘表名’ |
is_enabled | 是否启用 | is_enabled ‘表名’ |
disable | 使表无效 | disable ‘表名’ |
is_disabled | 是否无效 | is_disabled ‘表名’ |
drop | 删除表 | drop的表必须是disable的 disable ‘表名’ drop ‘表名’ |
shutdown | 关闭hbase集群(与exit不同) | |
tools | 列出hbase所支持的工具 | |
exit | 退出hbase shell |
命令例子参考
https://blog.csdn.net/m0_37809146/article/details/91128061
Hbase详细信息可查看其官网
Hbase官网:https://hbase.apache.org/
Hbase中文文档:http://abloz.com/hbase/book.html#shell_tricks
参考文档:
https://blog.csdn.net/lukabruce/article/details/80624619
https://blog.csdn.net/u010270403/article/details/51648462
https://www.cnblogs.com/frankdeng/p/9310278.html