Hbase
1 HBase的概念
HBase,基于Google Bigtable实现的开源、分布式、可伸缩的列式存储数据库,诞生于Hadoop,也是Hadoop生态的重要一环,如今作为一个Apache顶级项目,早已经不能将其仅仅看作Hadoop的一部分,基于Storm,Spark等框架的数据处理方案中,都有它的身影,可以说它已经成为大数据工具箱中非常重要的一种数据存储工具,也因此必然会被很纳入很多人学习计划。
Hbase的元数据放在zookeeper,真实数据放在hdfs上。
命名空间(namespace):0.96版本开始支持,是对多个表的逻辑分组,类似于关系数据库的database。
1.1 HBase 这个 NoSQL 数据库的要点
① 它介于 NoSQL 和 RDBMS 之间,仅能通过主键(rowkey)和主键的范围来检索数据
② HBase 查询数据功能很简单,不支持 join 等复杂操作
③ 不支持复杂的事务,只支持行级事务(可通过 hive 支持来实现多表 join 等复杂操作)。
④ HBase 中支持的数据类型:byte[](底层所有数据的存储都是字节数组)
⑤ 主要用来存储结构化和半结构化的松散数据。
1.2 HBase 中的表特点
1、大:一个表可以有上十亿行,上百万列
2、面向列:面向列(族)的存储和权限控制,列(簇)独立检索。
3、稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。
4、无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列
1.3 Rowkey的概念
Rowkey的概念和mysql中的主键是完全一样的,Hbase使用Rowkey来唯一的区分某一行的数据。
由于Hbase只支持3中查询方式:
1、基于Rowkey的单行查询
2、基于Rowkey的范围扫描
3、全表扫描
因此,Rowkey对Hbase的性能影响非常大,Rowkey的设计就显得尤为的重要。设计的时候要兼顾基于Rowkey的单行查询也要键入Rowkey的范围扫描。具体Rowkey要如何设计后续会整理相关的文章做进一步的描述。这里大家只要有一个概念就是Rowkey的设计极为重要。
rowkey 行键可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),最好是 16。在 HBase 内部,rowkey 保存为字节数组。HBase 会对表中的数据按照 rowkey 排序 (字典顺序)
1.4 HBase写数据流程
1、Client先访问zookeeper,从meta表获取相应region信息,然后找到meta表的数据
2、从namespace、表名和rowkey根据meta表的数据找到写入数据对应的region信息
3、找到对应的regionserver
4、把数据分别写到HLog和MemStore上一份
5、MemStore达到一个阈值后则把数据刷成一个StoreFile文件。(若MemStore中的数据有丢失,则可以从HLog上恢复)
6、当多个StoreFile文件达到一定的大小后,会触发Compact合并操作,合并为一个StoreFile,(这里同时进行版本的合并和数据删除。)
7、当Storefile大小超过一定阈值后,会把当前的Region分割为两个(Split),并由Hmaster分配到相应的HRegionServer,实现负载均衡
1.5 HBase读数据流程
1、Client先访问zookeeper,从meta表读取region的位置,然后读取meta表中的数据。meta中又存储了用户表的region信息。
2、根据namespace、表名和rowkey在meta表中找到对应的region信息
3、找到这个region对应的regionserver
4、查找对应的region
5、先从MemStore找数据,如果没有,再到StoreFile上读(为了读取的效率)。
2 HBase的RowKey设计
2.1 Rowkey长度原则
Rowkey是一个二进制码流,Rowkey的长度被很多开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节。
原因如下:
(1)数据的持久化文件HFile中是按照KeyValue存储的,如果Rowkey过长比如100个字节,1000万列数据光Rowkey就要占用100*1000万=10亿个字节,将近1G数据,这会极大影响HFile的存储效率;
(2)MemStore将缓存部分数据到内存,如果Rowkey字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。因此Rowkey的字节长度越短越好。
(3)目前操作系统是都是64位系统,内存8字节对齐。控制在16个字节,8字节的整数倍利用操作系统的最佳特性。
2.2 Rowkey散列原则
如果Rowkey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个Regionserver实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个 RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer,降低查询效率。
2.3 Rowkey唯一原则
必须在设计上保证其唯一性。
问:你们公司hbase遇到过数据倾斜(热点问题)吗?你们是怎么解决的?
答: 有,我们在md5的方式在row key上加上一个随机数,只要region所管理的start-end keys范围比较随机,往region上写数据时就不会按照自下而上的方式写数据,而是随机的写,这样就解决了数据热点问题。
3 HBase架构组成
HBase采用Master/Slave架构搭建集群,它隶属于Hadoop生态系统,由以下类型节点组成:HMaster节点、HRegionServer节点、ZooKeeper集群,而在底层,它将数据存储于HDFS中,因而涉及到HDFS的NameNode、DataNode等,总体结构如下:
HMaster与HRegionServer进行心跳感知!!!
其中
3.1 HMaster节点用于:
1、管理HRegionServer,实现其负载均衡。
2、管理和分配HRegion,比如在HRegion split时分配新的HRegion;在HRegionServer退出时迁移其内的HRegion到其他HRegionServer上。
3、实现DDL操作(Data Definition Language,namespace和table的增删改,column familiy的增删改等)。
4、管理namespace和table的元数据(实际存储在HDFS上)。
5、权限控制(ACL)。
3.2 HRegionServer节点用于:
1、存放和管理本地HRegion。
2、读写HDFS,管理Table中的数据。
3、Client直接通过HRegionServer读写数据(从HMaster中获取元数据,找到RowKey所在的HRegion/HRegionServer后)。
HRegionServer一般和DataNode在同一台机器上运行,实现数据的本地性。HRegionServer包含多个HRegion,由WAL(HLog)、BlockCache、MemStore、HFile组成。
WAL即Write Ahead Log,在早期版本中称为HLog,它是HDFS上的一个文件,如其名字所表示的,所有写操作都会先保证将数据写入这个Log文件后,才会真正更新MemStore,最后写入HFile中。采用这种模式,可以保证HRegionServer宕机后,我们依然可以从该Log文件中读取数据,Replay所有的操作,而不至于数据丢失。这个Log文件会定期Roll出新的文件而删除旧的文件(那些已持久化到HFile中的Log可以删除)。WAL文件存储在/hbase/WALs/${HRegionServer_Name}的目录中(在0.94之前,存储在/hbase/.logs/目录中),一般一个HRegionServer只有一个WAL实例,也就是说一个HRegionServer的所有WAL写都是串行的(就像log4j的日志写也是串行的),这当然会引起性能问题,因而在HBase 1.0之后,通过HBASE-5699实现了多个WAL并行写(MultiWAL),该实现采用HDFS的多个管道写,以单个HRegion为单位。关于WAL可以参考Wikipedia的Write-Ahead Logging。顺便吐槽一句,英文版的维基百科竟然能毫无压力的正常访问了,这是某个GFW的疏忽还是以后的常态?
BlockCache是一个读缓存,即“引用局部性”原理(也应用于CPU,分空间局部性和时间局部性,空间局部性是指CPU在某一时刻需要某个数据,那么有很大的概率在一下时刻它需要的数据在其附近;时间局部性是指某个数据在被访问过一次后,它有很大的概率在不久的将来会被再次的访问),将数据预读取到内存中,以提升读的性能。HBase中提供两种BlockCache的实现:默认on-heap LruBlockCache和BucketCache(通常是off-heap)。通常BucketCache的性能要差于LruBlockCache,然而由于GC的影响,LruBlockCache的延迟会变的不稳定,而BucketCache由于是自己管理BlockCache,而不需要GC,因而它的延迟通常比较稳定,这也是有些时候需要选用BucketCache的原因。这篇文章BlockCache101对on-heap和off-heap的BlockCache做了详细的比较。
HRegion是一个Table中的一个Region在一个HRegionServer中的表达。一个Table可以有一个或多个Region,他们可以在一个相同的HRegionServer上,也可以分布在不同的HRegionServer上,一个HRegionServer可以有多个HRegion,他们分别属于不同的Table。
HRegion由多个Store(HStore)构成,每个HStore对应了一个Table在这个HRegion中的一个Column Family,即每个Column Family(列族)就是一个集中的存储单元,因而最好将具有相近IO特性的Column存储在一个Column Family(列族),以实现高效读取(数据局部性原理,可以提高缓存的命中率)。HStore是HBase中存储的核心,它实现了读写HDFS功能,一个HStore由一个MemStore 和0个或多个StoreFile组成。
MemStore是一个写缓存(In Memory Sorted Buffer),所有数据的写在完成WAL日志写后,会写入MemStore中,由MemStore根据一定的算法将数据Flush到地层HDFS文件中(HFile),通常每个HRegion中的每个 Column Family有一个自己的MemStore。
HFile(StoreFile) 用于存储HBase的数据(Cell/KeyValue)。在HFile中的数据是按RowKey、Column Family、Column排序,对相同的Cell(即这三个值都一样),则按timestamp倒序排列。
3.3 ZooKeeper集群是协调系统,用于:
1、存放整个 HBase集群的元数据以及集群的状态信息。
2、实现HMaster主从节点的失效备援 。
ZooKeeper为HBase集群提供协调服务,它管理着HMaster和HRegionServer的状态(available/alive等),并且会在它们宕机时通知给HMaster,从而HMaster可以实现HMaster之间的失效备援,或对宕机的HRegionServer中的HRegion集合的修复(将它们分配给其他的HRegionServer)。ZooKeeper集群本身使用一致性协议(PAXOS协议)保证每个节点状态的一致性。
3.4 How The Components Work Together?
ZooKeeper协调集群所有节点的共享信息,在HMaster和HRegionServer连接到ZooKeeper后创建Ephemeral节点,并使用Heartbeat机制维持这个节点的存活状态,如果某个Ephemeral节点失效,则HMaster会收到通知,并做相应的处理。
另外,HMaster通过监听ZooKeeper中的Ephemeral节点(默认:/hbase/rs/*)来监控HRegionServer的加入和宕机。在第一个HMaster连接到ZooKeeper时会创建Ephemeral节点(默认:/hbasae/master)来表示Active的HMaster,其后加进来的HMaster则监听该Ephemeral节点,如果当前Active的HMaster宕机,则该节点消失,因而其他HMaster得到通知,而将自身转换成Active的HMaster,在变为Active的HMaster之前,它会创建在/hbase/back-masters/下创建自己的Ephemeral节点。
HBase Client通过RPC方式和HMaster、HRegionServer通信;一个HRegionServer可以存放1000个HRegion;底层Table数据存储于HDFS中,而HRegion所处理的数据尽量和数据所在的DataNode在一起,实现数据的本地化;数据本地化并不是总能实现,比如在HRegion移动时,需要等下一次Compact才能继续回到本地化。
Hbase中的meta表放在zookeeper里,meta表里有info元数据,当执行start-hbase.sh的时候,会将zookeeper里存放的meta表结构信息放入内存,然后将hdfs上的真实数据组合起来!!!
本着半翻译的原则,再贴一个《An In-Depth Look At The HBase Architecture》的架构图:
这个架构图比较清晰的表达了HMaster和NameNode都支持多个热备份,使用ZooKeeper来做协调;ZooKeeper并不是云般神秘,它一般由三台机器组成一个集群,内部使用PAXOS算法支持三台Server中的一台宕机,也有使用五台机器的,此时则可以支持同时两台宕机,既少于半数的宕机,然而随着机器的增加,它的性能也会下降;RegionServer和DataNode一般会放在相同的Server上实现数据的本地化。
3.5 HRegion:
HBase使用RowKey将表水平切割成多个HRegion,从HMaster的角度,每个HRegion都纪录了它的有且仅有一个StartKey和有且仅有一个EndKey(第一个HRegion的StartKey为空,最后一个HRegion的EndKey为空),由于RowKey是排序的(环形缓冲区进行字典排序或者是LinkedHashmap排序,每个HRegion里的RowKey是有序的,各个HRegion相对是无序的),因而Client可以通过HMaster快速的定位每个RowKey在哪个HRegion中。HRegion由HMaster分配到相应的HRegionServer中,然后由HRegionServer负责HRegion的启动和管理,和Client的通信,以及负责数据的读(使用HDFS)。每个HRegionServer可以同时管理1000个左右的HRegion(这个数字怎么来的?没有从代码中看到限制,难道是出于经验?超过1000个会引起性能问题?
来回答这个问题:感觉这个1000的数字是从BigTable的论文中来的(5 Implementation节):Each tablet server manages a set of tablets(typically we have somewhere between ten to a thousand tablets per tablet server))。
4 META表结构
在 HBase Shell 里对meta表进行 scan 和 describe :
可以看出,meta表的结构如下:
meta表中每一行记录了一个Region的信息。
1) RowKey
RowKey就是Region Name,它的命名形式是TableName,StartKey,TimeStamp.Encoded.。
其中 Encoded 是TableName,StartKey,TimeStamp的md5值。
例如:
mytable,,1438832261249.ea2b47e1eba6dd9a7121315cdf0e4f67.
表名是mytable,StartKey为空,时间戳是1438832261249,前面三部分的md5是:
$ echo -n "mytable,,1438832261249" | md5sum # -n选项表示不输出换行符
ea2b47e1eba6dd9a7121315cdf0e4f67 -
2) Column Family
.META.表有两个Column Family:info 和 historian。
其中info包含了三个Column:
- regioninfo:region的详细信息,包括StartKey、EndKey以及Table信息等等。
- server:管理该region的 RegionServer 的地址。
- serverstartcode:RegionServer 开始托管该region的时间。
至于historian:
That was a family used to keep track of region operations like open,
close, compact, etc. It proved to be more troublesome than handy so we
disabled this feature until coming up with a better solution. The
family stayed for backward compatibility.
大致的意思是:这个Column Family是用来追踪一些region操作的,例如open、close、compact等。事实证明这非常的麻烦,所以在想出一个更好的解决方案之前我们禁用了此功能。这个列族会保持向后兼容。
综上所述,.META.表中保存了所有用户表的region信息,在进行数据访问时,它是必不可少的一个环节。当Region被拆分、合并或者重新分配的时候,都需要来修改这张表的内容来保证访问数据时能够正确地定位region。
5 进入HBase命令行
hbase shell 进入命令行模式
6 过滤器
1、SingleColumnValueFilter
SingleColumnValueFilter 用于测试值的情况(相等,不等,范围 、、、)
2、SingleColumnValueExcludeFilter
跟SingleColumnValueFilter 功能一样,只是不查询出该列的值。
3、FamilyFilter
用于过滤列族(通常在 Scan 过程中通过设定某些列族来实现该功能,而不是直接使用该过滤器)。
4、QualifierFilter
用于列名(Qualifier)过滤
5、ColumnPrefixFilter
用于列名(Qualifier)前缀过滤,即包含某个前缀的所有列名。
6、RowFilter
行键过滤器,一般来讲,执行 Scan 使用 startRow/stopRow 方式比较好,而 RowFilter 过滤器也可以完成对某一行的过滤。
7、RandomRowFilter
该过滤器是随机选择一行的过滤器。参数 chance 是一个浮点值,介于 0.1 和 1.0 之间。
8、PageFilter
用于按行分页。