1.系统架构
1.1 图解
从HBase的架构图上可以看出,HBase中的组件包括Client、Zookeeper、HMaster、HRegionServer、HRegion、Store、MemStore、StoreFile、HFile、HLog等,每一个 RegionServer 就只有一个 HLog,而不是一个 Region 有一个 HLog。
1.2 client
HBase 有两张特殊表:
1).META.:记录了用户所有表拆分出来的的Region映射信息,.META.可以有多个 Regoin
2)-ROOT-:记录了.META.表的Region信息,-ROOT-只有一个Region,无论如何不会分裂
Client访问用户数据前需要首先访问ZooKeeper,找到-ROOT-表的 Region所在的位置,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过client端会做cache缓存。
因此,client包含了访问HBase的接口,另外Client还维护了对应的cache来加速HBase的访问,比如cache的.META.元数据的信息。
1.3 ZooKeeper
Hbase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。具体工作如下:
1)为HBase提供Failover机制,保障群众只有1个master在运行,如果有master异常,会通过竞争机制选举新的Master提供服务,避免单点Master单点故障问题
2)存储所有Region的寻址入口:-ROOT-表在哪台服务器上。-ROOT-这张表的位置信息
3)实时监控RegionServer的状态,当RegionServer有异常的时候,通过回调将RegionServer的上线和下线信息实时通知给Master
4)存储HBase的Schema,包括有哪些Table,每个Table有哪些Column Family
1.4 HMaster
1、为RegionServer 分配 Region
2、负责 RegionServer的负载均衡
3、发现失效的RegionServer并重新分配其上的Region。当RegionServer失效的时候,协调对应的Hlog的拆分
4、HDFS上的垃圾文件(HBase)回收
5、维护集群的元数据信息,处理Schema更新请求(表的创建,删除,修改,列簇的增加等等)
1.5 HRegionServer
HregionServer直接对接用户的读写请求,是真正的“干活”的节点。它的功能概括如下:
1)RegionServer维护Master分配给它的Region,处理对这些Region的IO请求
2)RegionServer负责Split在运行过程中变得过大的Region,负责Compact操作
3)负责和底层HDFS交互,存储数据到HDFS
4)负责Storefile合并工作
可以看到,client访问HBase上数据的过程并不需要master参与(寻址访问zookeeper和RegioneServer,数据读写访问RegioneServer),Master仅仅维护者Table和Region的元数据信息,负载很低。
.META. 存的是所有的Region的位置信息,那么RegioneServer 当中 Region在进行分裂之后的新产生的Region,是由Master来决定发到哪个RegioneServer,这就意味着,只有Master知道newRegion的位置信息,所以,由Master来管理.META.这个表当中的数据的CRUD,所以结合以上两点表明,在没有Region分裂的情况,Master宕机一段时间是可以忍受的。
1.6 HRegion
table在行的方向上分隔为多个Region。Region是HBase中分布式存储和负载均衡的最小单元,即不同的region可以分别在不同的Region Server上,但同一个Region是不会拆分到多个server上。
Region按大小分隔,每个表一般是只有一个region。随着数据不断插入表,region不断增大,当region的某个列族达到一个阈值时就会分成两个新的region。
每个region由以下信息标识:< 表名,startRowkey,创建时间>
由目录表(-ROOT-和.META.)记录该region的endRowkey
1.7 Store
每一个region由一个或多个store组成,至少是一个store,hbase会把一起访问的数据放在一个store里面,即为每个 ColumnFamily建一个store,如果有几个ColumnFamily,也就有几个Store。一个Store由一个memStore和0或者 多个StoreFile组成。HBase以store的大小来判断是否需要切分region
1.8 MemStore
memStore 是放在内存里的。保存修改的数据即keyValues。当memStore的大小达到一个阀值(默认128MB)时,memStore会被flush到文件,即生成一个快照。目前hbase会有一个线程来负责memStore的flush操作。
1.9 StoreFile
memStore内存中的数据写到文件后就是StoreFile,StoreFile底层是以HFile的格式保存。
1.10 HFile
HBase中KeyValue数据的存储格式,HFile是Hadoop的二进制格式文件,实际上StoreFile就是对Hfile做了轻量级包装,即StoreFile底层就是HFile
1.11 HLog
1.11.1 简介
HLog(WAL log):是HBase实现WAL(write ahead log)方式产生的日志,内部是一个简单的顺序日志,用来做灾难恢复使用。每个RegionServer对应一个Hlog,所有对于该RegionServer的写入都记录到Hlog中,一旦region server宕机,就可以从log中进行恢复。此外为了保证恢复的效率,Hbase会限制最大保存的Hlog数量,如果达到Hlog的最大个数(hase.regionserver.max.logs参数控制)的时候,就会触发强制刷盘操作。对于已经刷盘的数据,其对应的Hlog会有一个过期的概念,Hlog过期后,会被监控线程移动到.oldlogs,然后会被自动删除掉。
HLog文件就是一个普通的Hadoop Sequence File,Sequence File的value是key时HLogKey对象,其中记录了写入数据的归属信息,除了table和region名字外,还同时包括sequence number和timestamp,timestamp是写入时间,sequence number的起始值为0,或者是最近一次存入文件系统中的sequence number。Sequence File的value是HBase的KeyValue对象,即对应HFile中的KeyValue。
1.11.2 Hlog结构
下图是Hlog的详细结构:
从上图我们可以看出多个Region共享一个Hlog文件,单个Region在Hlog中是按照时间顺序存储的,但是多个Region可能并不是完全按照时间顺序。
每个Hlog最小单元由Hlogkey和WALEdit两部分组成。Hlogkey由sequenceid、timestamp、cluster ids、regionname以及tablename等组成,WALEdit是由一系列的KeyValue组成,对一行上所有列(即所有KeyValue)的更新操作,都包含在同一个WALEdit对象中,这主要是为了实现写入一行多个列时的原子性。
注意,sequenceid是一个store级别的自增序列号,非常重要,region的数据恢复和Hlog过期清除都要依赖sequenceid。
Memstore在达到一定的条件会触发刷盘的操作,刷盘的时候会获取刷新到最新的一个sequenceid的下一个sequenceid,并将新的sequenceid赋给oldestUnflushedSequenceId,并刷到Ffile中。举个例子来说明:比如对于某一个store,开始的时候oldestUnflushedSequenceId为NULL,此时,如果触发flush的操作,假设初始刷盘到sequenceid为10,那么hbase会在10的基础上append一个空的Entry到HLog,最新的sequenceid为11,然后将sequenceid为11的号赋给oldestUnflushedSequenceId,并将oldestUnflushedSequenceId的值刷到Hfile文件中进行持久化。
Hlog文件对应所有Region的store中最大的sequenceid如果已经刷盘,就认为Hlog文件已经过期,就会移动到.oldlogs,等待被移除。
当RegionServer出现故障的时候,需要对Hlog进行回放来恢复数据。回放的时候会读取Hfile的oldestUnflushedSequenceId中的sequenceid和Hlog中的sequenceid进行比较,小于sequenceid的就直接忽略,但与或者等于的就进行重做。回放完成后,就完成了数据的恢复工作。
1.11.3 Hlog的生命周期
Hlog从产生到最后删除需要经历如下几个过程:
1.11.3.1 产生
所有涉及到数据的变更都会先写Hlog,除非是你关闭了Hlog
1.11.3.2 滚动
Hlog的大小通过参数hbase.regionserver.logroll.period控制,默认是1个小时,时间达到hbase.regionserver.logroll.period设置的时间,Hbase会创建一个新的Hlog文件。这就实现了Hlog滚动的目的。Hbase通过hbase.regionserver.maxlogs参数控制Hlog的个数。滚动的目的,为了控制单个Hlog文件过大的情况,方便后续的过期和删除。
1.11.3.3 过期
Hlog的过期依赖于对sequenceid的判断。Hbase会将Hlog的sequenceid和Hfile最大的sequenceid(刷新到的最新位置)进行比较,如果该Hlog文件中的sequenceid比刷新的最新位置的sequenceid都要小,那么这个Hlog就过期了,过期了以后,对应Hlog会被移动到.oldlogs目录。
这里有个问题,为什么要将过期的Hlog移动到.oldlogs目录,而不是直接删除呢?答案是因为Hbase还有一个主从同步的功能,这个依赖Hlog来同步Hbase的变更,有一种情况不能删除Hlog,那就是Hlog虽然过期,但是对应的Hlog并没有同步完成,因此比较好的做法是移动到别的目录。再增加对应的检查和保留时间。
1.11.3.4 删除
如果Hbase开启了replication,当replication执行完一个Hlog的时候,会删除Zoopkeeper上的对应Hlog节点。在Hlog被移动到.oldlogs目录后,Hbase每隔hbase.master.cleaner.interval(默认60秒)时间会去检查.oldlogs目录下的所有Hlog,确认对应的Zookeeper的Hlog节点是否被删除,如果Zookeeper上不存在对应的Hlog节点,那么就直接删除对应的Hlog。
hbase.master.logcleaner.ttl(默认10分钟)这个参数设置Hlog在.oldlogs目录保留的最长时间。
1.12 HDFS
HDFS为HBase提供最终的底层数据存储服务,同事为HBase提供高可用(Hlog存储在HDFS)的支持,具体功能有:
提供元数据和表数据的底层分布式存储服务
数据多副本,提供的高可靠和高可用性
2 物理存储
2.1 整体的物理结构
1、Table 中的所有行都按照 RowKsey 的字典序排列。
2、Table 在行的方向上分割为多个 HRegion。
3、HRegion按大小分割的(默认10G),每个表一开始只有一个HRegion,随着数据不断插入表,HRegion不断增大,当增大到一个阀值的时候,HRegion就会等分会两个新的 HRegion。当表中的行不断增多,就会有越来越多的 HRegion。
4、HRegion 是Hbase中分布式存储和负载均衡的最小单元。最小单元就表示不同的HRegion可以分布在不同的HRegionserver 上。但一个 HRegion 是不会拆分到多个server 上的。
5、HRegion 虽然是负载均衡的最小单元,但并不是物理存储的最小单元。事实上,HRegion由一个或者多个Store 组成,每个 Store 保存一个 ColumnFamily。每个Strore 又由一个 memStore 和 0 至多个 StoreFile 组成
2.2 StoreFile 和 HFile 结构
StoreFile 以 HFile 格式保存在 HDFS 上,请看下图 HFile 的数据组织格式:
首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer 和 FileInfo。Trailer中有指针指向其他数据块的起始点。FileInfo中记录了文件的一些Meta信息,例如:AVG_KEY_LEN,AVG_VALUE_LEN,LAST_KEY,COMPARATOR, MAX_SEQ_ID_KEY 等。
HFile 分为六个部分:
Data Block 段–保存表中的数据,这部分可以被压缩
Meta Block 段(可选的)–保存用户自定义的kv对,可以被压缩。
File Info 段–Hfile的元信息,不被压缩,用户也可以在这一部分添加自己的元信息。
Data Block Index 段–Data Block的索引。每条索引的 key 是被索引的 block 的第一条记录的 key。
Meta Block Index 段 (可选的)–Meta Block 的索引。
Trailer 段–这一段是定长的。保存了每一段的偏移量,读取一个 HFile时,会首先读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index 会被读取到内存中,这样,当检索某个key时,不需要扫描整个 HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个block读取到内存中,再找到需要的key。DataBlock Index 采用 LRU 机制淘汰。
HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络 IO 和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。目标Hfile的压缩支持两种方式:Gzip,LZO。
Data Index 和 Meta Index 块记录了每个 Data 块和 Meta 块的起始点。
Data Block是HBase I/O的基本单元,为了提高效率,HRegionServer中有基于 LRU 的 Block Cache机制。每个Data块的大小可以在创建一个 Table的时候通过参数指定,大号的Block有利于顺序 Scan,小号Block利于随机查询。每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成,Magic内容就是一些随机数字,目的是防止数据损坏。
HFile 里面的每个KeyValue对就是一个简单的byte数组。但是这个 byte数组里面包含了很多项,并且有固定的结构。我们来看看里面的具体结构:
开始是两个固定长度的数值,分别表示Key的长度和Value 的长度。紧接着是Key,开始是固定长度的数值,表示RowKey 的长度,紧接着是RowKey,然后是固定长度的数值,表示 Family 的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示 Time Stamp 和 Key Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。
2.3 MemStore 和 StoreFile
一个 Hregion 由多个 Store 组成,每个 Store 包含一个列族的所有数据。
Store 包括位于内存的一个 memstore 和位于硬盘的多个 storefile 组成。
写操作先写入 memstore,当memstore中的数据量达到某个阈值,HRegionServer启动flushcache进程写入storefile,每次写入形成单独一个 Hfile。
当总 storefile大小超过一定阈值后,会把当前的region 分割成两个,并由HMaster分配给相应的region服务器,实现负载均衡。
客户端检索数据时,先在memstore找,找不到再找storefile。
2.4 Hbase WAL HLog预写
WAL意为Write.ahead.log,类似mysql中的binlog,用来做灾难恢复之用,Hlog记录数据的所有变更,一旦数据修改,就可以从 log 中 进行恢复。
每个 Region Server 维护一个 Hlog,而不是每个 Region 一个。这样不同region(来自不同table)的日志会混在一起,这样做的目的是不断追加单个文件相对于同时写多个文件而言,可以减少磁盘寻址次数,因此可以提高对table 的写性能。带来的麻烦是,如果一台 region server下线,为了恢复其上的 region,需要将 region server 上的 log 进行拆分,然后分发到其它 region server 上进行恢复。
HLog 文件就是一个普通的 Hadoop Sequence File(序列化文件):
1)HLog Sequence File 的 Key 是 HLogKey 对象,HLogKey中记录了写入数据的归属信息,除了table 和 region 名字外,同时还包括 sequence number和timestamp,timestamp是”写入时间”,sequence number 的起始值为 0,或者是最近一次存入文件系统中 sequence number。
2)HLog Sequece File 的 Value 是 HBase 的 KeyValue 对象,即对应 HFile 中的 KeyValue。
2.5 Region 寻址机制
既然读写都在RegionServer上发生,我们前面有讲到,每个RegionSever 为一定数量的Region服务,那么 Client要对某一行数据做读写的时候如何能知道具体要去访问哪个 RegionServer
访问路径为3 步:
第 1 步:Client 请求 ZooKeeper 获取.META.所在的 RegionServer 的地址。
第 2 步:Client请求.META.所在的RegionServer获取访问数据所在的RegionServer地址,Client会将.META.的相关信息cache下来,以便下一次快速访问。
第 3 步:Client请求数据所在的RegionServer,获取所需要的数据。
Client 会缓存.META.的数据,用来加快访问,既然有缓存,那它什么时候更新?如果.META.更新了,比如Region1不在RerverServer2上了,被转移到了RerverServer3 上。Client 的缓存没有更新会有什么情况?
其实,Client 的元数据缓存不更新,当.META.的数据发生更新。如上面的例子,由于Region1的位置发生了变化,Client 再次根据缓存去访问的时候,会出现错误,当出现异常达到重试次数后就会去.META.所在的RegionServer 获取最新的数据,如果.META.所在的RegionServer也变了,Client 就会去 ZooKeeper 上获取.META.所在的 RegionServer 的最新地址。
2.6 读写过程
2.6.1 读请求过程
1、客户端通过ZooKeeper以及-ROOT-表和.META.表找到目标数据所在的RegionServer(就是 数据所在的 Region 的主机地址)
2、联系 RegionServer 查询目标数据
3、RegionServer定位到目标数据所在的Region,发出查询请求
4、Region 先在 Memstore 中查找,命中则返回
5、如果在 Memstore中找不到,则在Storefile中扫描为了能快速的判断要查询的数据在不在这个 StoreFile 中,应用了 BloomFilter
(BloomFilter,布隆过滤器:迅速判断一个元素是不是在一个庞大的集合内,但是他有一个弱点:它有一定的误判率)
(误判率:原本不存在与该集合的元素,布隆过滤器有可能会判断说它存在,但是,如果布隆过滤器,判断说某一个元素不存在该集合,那么该元素就一定不在该集合内)
2.6.2 写请求过程
HBase的写逻辑涉及到写内存、写log、刷盘等操作。
2.6.2.1 HBase写入逻辑
写入流程:
1、Client先根据RowKey找到对应的 Region 所在的RegionServer
2、Client向RegionServer提交写请求
3、RegionServer找到目标Region
4、Region检查数据是否与Schema 一致
5、如果客户端没有指定版本,则获取当前系统时间作为数据版本
6、将更新写入 WAL Log
7、将更新写入 Memstore
8、判断 Memstore 的是否需要 flush 为 StoreFile 文件。
只有写当Hlog和写MemStore都成功了才算请求写入成功,MemStore后续会主键刷到HDFS中。Hlog存储在HDFS中,当RegionServer出现异常,需要使用Hlog来恢复数据。
Hbase 在做数据插入操作时,首先要找到RowKey所对应的的Region,因为.META.表存储了每张表每个Region的起始RowKey了,可以很容易找到相应Region.
建议:在做海量数据的插入操作,避免出现递增 rowkey 的 put 操作
如果put操作的所有RowKey都是递增的,那么试想,当插入一部分数据的时候刚好进行分裂,那么之后的所有数据都开始往分裂后的第二个Region插入,就造成了数据热点现象。
2.6.2.2 MemStore刷盘
数据在更新时首先写入HLog(WAL Log),再写入内存(MemStore)中,MemStore中的数据是排序的,此时并不会立即刷盘,而是当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时,系统会在ZooKeeper中记录一个redoPoint,表示这个时刻之前的变更已经持久化了。当系统出现意外时,可能导致内存(MemStore)中的数据丢失,此时使用HLog(WAL Log)来恢复checkpoint之后的数据。
2.6.2.2.1 全局内存控制
这个全局的参数是控制内存整体的使用情况,当所有memstore占整个heap的最大比例的时候,会触发刷盘的操作。这个参数是hbase.regionserver.global.memstore.upperLimit,默认为整个heap内存的40%。但这并不意味着全局内存触发的刷盘操作会将所有的MemStore都进行输盘,而是通过另外一个参数hbase.regionserver.global.memstore.lowerLimit来控制,默认是整个heap内存的35%。当flush到所有memstore占整个heap内存的比率为35%的时候,就停止刷盘。这么做主要是为了减少刷盘对业务带来的影响,实现平滑系统负载的目的。
2.6.2.2.2 MemStore达到上限
当MemStore的大小达到hbase.hregion.memstore.flush.size大小的时候会触发刷盘,默认128M大小
2.6.2.2.3 RegionServer的Hlog数量达到上限
前面说到Hlog为了保证Hbase数据的一致性,那么如果Hlog太多的话,会导致故障恢复的时间太长,因此Hbase会对Hlog的最大个数做限制。当达到Hlog的最大个数的时候,会强制刷盘。这个参数是hase.regionserver.max.logs,默认是32个。
2.6.2.2.4 手工触发
可以通过hbase shell或者java api手工触发flush的操作。
2.6.2.2.5 关闭RegionServer触发
在正常关闭RegionServer会触发刷盘的操作,全部数据刷盘后就不需要再使用Hlog恢复数据。
2.6.2.2.6 Region使用HLOG恢复完数据后触发
工作机制:每个 HRegionServer 中都会有一个 HLog对象,HLog是一个实现Write Ahead Log 的类,每次用户操作写入 Memstore 的同时,也会写一份数据到 HLog 文件,HLog 文件定期会滚动出新,并删除旧的文件(已持久化到StoreFile中的数据)。当 HRegionServer 意外终止 后,HMaster 会通过ZooKeeper感知,HMaster 首先处理遗留的 HLog 文件,将不同Region的log数据拆分,分别放到相应Region 目录下,然后再将失效的 Region(带有刚刚拆分的 log)重新分配,领取到这些 Region的HRegionServer在load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到MemStore中,然后flush到StoreFiles,完成数据恢复。
当RegionServer出现故障的时候,其上面的Region会迁移到其他正常的RegionServer上,在恢复完Region的数据后,会触发刷盘,当刷盘完成后才会提供给业务访问。。
2.6.2.3 StoreFile存储
StoreFile是只读的,一旦创建后就不可以再修改。因此HBase的更新/修改其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(minor_compact,major_compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对StoreFile进行split,等分为两个 StoreFile。由于对表的更新是不断追加的,compact时,需要访问 Store中全部的StoreFile和MemStore,将他们按 rowkey 进行合并,由于 StoreFile 和 MemStore都是经过排序的,并且StoreFile带有内存中索引,合并的过程还是比较快。
major_compact 和 minor_compact 的区别:
minor_compact 仅仅合并小文件(HFile)
major_compact 合并一个 region 内的所有文件
Client 写入 -> 存入MemStore,一直到MemStore满->Flush成一个StoreFile,直至增长到 一定阈值 -> 触发 Compact 合并操作 -> 多个 StoreFile 合并成一个 StoreFile,同时进行版本合并和数据删除->当StoreFiles Compact后,逐步形成越来越大的StoreFile->单个StoreFile大小超过一定阈值后,触发Split操作,把当前 Region Split 成 2 个 Region,Region 会下线, 新 Split 出的 2 个孩子Region会被HMaster分配到相应的HRegionServer上,使得原先1个Region的压力得以分流到2个Region上。
由此过程可知,HBase只是增加数据,有所得更新和删除操作,都是在Compact阶段做的,所以,用户写操作只需要进入到内存即可立即返 回,从而保证 I/O 高性能。
2.7 Master 工作机制
2.7.1 Master 上线
Master 启动进行以下步骤:
1、从 ZooKeeper 上获取唯一一个代表 Active Master 的锁,用来阻止其它 Master 成为 Master。
2、扫描 ZooKeeper 上的 server 父节点,获得当前可用的 RegionServer 列表。
3、和每个 RegionServer 通信,获得当前已分配的 Region 和 RegionServer 的对应关系。
4、扫描.META. Region 的集合,计算得到当前还未分配的 Region,将他们放入待分配 Region 列表。
2.7.2 Master 下线
由于Master只维护表和Region的元数据,而不参与表数据IO的过程,Master下线仅导致所有元数据的修改被冻结(无法创建删除表,无法修改表的schema,无法进行Region的负载均衡,无法处理Region上下线,无法进行Region的合并,唯一例外的是Region的split可以正常进行,因为只有RegionServer参与),表的数据读写还可以正常进行。因此Master 下线短时间内对整个hbase集群没有影响。
从上线过程可以看到,Master保存的信息全是可以冗余信息(都可以从系统其它地方收集到或者计算出来)
因此,一般 HBase 集群中总是有一个Master在提供服务,还有一个以上的Master 在等 待时机抢占它的位置。
3. Region Server
3.1 RegionServer 工作机制
3.1.1 Region 分配
任何时刻,一个Region只能分配给一个RegionServer。master 记录了当前有哪些可用的RegionServer。以及当前哪些 Region 分配给了哪些 RegionServer,哪些 Region 还没有分配。 当需要分配的新的Region,并且有一个RegionServer 上有可用空间时,Master就给这个RegionServer 发送一个装载请求,把Region分配给这个RegionServer。RegionServer得到请求后,就开始对此Region提供服务。
3.1.2 RegionServer 上线
Master使用 zookeeper来跟踪RegionServer状态。当某个 RegionServer 启动时,会首先在 ZooKeeper 上的server 目录下建立代表自己的 znode。由于Master订阅了server 目录上的变 更消息,当server目录下的文件出现新增或删除操作时,Master可以得到来自ZooKeeper的实时通知。因此一旦RegionServer上线,Master能马上得到消息。
3.1.3 RegionServer 下线
当 RegionServer下线时,它和zookeeper的会话断开,ZooKeeper 而自动释放代表这台server的文件上的独占锁。Master 就可以确定:
1、RegionServer 和 ZooKeeper 之间的网络断开了。
2、RegionServer 挂了。
无论哪种情况,RegionServer都无法继续为它的Region提供服务了,此时Master会删除server目录下代表这台RegionServer 的 znode数据,并将这台RegionServer的Region 分配给其它。
3.2 RegionServer的故障恢复
RegionServer的相关信息保存在ZK中,在RegionServer启动的时候,会在Zookeeper中创建对应的临时节点。RegionServer通过Socket和Zookeeper建立session会话,RegionServer会周期性地向Zookeeper发送ping消息包,以此说明自己还处于存活状态。而Zookeeper收到ping包后,则会更新对应session的超时时间。
当Zookeeper超过session超时时间还未收到RegionServer的ping包,则Zookeeper会认为该RegionServer出现故障,ZK会将该RegionServer对应的临时节点删除,并通知Master,Master收到RegionServer挂掉的信息后就会启动数据恢复的流程。Master启动数据恢复流程后,其实主要的流程如下:
RegionServer宕机---》ZK检测到RegionServer异常---》Master启动数据恢复---》Hlog切分---》Region重新分配---》Hlog重放---》恢复完成并提供服务
故障恢复有3中模式
3.2.1 LogSplitting
在最开始的恢复流程中,Hlog的整个切分过程都由于Master来执行,如下图所示:
a、将待切分的日志文件夹进行重命名,防止RegionServer未真的宕机而持续写入Hlog
b、Master启动读取线程读取Hlog的数据,并将不同RegionServer的日志写入到不通的内存buffer中
c、针对每个buffer,Master会启动对应的写线程将不同Region的buffer数据写入到HDFS中,对应的路径为/hbase/table_name/region/recoverd.edits/.tmp。
d、Master重新将宕机的RegionServer中的Rgion分配到正常的RegionServer中,对应的RegionServer读取Region的数据,会发现该region目录下的recoverd.edits目录以及相关的日志,然后RegionServer重放对应的Hlog日志,从而实现对应Region数据的恢复。
从上面的步骤中,我们可以看出Hlog的切分一直都是master在干活,效率比较低。设想,如果集群中有多台RegionServer在同一时间宕机,会是什么情况?串行修复,肯定异常慢,因为只有master一个人在干Hlog切分的活。因此,为了提高效率,开发了Distributed Log Splitting架构。
3.2.2 Distributed Log Splitting
Distributed Log Splitting是LogSplitting的分布式实现,分布式就不是master一个人在干活了,而是充分使用各个RegionServer上的资源,利用多个RegionServer来并行切分Hlog,提高切分的效率。如下图所示:
上图的操作顺序如下:
a、Master将要切分的日志发布到Zookeeper节点上(/hbase/splitWAL),每个Hlog日志一个任务,任务的初始状态为TASK_UNASSIGNED
b、在Master发布Hlog任务后,RegionServer会采用竞争方式认领对应的任务(先查看任务的状态,如果是TASK_UNASSIGNED,就将该任务状态修改为TASK_OWNED)
c、RegionServer取得任务后会让对应的HLogSplitter线程处理Hlog的切分,切分的时候读取出Hlog的对,然后写入不通的Region buffer的内存中。
d、RegionServer启动对应写线程,将Region buffer的数据写入到HDFS中,路径为/hbase/table/region/seqenceid.temp,seqenceid是一个日志中该Region对应的最大sequenceid,如果日志切分成功,而RegionServer会将对应的ZK节点的任务修改为TASK_DONE,如果切分失败,则会将任务修改为TASK_ERR。
e、如果任务是TASK_ERR状态,则Master会重新发布该任务,继续由RegionServer竞争任务,并做切分处理。
f、Master重新将宕机的RegionServer中的Rgion分配到正常的RegionServer中,对应的RegionServer读取Region的数据,将该region目录下的一系列的seqenceid.temp进行从小到大进行重放,从而实现对应Region数据的恢复。
从上面的步骤中,我们可以看出Distributed Log Splitting采用分布式的方式,使用多台RegionServer做Hlog的切分工作,确实能提高效率。正常故障恢复可以降低到分钟级别。但是这种方式有个弊端是会产生很多小文件(切分的Hlog数宕机的RegionServer上的Region数)。比如一个RegionServer有20个Region,有50个Hlog,那么产生的小文件数量为2050=1000个。如果集群中有多台RegionServer宕机的情况,小文件更是会成倍增加,恢复的过程还是会比较慢。由次诞生了Distributed Log Replay模式。
3.2.3 Distributed Log Replay
Distributed Log Replay和Distributed Log Splitting的不同是先将宕机RegionServer上的Region分配给正常的RgionServer,并将该Region标记为recovering。再使用Distributed Log Splitting类似的方式进行Hlog切分,不同的是,RegionServer将Hlog切分到对应Region buffer后,并不写HDFS,而是直接进行重放。这样可以减少将大量的文件写入HDFS中,大大减少了HDFS的IO消耗。如下图所示:
3.3 Region的拆分
3.3.1 Hbase Region的三种拆分策略
Hbase Region的拆分策略有比较多,比如除了3种默认过的策略,还有DelimitedKeyPrefixRegionSplitPolicy、KeyPrefixRegionSplitPolicy、DisableSplitPolicy等策略,这里只介绍3种默认的策略。分别是ConstantSizeRegionSplitPolicy策略、IncreasingToUpperBoundRegionSplitPolicy策略和SteppingSplitPolicy策略。
3.3.1.1 ConstantSizeRegionSplitPolicy
ConstantSizeRegionSplitPolicy策略是0.94版本之前的默认拆分策略,这个策略的拆分规则是:当region大小达到hbase.hregion.max.filesize(默认10G)后拆分。
这种拆分策略对于小表不太友好,按照默认的设置,如果1个表的Hfile小于10G就一直不会拆分。注意10G是压缩后的大小,如果使用了压缩的话。如果1个表一直不拆分,访问量小也不会有问题,但是如果这个表访问量比较大的话,就比较容易出现性能问题。这个时候只能手工进行拆分。还是很不方便。
3.3.1.2 IncreasingToUpperBoundRegionSplitPolicy
IncreasingToUpperBoundRegionSplitPolicy策略是Hbase的0.94~2.0版本默认的拆分策略,这个策略相较于ConstantSizeRegionSplitPolicy策略做了一些优化,该策略的算法为:min(r^2*flushSize,maxFileSize ),最大为maxFileSize 。
从这个算是我们可以得出flushsize为128M、maxFileSize为10G的情况下,可以计算出Region的分裂情况如下:
- 第一次拆分大小为:min(10G,11128M)=128M
- 第二次拆分大小为:min(10G,33128M)=1152M
- 第三次拆分大小为:min(10G,55128M)=3200M
- 第四次拆分大小为:min(10G,77128M)=6272M
- 第五次拆分大小为:min(10G,99128M)=10G
- 第六次拆分大小为:min(10G,1111128M)=10G
从上面的计算我们可以看到这种策略能够自适应大表和小表,但是这种策略会导致小表产生比较多的小region,对于小表还是不是很完美。
3.3.1.3 SteppingSplitPolicy
SteppingSplitPolicy是在Hbase 2.0版本后的默认策略,拆分规则为
:If region=1 then: flush size * 2 else: MaxRegionFileSize
还是以flushsize为128M、maxFileSize为10场景为列,计算出Region的分裂情况如下:
第一次拆分大小为:2*128M=256M
第二次拆分大小为:10G
从上面的计算我们可以看出,这种策略兼顾了ConstantSizeRegionSplitPolicy策略和IncreasingToUpperBoundRegionSplitPolicy策略,对于小表也肯呢个比较好的适配。
3.3.2 Hbase Region拆分的详细流程
Hbase的详细拆分流程图如下:
从上图我们可以看出Region切分的详细流程如下:
- 第1步会ZK的/hbase/region-in-transition/region-name下创建一个znode,并设置状态为SPLITTING
- 第2步master通过watch节点检测到Region状态的变化,并修改内存中Region状态的变化
- 第3步RegionServer在父Region的目录下创建一个名称为.splits的子目录
- 第4步RegionServer关闭父Region,强制将数据刷新到磁盘,并这个Region标记为offline的状态。此时,落到这个Region的请求都会返回NotServingRegionException这个错误
- 第5步RegionServer在.splits创建daughterA和daughterB,并在文件夹中创建对应的reference文件,指向父Region的Region文件
- 第6步RegionServer在HDFS中创建daughterA和daughterB的Region目录,并将reference文件移动到对应的Region目录中
- 第7步在.META.表中设置父Region为offline状态,不再提供服务,并将父Region的daughterA和daughterB的Region添加到.META.表中,已表名父Region被拆分成了daughterA和daughterB两个Region
- 第8步RegionServer并行开启两个子Region,并正式提供对外写服务
- 第9步RegionSever将daughterA和daughterB添加到.META.表中,这样就可以从.META.找到子Region,并可以对子Region进行访问了
- 第10步RegionServr修改/hbase/region-in-transition/region-name的znode的状态为SPLIT
备注:为了减少对业务的影响,Region的拆分并不涉及到数据迁移的操作,而只是创建了对父Region的指向。只有在做大合并的时候,才会将数据进行迁移。
那么通过reference文件如何才能查找到对应的数据呢?如下图所示:
根据文件名来判断是否是reference文件
由于reference文件的命名规则为前半部分为父Region对应的File的文件名,后半部分是父Region的名称,因此读取的时候也根据前半部分和后半部分来识别
根据reference文件的内容来确定扫描的范围,reference的内容包含两部分,一部分是切分点splitkey,另一部分是boolean类型的变量(true或者false)。如果为true则扫描文件的上半部分,false则扫描文件的下半部分
接下来确定了扫描的文件,以及文件的扫描范围,那就按照正常的文件检索了
3.4 Region的合并
Region的合并分为小合并和大合并,下面就分别来做介绍:
3.4.1 小合并(MinorCompaction)
由前面的刷盘部分的介绍,我们知道当MemStore达到hbase.hregion.memstore.flush.size大小的时候会将数据刷到磁盘,生产StoreFile,因此势必产生很多的小问题,对于Hbase的读取,如果要扫描大量的小文件,会导致性能很差,因此需要将这些小文件合并成大一点的文件。因此所谓的小合并,就是把多个小的StoreFile组合在一起,形成一个较大的StoreFile,通常是累积到3个Store File后执行。通过参数hbase.hstore,compactionThreadhold配置。小合并的大致步骤为: - 分别读取出待合并的StoreFile文件的KeyValues,并顺序地写入到位于./tmp目录下的临时文件中 - 将临时文件移动到对应的Region目录中 - 将合并的输入文件路径和输出路径封装成KeyValues写入WAL日志,并打上compaction标记,最后强制自行sync - 将对应region数据目录下的合并的输入文件全部删除,合并完成
这种小合并一般速度很快,对业务的影响也比较小。本质上,小合并就是使用短时间的IO消耗以及带宽消耗换取后续查询的低延迟。
3.4.2 大合并(MajorCompaction)
所谓的大合并,就是将一个Region下的所有StoreFile合并成一个StoreFile文件,在大合并的过程中,之前删除的行和过期的版本都会被删除,拆分的母Region的数据也会迁移到拆分后的子Region上。大合并一般一周做一次,控制参数为hbase.hregion.majorcompaction。大合并的影响一般比较大,尽量避免统一时间多个Region进行合并,因此Hbase通过一些参数来进行控制,用于防止多个Region同时进行大合并。该参数为:hbase.hregion.majorcompaction.jitter
具体算法为:
hbase.hregion.majorcompaction参数的值乘于一个随机分数,这个随机分数不能超过hbase.hregion.majorcompaction.jitter的值。hbase.hregion.majorcompaction.jitter的值默认为0.5。
通过hbase.hregion.majorcompaction参数的值加上或减去hbase.hregion.majorcompaction参数的值乘于一个随机分数的值就确定下一次大合并的时间区间。
用户如果想禁用major compaction,只需要将参数hbase.hregion.majorcompaction设为0。建议禁用。