1. 简介
HBase是大数据组件中No-SQL数据库的典型代表,提供了高可靠、可伸缩、高性能、面向列的分布式存储。HBase逻辑上引入了Row-ColumnFamily-Qualifier(限定符)-Value-TimeStamp(Version)的概念,Value在数据库中以Byte[]数组存在,且Vaule允许为空。与关系型数据库相比,HBase相当于一个巨大的松散嵌套Map,因此对HBase进行横向扩展是极其方便的,可在廉价物理机上搭建并根据数据量的增多而横向扩展,为海量数据提供高性能的可靠存储。
HBase的数据写入首先将数据缓存在内存中,达到阈值后再顺序刷写磁盘的方式(所有的Write/Update均为Append磁盘文件的方式),避免了随机写入造成的性能低下,同时HBase以HDFS为最终的存储层,利用HDFS的特性保证了数据的容灾性。HBase的数据读取提供了服务端缓存、BloomFilter(布隆过滤)等机制,保证了随机读取的性能。
2. HBase与RDBMS的对比
HBase作为NoSql数据库,采用分布式存储在读写速度方面比RDBMS数据库快,且横向扩展能力强,当存储空间和读写速度遇到瓶颈时,通过增添机器横向扩建,存储空间和读写速度都能够得到很大提升。同时HBase采用列式存储,且结构松散,允许某列为空,可以存储半结构化数据。但这些都是有代价的,是以牺牲事务为代价的,且RDBMS支持多种数据类型,可以对任意列建立索引,支持事务,支持SQL语言,可以在多个表之间进行Join等操作,只需要一条SQL语句即可,同时支持表锁、页锁与行锁等。与之相比,HBase仅支持字节数组数据结构,仅支持RowKey索引,不支持多表Join,原生不支持SQL语言,需要通过外部插件才能实现对SQL语言的支持。
因此在选择时,需要根据具体的业务需要,从多方面综合考虑来选取最终的存储方案。
3. HBase架构
HBase架构中,整体上分为Clien——Zookeeper——Master——RegionServer四部分,而RegionServer内部又分为Region——HLog——BlockCache(上图中未标出),Region中又根据ColumnFamily分为多个Store(每个ColumnFaily对应一个Store),Store中包含MemStore和多个StoreFile,每个StoreFile中存在一个HFile,HFile存在HDFS中,最终的数据即在HFile中。
3.1 Client
创建Client时只需要提供Zookeeper地址即可(为了保证高可用,通常提供Zookeeper的物理机地址不只一个),Client与Zookeeper通信后可获得HBase集群中的其他物理机地址。Client通过HBase提供的HTable、Put、Get、Filter、BloomFilter、HBaseAdmin等Java类接口(HBase也提供了其他编程语言接口以及RestFul API),可以实现对HBase的建表、增删改查,以及客户端过滤Filter和服务端过滤BloomFilter(可进一步减少查询结果数据量,减少网络通信和磁盘IO)。同时Client中的一些相应配置(Baching配置等)与HBase的读写性能密切相关,在Client端程序需要综合考虑多种配置,选用最优配置,调用最优接口,从而保证HBase的读写性能。
3.2 Zookeeper
Zookeeper在HBase中扮演了极其重要的角色:
- 为了保证集群的高可用,减轻单点故障对集群造成的影响,HBase集群中Maser一般为多台,但只有一台为Active Maser,当Master宕机时,Zookeeper会根据选举算法重新选举出另一台Master。
- HBase集群中每台RegionServer周期性的向Zookeeper发送心跳包,当某台RegionServer宕机时,Zookeeper在设定时间内未收到心跳包即判定为宕机,然后通知Master将宕机RegionServer上的Region重新分配到其他正常RegionServer机器中。
- Zookeeper集群中保存了HBase的元数据信息,包括-Root-表存放的RegionServer位置,HBase集群表信息、Region信息等。
- 在Client对HBase集群进行读写时,Client并不与Master通信,而是通过Zookeeper获得-Root-表的存放地址,再由-Root-表获得对应的-Meta-表存放位置,最后通过-Meta-表获得读写Region的存放位置,从而完成读写过程。(Client——Root表——Meta表——数据最终存储地址,Client共需要跳转三次即可。-Root-表主要是为了防止数据量过大造成-Meta-表过大影响读写性能而设计的,但在HBase的0.96.x以后,由于考虑到正常情况下-Meta-表即可满足大多数数据需求,因此取消了-Root-表,使原来的读写过程从跳转三次,减少到调整两次,提供了HBase集群性能)
3.3 Master
Master主要是对RegionServer进行管理,包括将Region分配至RegionServer,HBase集群的负载均衡、在某个RegionServer宕机后,将该机器上的Region分配至正常的RegionServer,同时根据故障机器上的HLog,在正常RegionServer上完成数据恢复(该工作是恢复故障机器上MemStore中的数据)。
由于Client除了新建/删除表时才会与Master通信,正常读写不需要与Master通信,因此Master并不会成为HBase集群性能的瓶颈,即使在Master故障的情况下,也不会影响Client对HBase集群的读写,只会影响新建/删除表等操作。3.4 RegionServer
RegionServer是HBase集群中极其重要的部分,是数据的真正管理者,Client对HBase集群的读写均由RegionServer来完成,因此本文将RegionServer单独作为一小节来重点讲解。
4. RegionServer结构
4.1 HLog与MemStore
每个RegionServer中均包含一个HLog、一个BolckCache和多个Region,每个Region包含多个Store,每个Store对应Table的ColumnFamily。每个Store中包含一个MemStore缓存和多个StoreFile(HFile)。当Client向RegionServer写入数据时,RegionServer首先将数据写入HLog,HLog将数据连同数据对应的Store信息顺序写入磁盘。数据写入HLog后,RegionServer会将数据再写入对应Store的MemStore中进行缓存,当MemStore缓存满(默认为64M大小),MemStore会将数据溢出到磁盘形成临时文件,最后会将这些临时文件合并成为HFile文件。当数据从MemStore溢出到磁盘完成持久化后,HLog对应的数据则会被删除,从而保证HLog文件不会越来越大。之所以设计HLog机制,是为了在RegionServer宕机时,防止MemStore缓存内还未来得及溢出到磁盘的数据被丢失,由于HLog的存在,即使RegionServer宕机,也可通过HLog文件将MemStore缓存中的数据进行恢复。而MemStore则是通过对数据先缓存,待达到一定数据量后再集中溢出磁盘,从而减少了磁盘IO,提升了写入速度。
用户可以通过设置将HLog机制关闭(针对可以容忍小部分数据丢失的场景),这样可以提升HBase2~3倍的写入性能。同时由于MemStore作为内存缓存,属于JVM内存的一部分,MemStore的大小设置对HBase的写入性能也有一定的影响,需要根据不同的应用场景综合考虑。
4.2 BlockCache
每个HRegionServer中存在一个BlockCache。相对于HLog和MemStore,BlockCache很少被提及,但确是很重要的一种缓存。HBase在读取数据时,首先会从MemStore中查询数据(最新的数据保存在此),然后是从BlockCache中查询数据,当两者都为命中时,才会从HFile中读取数据。BlockCache用于缓存HFile中的热点数据,用于提升HBase的读取性能。与CPU中的一级缓存、二级缓存的原理类似,BlockCache将HFile中经常被读取的数据缓存至其中,当Client读取数据时,若数据被命中,则直接从BlockCache直接读取数据,减少从HFile读取的磁盘IO次数,从而提升HBase读取速度。
BlockCache的大致工作原理如上所述,其中涉及很多细节以及许多参数需要进行调优。首先与MemStore不同的是,BlockCache由于需要满足随机读取,因此BlockCache缓存中会产生大量内存碎片,由于BlockCache采用JVM中的内存,当内存碎片达到一定数量时即需要进行碎片整理,这个过程会导致HBase的读取性能显著下降,甚至产生Full GC问题导致RegionServer卡死或宕机。因此BlockCache的内存回收算法和内存分配机制是一个关键的性能调优点。
BlockCache默认采用JVM的内存,并使用LRU内存回收算法,因此回收内存时会产生性能问题,影响读取性能。因此,在HBase接下来的版本中,又引入了BucketBlockCache,并在该方式下设置了三种模式heap(JVM堆内存)、offheap(堆外内存)、file(SSD存储)。特别是BucketBlockCache的offheap模式,直接利用操作系统中的内存机制而非JVM堆内存,避免了内存回收产生的问题。应用中我们一般采用LRUBlockCache与BucketBlockCache(offheap模式)相结合的方式CombinedBlockCache,该方式下LRUBlockCache存储Index Block,BucketBlockCache存储Data Block(HFile的数据分为Index Block和Data Block等多种)。
BlockCahce的内存大小影响HBase的读取性能,MemStore影响HBase的写入性能,同时HBase规定BlockCache与MemStore的内存大小之和不得大于JVM总内存的80%(为运算留足内存空间),因此我们需要针对不同的应用场景(读多写少/读少写多)来合理安排BlockCache与MemStore在JVM内存中所占比例,从而达到性能最优。
4.3 HFile
HBase中数据按照Table——Row——ColumnFamily——Qualifier——Value——TimeStamp(Version),对HBase的Write/Update操作,都会更新KeyValue的版本(版本一般采用TimeStamp时间戳)。用户可以配置每种ColumnFamily最大版本数和数据最长存储时间,Client读取数据时可设定需要读取的版本数。
HFile文件被切分为大小相同的Block,BlockSize可配置,默认64KB,BlockSize大有利于顺序Scan,BlockSize小有利于随机查询。
在HFile的众多Block中,DataBlock用来存储用户的KeyValue数据,Bloom相关的的Block主要用来完成BloomFilter。BloomFilter采用位图的方式,将Row/Row-ColumnFamily进行索引,当数据写入时将Rowd/Row-ColumnFamily的Hash值对应的位置1,当数据读取时首先去查找BloomFilter位图中相应位是否为1 ,为1则说明数据在该HFile中,去DataBlock读取数据,为0则说明数据不在该HFile中。通过BloomFilter可以减少磁盘IO次数,提升HBase读取性能。
ps: 参考文献及图片来源:有态度的HBase/Spark/BigData以及其他网络内容,未注名出处,望见谅