目录
HBase的基础架构
1. HMaster:主节点。负责分配region,以及管理HRegionServer。
2. HRegionServer:从节点。负责管理region。
HBase的主节点有一个或多个,其中一个是活动节点,其他是备用节点。在zookeeper集群中抢占到唯一一个代表active master锁的就是活动节点,其他备用节点对zookeeper集群中的这个锁设置观察,等待锁释放,以伺机抢占锁。
从节点启动时,在zookeeper上的server目录下建立代表自己的临时节点。由于主节点订阅了server目录上的变更消息,当server目录下的文件出现新增或删除操作时,主节点可以得到来自zookeeper的实时通知。因此一旦从节点上线,主节点能马上得到消息。
HBase的底层原理
参考资料
HMaster如何分配region
1 扫描zookeeper上的server父节点,获得当前可用的region server列表。
2 和每个region server通信,获得当前已分配的region和region server的对应关系。
3 扫描.META.region的集合,计算得到当前还未分配的region,将他们放入待分配region列表。
4 有一个region server上有可用空间时,master就给这个region server发送一个装载请求,把region分配给这个region server。region server得到请求后,就开始对此region提供服务。
5 当region server下线时,它和zookeeper的会话断开,zookeeper自动释放代表这台region server的独占锁。master就可以确定region server无法继续为它的region提供服务了,此时master会将这台region server的region分配给其它还活着的region server。
HBase的Region定位
在一个RegionServer中可以管理多个不同的region。Hbase表会根据RowKey值被切分成不同的分片region,region是Hbase负载均衡的最小单元。最小单元就表示不同的region可以由不同的Region server管理,但一个region只能由一个region server管理。
在HBase 0.96以前,HBase有两个特殊的表:-ROOT-和.META,其中-ROOT表的位置存储在ZooKeeper,它存储了.META表的RegionInfo信息,并且它只能存在一个HRegion,而.META表则存储了用户所有表的RegionInfo信息,它可以被切分成多个HRegion,因而对第一次访问用户Table流程如下:
- 从ZooKeeper中读取-ROOT所在HRegionServer,缓存该位置信息
- 从该HRegionServer中根据请求的TableName,RowKey读取.META所在HRegionServer,缓存该位置信息
- 从该HRegionServer中读取.META. Table的内容而获取此次请求需要访问的HRegion所在的位置,缓存该位置信息
- 访问该HRegionSever,获取请求的数据
这需要三次请求才能找到用户Table所在的位置,然后第四次请求开始获取真正的数据。当然为了提升性能,客户端会缓存-ROOT和.META的位置信息以及内容。
可是即使客户端有缓存,在初始阶段需要三次请求才能找到用户Table真正所在的位置,性能比较低下。实际上现在的HRegion的最大大小都会设置的比较大,一个HRegion完全装得下完整的.meta表,所以HBase 0.96以后去掉了-ROOT- Table,只剩下这个特殊的目录表叫做Meta Table(hbase:meta),它存储了集群中所有用户HRegion的位置信息,而ZooKeeper的节点中(/hbase/meta-region-server)存储的则直接是这个Meta Table的位置,并且这个Meta Table和以前的-ROOT- Table一样是不可split的,只存在一个HRegion中。这样,客户端在第一次访问用户Table的流程就变成了:
- 从ZooKeeper(/hbase/meta-region-server)中读取hbase:meta所在的HRegionServer,缓存该位置信息。
- 从该HRegionServer中读取hbase:meta的内容而获取此次请求需要访问的HRegion所在的HRegionServer,缓存该位置信息。
- 访问该HRegionSever,获取请求的数据
从这个过程中,我们发现客户会缓存这些位置信息,然而第二步它只是缓存当前RowKey对应的HRegion的位置,因而如果下一个要查的RowKey不在同一个HRegion中,则需要继续查询hbase:meta所在的HRegion,然而随着时间的推移,客户端缓存的位置信息越来越多,以至于不需要再次查找hbase:meta表的信息,除非某个HRegion因为宕机或Split被移动,此时需要重新查询并且更新缓存。
写机制
客户端定位到相应的region server后,开始将数据写入到对应的region里面去。
第一步将数据写入HLog中
HLog是HBase的日志文件,类似于MySQL的binlog。当对HBase写数据的时候,数据不是直接写进磁盘,它会在内存中保留一段时间(时间以及数据量阈值可以设定)。但把数据保存在内存中很有可能引起数据丢失,为了解决这个问题,数据会先写在一个叫做HLog(Write-Ahead logfile)的文件中,然后再写入内存中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。HLog文件是存储在HDFS上的,也就是说,region server直接操作HDFS写HLog。
第二步写memoryStore。这两步写入成功,数据就算写入成功。
写memoryStore过程涉及到HBase的三个重要机制:flush、compact、split
flush
memoryStore(In Memory Sorted Buffer)是写缓存,位于region server上的内存存储,用来保存当前的数据操作,所以当数据保存在HLog中之后,RegsionServer会在内存中存储键值对。一个region对应多个memoryStore,一个memoryStore对应一个列族。数据一直往memoryStore里面写,等到memoryStore写到一定的阈值的时候,启动线程进行flush过程,flush到HDFS中,形成storeFile。
flush涉及属性:
hbase.hregion.memstore.flush.size:134217728
即:128M就是当前region对应所有Memstore大小总和的默认阈值
hbase.regionserver.global.memstore.upperLimit:0.4
hbase.regionserver.global.memstore.lowerLimit:0.38
即:这两个参数是全局所有的Memstore大小总和所占内存百分比,Memstore大小总和超过0.4,就开始由大到小依次flush Memstore,直到Memstore大小总和小于0.38为止
compact
storeFile是store中小的HFile格式的文件。
数据持久化存储的体现形式是Hfile,存放于DataNode中,被ResionServer以region的形式进行管理。
store就是region中对应列族的存储数据,来自同一个memoryStore的所有storeFile就包含了同一个列族的数据。随着storeFile的增多,region server 将 region内所有的,不仅限于同一个列族的 storeFile合并成为一个大的HFile,这一个过程叫做compact机制,适应了hdfs擅长保存大文件的特点。
在storeFile合并的过程当中,标记为Deleted的Cell会被删除,已经过期的、超过最多版本数的Cell会被丢弃。HBase中,删除一条记录并不是修改HFile里面的内容,而是写新的文件,待HBase做合并的时候,把这些文件合并成一个HFile,用时间比较新的文件覆盖旧的文件。HBase这样做的根本原因是,HDFS不支持修改文件。
split
region的大小是有上限的,其阈值由hbase.hregion.max.filesize指定,默认为10G。regoin中的HFile越来越大,一旦达到阈值,region server会将region切开,分为两个region,这是split机制。
HRegionServer拆分region的步骤是,先将该region下线,然后拆分,将其子region加入到hbase:meta表中,再将他们加入到原本的HRegionServer中。region server 更新 znode 的状态为SPLIT。master就能知道状态更新了,master的平衡机制会判断是否需要把子region 分配到其他region server 中。在split之后,meta和HDFS依然会有引用指向父region. 当compact 操作发生在子regions中,会重写数据文件,这个时候引用就会被逐渐的去掉。垃圾回收任务会定时检测子regions是否还有引用指向父文件,如果没有引用指向父文件的话,父region 就会被删除。
读机制
客户端定位到相应的region server后,开始访问对应的HRegionServer。
HRegionServer优先扫描的顺序是BlockCache、memoryStore、storeFile和HFile,获取数据后将数据合并,响应给客户端。
其中StoreFile的扫描先会使用Bloom Filter过滤那些不可能符合条件的数据,然后使用Block Index快速定位Cell,并将其加载到BlockCache中,然后从BlockCache中读取。BlockCache是读缓存,位于region server上的内存存储,利用了“引用局部性”原理,将数据预读取到内存中,以提升读的性能。“引用局部性”分为空间局部性和时间局部性。空间局部性是指CPU在某一时刻需要某个数据,那么有很大的概率在一下时刻它需要的数据在其附近;时间局部性是指某个数据在被访问过一次后,它有很大的概率在不久的将来会被再次的访问。
对HBase的二级索引的理解
HBase的一级索引是rowkey(行键),在hbase客户端直接做条件查询时,只能通过get或scan查询指定的rowkey。如果想以column(字段)内容作为查询条件,往往要通过MapReduce/Spark等分布式计算框架进行,硬件资源消耗和时间延迟都会比较高。为了让HBase的数据查询更高效、适应更多的场景, 诸如使用非rowkey字段检索也能做到秒级响应,或者支持各个字段进行模糊查询和多字段组合查询等, 因此需要在HBase上面构建二级索引, 以满足现实中更复杂多样的业务需求。
常用的解决方案是以空间换时间,把常用查询字段作为二级索引,将该字段下的数据冗余保存一份到es或者solr里面去,作为二级索引表。查询的时候首先查索引表,获取行键后再到hbase查询详情。