2019独角兽企业重金招聘Python工程师标准>>>
1.简介
HBase– Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。
HBase是GoogleBigtable的开源实现,类似Google Bigtable利用GFS作为其文件存储系统,HBase利用HadoopHDFS作为其文件存储系统;Google运行MapReduce来处理Bigtable中的海量数据,HBase同样利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable利用 Chubby作为协同服务,HBase利用Zookeeper作为对应。
上图描述了Hadoop EcoSystem中的各层系统,其中HBase位于结构化存储层,Hadoop HDFS为HBase提供了高可靠性的底层存储支持,Hadoop MapReduce为HBase提供了高性能的计算能力,Zookeeper为HBase提供了稳定服务和failover机制。
此外,Pig和Hive还为HBase提供了高层语言支持,使得在HBase上进行数据统计处理变的非常简单。 Sqoop则为HBase提供了方便的RDBMS数据导入功能,使得传统数据库数据向HBase中迁移变的非常方便。
2.HBase使用场景
当我们对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取的数据适合用使用什么数据库?答案是什么,如果我们使用的传统数据库,肯定留有多余的字段,10个不行,20个,但是这个严重影响了质量。并且如果面对大数据库,pt级别的数据,这种浪费更是严重的,那么我们该使用是什么数据库?hbase数个不错的选择,那么我们对于hbase还存在下列问题:
1.Column Family代表什么?
2.HBase通过row和column确定一份数据,这份数据的值可能有多个版本,为什么会存在多个版本?
3.查询的时候会显示那个版本?
4.它们的存储类型是什么?
5.tableName是什么类型?
6.RowKey 和 ColumnName是什么类型?
7.Timestamp 是什么类型?
8.value 是什么类型?
引言 团队中使用HBase的项目多了起来,对于业务人员而言,通常并不需要从头搭建、维护一套HBase的集群环境,对于其架构细节也不一定要深刻理解(交由HBase集群维护团队负责),迫切需要的是快速理解基本技术来解决业务问题。最近在XX项目轮岗过程中,尝试着从业务人员视角去看HBase,将一些过程记录下来,期望对快速了解HBase、掌握相关技术来开展工作的业务人员有点帮助。我觉得作为一个初次接触HBase的业务开发测试人员,他需要迫切掌握的至少包含以下几点: 深入理解HTable,掌握如何结合业务设计高性能的HTable 掌握与HBase的交互,反正是离不开数据的增删改查,通过HBase Shell命令及Java Api都是需要的 掌握如何用MapReduce分析HBase里的数据,HBase里的数据总要分析的,用MapReduce是其中一种方式 掌握如何测试HBase MapReduce,总不能光写不管正确性吧,debug是需要的吧,看看如何在本机单测debug吧 本系列将围绕以上几点展开,篇幅较长,如果是HBase初学者建议边读边练,对于HBase比较熟练的,可以选读下,比如关注下HBase的MapReduce及其测试方法。 从一个示例说起 传统的关系型数据库想必大家都不陌生,我们将以一个简单的例子来说明使用RDBMS和HBase各自的解决方式及优缺点。 以博文为例,RDBMS的表设计如下: |
为了方便理解,我们以一些数据示例下
上面的例子,我们用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确定一份数据,这份数据的值可能有多个版本,不同版本的值按照时间倒序排序,即最新的数据排在最前面,查询时默认返回最新版本。如上例中row key=1的author:nickname值有两个版本,分别为1317180070811对应的“一叶渡江”和1317180718830对应的“yedu”(对应到实际业务可以理解为在某时刻修改了nickname为yedu,但旧值仍然存在)。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[])。
存储结构
可以简单的将HTable的存储结构理解为
即HTable按Row key自动排序,每个Row包含任意数量个Columns,Columns之间按Column key自动排序,每个Column包含任意数量个Values。理解该存储结构将有助于查询结果的迭代。
话说什么情况需要HBase
半结构化或非结构化数据
对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取的数据适合用HBase。以上面的例子为例,当业务发展需要存储author的email,phone,address信息时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)。
3.HBase的优缺点
HBase的优点:
1 列的可以动态增加,并且列为空就不存储数据,节省存储空间.
2 Hbase自动切分数据,使得数据存储自动具有水平scalability.
3 Hbase可以提供高并发读写操作的支持
Hbase的缺点:
1 不能支持条件查询,只支持按照Row key来查询.
2 暂时不能支持Master server的故障切换,当Master宕机后,整个存储系统就会挂掉.
4.HBase术语及原理
术语
catalog 表
-ROOT- 表用来跟踪 .META. 表。
META 用来保存所有 region 列表。
客户端直接和对应的 regionserver 联系。不通过 master。一个 region 是否应该被重新分配要么由 master 的负载均衡决定要么一个 regionserer 死了,客户端重新查询 catalog 表来看新的 region 在哪个 regionserver 上。
关于 master
用来监视所有的 RegionServer 的行为,同时也是所有 meta 数据改变的接口。
启动行为
在多主环境下,主会竞争。
运行时的影响
如果主挂掉,由于客户端直接和 RegionServer 通信,因此 cluster 可能短期依然是可运行的。catalog 表不一定存在于 master 上。master 应该尽快恢复。
接口
master 暴露的接口包括:表的创建,修改,删除等等;region 的移动,分配;列族的增加修改等等。
master 有几个后台线程:
负载均衡线程,.META. 的清理等等。
regionserver
暴露的方法包括数据的增加,删除,next,get 等;
region 的拆分紧凑等等。
当 HBaseAdmin 的 majorCompact 方法在一个表上被请求时,客户端实际上是直接和每个 region 在通信。
会启动下面几个线程:
compact,split,memstore flush,HLog 检查。
major 和 minor compact 区别是啥?
“minor compaction” 仅仅合并小文件为大文件,major compaction 则合并一个 region 内的所有文件,并进行清理操作。
log 总是刷吗?
是的,也可以不刷。
zookeeper 作用:
保存了 root 在哪里。
Zookeeper为HBase提供了稳定服务和failover机制。
ROOT- 和.META表
HBase中有两张特殊的Table,-ROOT-和.META.
META.:记录了用户表的Region信息,.META.可以有多个regoin
ROOT-:记录了.META.表的Region信息,-ROOT-只有一个region
Zookeeper中记录了-ROOT-表的location
Client访问用户数据之前需要首先访问zookeeper,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过client端会做cache缓存。
Zookeeper
Zookeeper Quorum中除了存储了-ROOT-表的地址和HMaster的地址,HRegionServer也会把自己以Ephemeral方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的健康状态。此外,Zookeeper也避免了HMaster的单点问题
原理简介
前提是大家至少了解HBase的基本需求和组件。
从大家最熟悉的客户端发起请求开始讲起吧,这样大家能够深有体会的逐步了解原理。比如我们发起了一条PUT请求,客户端首先需要查找到需要响应请求的REGIONSERVER。 记录region->regionserver映射是由HBASE系统表.META.记录的。所以我们只要知道. META.表的位置就能知道每个region响应的key的范围 和region所在机器。但是.META.表又保存在哪些机器上呢?这又是由-ROOT-表记录的 master在分配完-ROOT-表后 会将-ROOT-表的位置放到ZOOKEEPER中。所以我们在配置客户端的时候配置的是ZOOKEEPER的位置,而不是MASTER位置。
为什么要分为-ROOT-和.META.呢?这是因为region信息本身很多 一个集群中可能会出现成千上万的region 因此.META.表本身也无法在一个region中保存所有用户region的信息,所以本身也会分裂。而.META.表的region数就比较有限了所以-ROOT-是不会分裂的.
综上,客户端首次请求时,先拿-ROOT-然后通过请求范围找对应的.META.,在.META.中找打具体的region server 然后发送请求。-ROOT-和.META.是可以缓存的。
现在,我们解决了 客户端应当把PUT发送到哪个rs的问题,接下来就要发送请求了。region server收到请求后会保存PUT数据。这就不得不说HBASE的数据模型了,HBASE使用的列式存储,基本数据结构为LSMT log structure merge tree。简略的思路描述是,将操作记录在树中的节点上然后适时的将节点合并从而使key的删除修改能够最终体现在一个节点上,读取的时候会读取带有key相应操作的节点,返回最终key的值。可以看到lsmt是将随机读写转化为顺序读写的数据结构,读方面更适合扫库那样的顺序读取,不太适合随机读取。
那么一个PUT请求时怎么和LSMT搭上关系的呢?首先region server接到请求时,先将操作(keyvalue 时间戳 操作类型)保存为HLog,然后在保存到memstore中,然后即可返回写入成功的请求。其中memstore保存在内存中,写满后flush为hdfs文件。hlog是为了防止rs故障时,memstore数据必然丢失导致的数据丢失,在客户端可以禁用hblog来加快写入速度,但这是用数据不安全换来的。只要每次memstore刷入hdfs后,会判断hdfs刷入的中最早的操作 然后由另外的线程根据此记录删除旧的HLog文件。
接下来说说memstore写满时的处理。memstore写满(每个region的列族都有单独的memstore对象但实际上共用一块内存池)时,会将其中的操作分发到对应region的每个列族(store)做处理。然后store将这些操作序列保存为存储文件(storefile)。
从大体上粗略的看 region server这边重要的实体结构是这样:regionserver : region = 1 : n;region : store= 1 : n;store : storefile = 1 : n。对于每个列族的数据文件,实机上是一个LSMT的叶子节点,每个文件中保存的是最近的对于列族中key的操作。
当一个列族中文件过多的时候,会触发compact,也就是说的文件合并。HBase的compact分为两种 minor和major:minor是小范围内的合并文件,只合并部分。目的在于把小文件积累成大文件。因为没有全量数据,所以对于一个key的删除操作还是需要保留标记,无法物理删除。majorcompact把列族中的所有文件合并为一个,目的在于使key的修改和删除,最终在物理上生效。因为major compact操作的是此列族的全量数据,所以可以做物理删除。但是也由于是全量数据,执行起来耗费时间也会比价长,所以hbase对major compact做了时间间隔限制。
当store的store file集合中总文件长度太大时(超过配置的阈值),这个region会一分为二,也就是split。由于split是以region为单位的,所以有些列族因为其他列族过大也被连坐般的split。所以从这个流程粗略的看来 put会触发flush,flush会触发compact,compact会触发split。当然这都是在多个线程中执行的,不会明显的阻塞住客户端请求。
store file的大小和memstore大小有关系,一次flush会在一个列族里生成一个store file。所以memstore越大,产生大store file的机会也就越多。put不均匀时,有的列族里会有比较多的长度较小的store file,但是文件多了会触发compact。小文件compact很快,所以不用担心。
store file
------------------------------------------------
|block |
|----------------------------------------------|
|block |
...
| meta |
|---------------------------------------------|
|block索引,以及一些key范围信息|
|---------------------------------------------|
|布隆过滤 |
-----------------------------------------------
可以粗略的认为 一个storefile的结构是这样的,尾部的顺序和细节记不太清楚了。一个block包括多个key value,key在文件内是有序的。一条key value记录如下图:
读数据的时候我会发送一个get请求,在region server内部会转为一个scan。他会到相关列族中去scan storefile。storefile的尾部包含block索引、布隆过滤器、更新时间等所以这可以加快需要scan的文件过滤。所以针对一个store file读是这样的:判断get请求中的row key是否在文件保存的数据范围内;判断get请求中的row key是否能从布龙过滤器中找到(如果过滤器为row-col过滤器还可以判断是否包括需要get的col);判断get请求中的时间范围 是否在文件保存的数据的时间范围中;获取对应的block index;把block加载到block cache中;然后scan block;从多个store file的结果中 get请求中需要包含的version个数,取前几个从而满足get请求中需要包含的version个数。get可以看做特殊的scan操作。
总得blockcache大小是有限的,会有淘汰的.实际上blockcache对于scan来说更合适,因为scan一般是一个范围的扫,block中的row key又是有序的,所以说顺序读会比随机读快。一般hbase比较难适应高并发的随机读,因为blockcache这个设计的本身,就不适合缓存随机的row key:随机读的特点就是读的key均匀散列,这样会使读操作,落在每个block上,导致读的时候每个block先被加载到内存,然后很快因为其他的block持续加载进来而被淘汰出去,然后就这样换来换去,反而更浪费时间。
最后两个比较重要的操作是open和close region。这两个在容灾和均衡中常用。
先说close吧 正常close时会先flush memstore 然后通知master close结束。非正常关闭时,就来不及flush了。master会通过zk和region server之间的心跳这两种途径得知regionsever挂掉的情况。
open 一般由master发起。master先找到包含region操作对应的HLog文件,然后挑选出region对应的操作放到region目录中,然后命令某个region server open之。open时先重演HLog中记录的操作,然后再加载region对应的store和store file。
比较重要的原理就是这样的了。原理清楚了的话,再分析起来代码,就能有一个宏观的了解了。