什么是HBase?
一、概念
基于hdfs,高可靠性、高性能、列存储、可伸缩、快速响应、实时读写nosql的数据库系统
二、应用场景
Hbase非常适合用来进行大数据的实时查询。如:Facebook用Hbase进行消息和实时的分析。它也可以用来统计Facebook的连接数。
三、整体架构
各组件成员介绍及对应职责
Clinet(客户端):
包含访问HBase接口,维护一些cache(缓存),如:Region位置信息,加快对HBase的访问
Zookeeper:
保证只有一个Master
保证所有Region的寻址入口
实时监控RegionServer动态上下线
通知Master存储HBase的schema和table的元数据
HMaster:
负责.META.表的维护
为RegionServer分配Region
负责RegionServer的负载均衡,发现有失效的RegionServer就重新分配Region
HDFS上的垃圾回收 (HDFS上的目录 /tmp)
处理schema的更新请求
RegionServer:
每个RegionServer会维护一个HLog,并且HLog是以单个文件追加的方式写入,而不是同时写入多个文件。目的:减少寻址次数,提高对table的写性能
维护分配到的Region,处理这些Region的IO请求
负责切分达到阈值的Region
每个RegionServer各自保管自己的HLog
大致了解了HBase的整体架构之后,接下来深入了解一些内部原理
内部原理
思考:
HBase每次数据写入和读取的底层是如何找到对应区域进行读取和写入的呢?
这里引出HBase的一个定位架构
1) .META.表和-ROOT-表
HBase0.96版本以前:三层架构
层次 | 名称 | 作用 |
---|---|---|
第一层 | Zookeeper文件 | 记录了-ROOT-表的位置信息 |
第二层 | -ROOT-表 | 记录了.META.表的Region位置信息,-ROOT-表只能有一个Region。通过-ROOT-表就可以访问.META.表中的数据 |
第三层 | .META.表 | 记录了用户数据表的Region位置信息,.META.表可以有多个Region,保存了HBase中所有用户数据表的Region位置信息 |
解析定位步骤:
1)meta表的Rowkey由表名、起始key、时间戳组成,如果起始key为空,则表示第一个Region。按照起始key排序使得行键不需要终止key就能表示范围。
2)值则是终止key、列族、列值,该RegionServer的地址等等。
3)meta表由于数据量过大可能被分割由多个RegionServer存储,因此又设置了-ROOT-表存放.META.表所有的Region,以及该Region所属的.META.表的位置。
因此,三层架构需要三次跳转才能获取到HRegion,如果缓存失效则需要6次,理论上三层架构最少能存储2ZB的数据
HBase0.96版本以后:双层架构
变成双层架构的原因:
三层架构使HBase最少存2ZB的数据,实际上根本用不到这么多,于是删除了-ROOT-表,只使用.META.表定位,.META.表的一个Region最多可以定位16TB的行键范围,假设一个行键范围包括10条数据,就已经是160TB了,假如一个Region大于128M,则足以存储更多的数据,因此根本不需要-ROOT-表
层次 | 名称 | 作用 |
---|---|---|
第一层 | Zookeeper文件 | 记录.META.表的Region位置信息 |
第二层 | .META.表 | 记录了用户数据表的Region位置信息,.META.表可以有多个Region,保存了HBase中所有用户数据表的Region位置信息 |
双层定位步骤:
1)客户端Client通过Zookeeper的/hbase/meta-region-server节点去查询哪个RegionServer上有.META.表
2)通过.META.表查询目标数据(Rowkey)所在的Region,再查询这个Region属于哪个RegionServer
最后,客户端会把.META.信息缓存起来,下次不用再重复操作
2)HBase的读写流程
读数据流程
1)客户端Client会从Zookeeper中获取.META.表位置信息,然后到对应的RegionServer获取该表,或者直接从缓存中读取该表
2)客户端从.META.表中获取行键所在的Region位置
3)先从Region的memstore读取,如果没有再从blockcache(读缓存)
4)如果blockcache(读缓存)也查询不到,再到HFile中查找,查找HFile之前先用布隆过滤器筛选出可能存在该行键的HFile
ps:从HFile读取到的数据会复制一份到blockcache中
写数据流程
1)客户端Client会从Zookeeper中获取.META.表的位置信息,然后到对应的RegionServer上获取该表,如果缓存有记录信息,则直接从缓存中读取该表
2)客户端会从.META.表中获取要写的数据存放的Region和所在的RegionServer
3)客户端会给数据设置版本号(默认为当前时间),然后往该RegionServer上的HLog写日志数据,同时往RegionServer的memstore写数据
4)当memstore达到阈值就会刷写到磁盘形成一个storefile小文件
5)当storefile小文件数量达到阈值就会将多个小storefile合并成一个大storefile文件
6)当storefile大文件的体积大小达到阈值又会进行split切分,等分分裂为两个storefile
四、HBase行键热点问题
产生行键热点问题的原因:
1)HBase的Region维护着每个Rowkey的起始key和终止key,当加入的数据的Rowkey范围符合该Region维护的范围,就会由该Region去维护这个Rowkey,从而可能会出现某个Region维护的Rowkey过多,其他Region维护Rowkey过少的现象就会导致行键热点问题
2)HBase在HDFS中的路径结构如下:名空间/表名/region名/列族 名/文件名
从这个路径可以看出,每张表会被划分为多个Region,实际上这些些region会被平均分配到多个节点上,如果某个时间点有大量的请求都落在某个单一region上,则会加重该节点的负担,严重时甚至导致死机。
region将表按rowkey进行固定大小的划分,范围内的数据到达一个阀值就会生成一个新的region,
因此hbase的热点问题也可以说是行键的热点问题。
总结:有点类似MR的数据倾斜原因。HBase中某个Region维护的Rowkey特别多,处理的IO请求特别多,而其他的Region维护和处理比较少,就会导致某个Region节点压力过大,造成死机等不好的影响。这个现象就称为行键的热点问题。
解决方案
1)反转
将行键和列值互换,但是这样也会让Rowkey失去有序性
2)加盐
将每一个Rowkey加一个前缀,前缀使用一些随机字符,使得数据分散在多个不同的Region,达到Region负载均衡的目标
ps:前缀是按照ASCLL码一个前缀最多能有128种字节,可以根据业务需求限制随机范围,128种前缀对应128个节点随机分配,一般生产环境已经足够使用
3)Hash
用Hash散列来替代随机加盐前缀的好处是能让一个给定的行有相同的前缀,这在分散Region负载的同时,使读操作也能推断。确定性Hash(如md5后取前4位做前缀)能让客户端重建完整的Rowkey,可以使用get操作直接get想要的行
五、HBase的高可用(宕机恢复)
1)HMaster通过zk保持对外单服务,HReigionServer则通过Hlog保证以外宕机时内存数据丢失恢复。
2)HRegionServer意外宕机时,HMaster首先把原本分配给该节点的region(存储在hdfs上的文件都会有备份)分配给其它节点,然后尝试读取宕机节点的Hlog,将数据写入region。
3)读取日志进行恢复的机制随版本不断变化,一开始是性能最低的LogSplitting机制,后来采用Distributed Log Splitting机制,最后是Distributed Log Replay机制。
六、Hbase的优化
1)行键优化
1)唯一原则:
行键不能重复
2)长度原则:
越短越好,最好是8的倍数。如果行键过长,寻址过程就会消耗更多的时间
举例:
其一是HBase的持久化文件HFile是按照KeyValue存储的,如果Rowkey过长比如500个字节,1000万列数据光Rowkey就要占用 500*1000万=50亿个字节,将近1G数据,这会极大影响HFile的存储效率
其二是MemStore缓存部分数据到内存,如果Rowkey字段过长内存的有效利用率会降低,系统无法缓存更多的数据,这会降低检索效率
3)散列原则:
尽量将数据分散在多个Region中,避免热点问题
2)列族优化
1)长度尽量小,最好是单字节。因为列式存储必带列族名
2)数量尽量少,控制在2个以内。因为HBase按region拆表,而region按列族把列拆成多个store,当region整体达到阀值时会拆分region,因此当两个列族数据大小差距悬殊时会导致数据量很小的列族数据也被迫参与拆分,该列族数据分散太多。最终查询该列族数据时,不得不请求多个region。
3)协处理器Coprocessor
hbase0.92版本之后支持协处理器,恶意为表埋钩子代码,当条件符合时自动触发钩子,大幅降低用户端的维护难度。
如:可以利用协处理器建立二级索引
步骤:
1)创建一个类继承观察者类,重写其中的preput方法,在插入数据到本表前会先执行该方法,自定义地将数据插入到索引表。
2)打包上传到HDFS上,用HBase shell命令加载该协处理器到表中
4)预分区
1)手动设置分区点
2)生成16进制序列预分区
3)按照文件中设置的规则预分区
4)使用java API
5)其他优化
关闭自动刷写:HTable.setAutoFlushTo(false)
增加写入缓存:HTable.setWriteBufferSize(writeBufferSize)
不使用WAL:Put.setWriteToWAL(false)
压缩传输:hcd.setCompressionType(Algorithm.SNAPPY)
设置最大版本数:HColumnDescriptor.setMaxVersions(int maxVersions)
设置生命周期:HColumnDescriptor.setTimeToLive(int timeToLive)
未完待续,往后会补充HBase具体的API操作,以及HBase整合Hive、HBase整合Pheonix