1.系统架构java
1.1 图解node
从HBase的架构图上能够看出,HBase中的组件包括Client、Zookeeper、HMaster、HRegionServer、HRegion、Store、MemStore、StoreFile、HFile、HLog等,每个 RegionServer 就只有一个 HLog,而不是一个 Region 有一个 HLog。mysql
1.2 client算法
HBase 有两张特殊表:sql
1).META.:记录了用户全部表拆分出来的的Region映射信息,.META.能够有多个 Regoin shell
2)-ROOT-:记录了.META.表的Region信息,-ROOT-只有一个Region,不管如何不会分裂api
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
一、为RegionServer 分配 Region
二、负责 RegionServer的负载均衡
三、发现失效的RegionServer并从新分配其上的Region。当RegionServer失效的时候,协调对应的Hlog的拆分
四、HDFS上的垃圾文件(HBase)回收
五、维护集群的元数据信息,处理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 总体的物理结构
一、Table 中的全部行都按照 RowKsey 的字典序排列。
二、Table 在行的方向上分割为多个 HRegion。
三、HRegion按大小分割的(默认10G),每一个表一开始只有一个HRegion,随着数据不断插入表,HRegion不断增大,当增大到一个阀值的时候,HRegion就会等分会两个新的 HRegion。当表中的行不断增多,就会有愈来愈多的 HRegion。
四、HRegion 是Hbase中分布式存储和负载均衡的最小单元。最小单元就表示不一样的HRegion能够分布在不一样的HRegionserver 上。但一个 HRegion 是不会拆分到多个server 上的。
五、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 读请求过程
一、客户端经过ZooKeeper以及-ROOT-表和.META.表找到目标数据所在的RegionServer(就是 数据所在的 Region 的主机地址)
二、联系 RegionServer 查询目标数据
三、RegionServer定位到目标数据所在的Region,发出查询请求
四、Region 先在 Memstore 中查找,命中则返回
五、若是在 Memstore中找不到,则在Storefile中扫描为了能快速的判断要查询的数据在不在这个 StoreFile 中,应用了 BloomFilter
(BloomFilter,布隆过滤器:迅速判断一个元素是否是在一个庞大的集合内,可是他有一个弱点:它有必定的误判率)
(误判率:本来不存在与该集合的元素,布隆过滤器有可能会判断说它存在,可是,若是布隆过滤器,判断说某一个元素不存在该集合,那么该元素就必定不在该集合内)
2.6.2 写请求过程
HBase的写逻辑涉及到写内存、写log、刷盘等操做。
2.6.2.1 HBase写入逻辑
写入流程:
一、Client先根据RowKey找到对应的 Region 所在的RegionServer
二、Client向RegionServer提交写请求
三、RegionServer找到目标Region
四、Region检查数据是否与Schema 一致
五、若是客户端没有指定版本,则获取当前系统时间做为数据版本
六、将更新写入 WAL Log
七、将更新写入 Memstore
八、判断 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 启动进行如下步骤:
一、从 ZooKeeper 上获取惟一一个表明 Active Master 的锁,用来阻止其它 Master 成为 Master。
二、扫描 ZooKeeper 上的 server 父节点,得到当前可用的 RegionServer 列表。
三、和每一个 RegionServer 通讯,得到当前已分配的 Region 和 RegionServer 的对应关系。
四、扫描.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 就能够肯定:
一、RegionServer 和 ZooKeeper 之间的网络断开了。
二、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。建议禁用。