大数据学习之HBase——05Hbase原理
一. 系统架构
1. 架构图
-
错误架构图
这张图是有一个错误点:应该是每一个 RegionServer 就只有一个 HLog,而不是一个 Region 有一个 HLog。 -
正确图解
从HBase的架构图上可以看出,HBase中的组件包括Client、Zookeeper、HMaster、HRegionServer、HRegion、Store、MemStore、StoreFile、HFile、HLog等,接下来介绍他们的作用。
2. 相关组件
1. Client
-
HBase 有两张特殊表:
- .META.:记录了用户所有表拆分出来的的 Region 映射信息,.META.可以有多个 Regoin
- -ROOT-:记录了.META.表的 Region 信息,-ROOT-只有一个 Region,无论如何不会分裂
-
Client 访问用户数据前需要首先访问 ZooKeeper,找到-ROOT-表的 Region 所在的位置,然 后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次 网络操作,不过 client 端会做 cache 缓存。
2. ZooKeeper
- ZooKeeper 为 HBase 提供 Failover 机制,选举 Master,避免单点 Master 单点故障问题
- 存储所有 Region 的寻址入口:-ROOT-表在哪台服务器上。-ROOT-这张表的位置信息
- 实时监控 RegionServer 的状态,将 RegionServer 的上线和下线信息实时通知给 Master
- 存储 HBase 的 Schema,包括有哪些 Table,每个 Table 有哪些 Column Family
3. Master
- 为 RegionServer 分配 Region
- 负责 RegionServer 的负载均衡
- 发现失效的 RegionServer 并重新分配其上的 Region
- HDFS 上的垃圾文件(HBase)回收
- 处理 Schema 更新请求(表的创建,删除,修改,列簇的增加等等)
4. RegionServer
- RegionServer 维护 Master 分配给它的 Region,处理对这些 Region 的 IO 请求
- RegionServer 负责 Split 在运行过程中变得过大的 Region,负责 Compact 操作
- 可以看到,client 访问 HBase 上数据的过程并不需要 master 参与(寻址访问 zookeeper 和 RegioneServer,数据读写访问 RegioneServer),Master 仅仅维护者 Table 和 Region 的元数据信息,负载很低。
- .META. 存的是所有的 Region 的位置信息,那么 RegioneServer 当中 Region 在进行分裂之后 的新产生的 Region,是由 Master 来决定发到哪个 RegioneServer,这就意味着,只有 Master 知道 new Region 的位置信息,所以,由 Master 来管理.META.这个表当中的数据的 CRUD
- 所以结合以上两点表明,在没有 Region 分裂的情况,Master 宕机一段时间是可以忍受的。
5. HRegion
- table在行的方向上分隔为多个Region。Region是HBase中分布式存储和负载均衡的最小单元,即不同的region可以分别在不同的Region Server上,但同一个Region是不会拆分到多个server上。
Region按大小分隔,每个表一般是只有一个region。随着数据不断插入表,region不断增大,当region的某个列族达到一个阈值时就会分成两个新的region。 - 每个region由以下信息标识:< 表名,startRowkey,创建时间>
- 由目录表(-ROOT-和.META.)记录该region的endRowkey
6. Store
- 每一个region由一个或多个store组成,至少是一个store,hbase会把一起访问的数据放在一个store里面,即为每个 ColumnFamily建一个store,如果有几个ColumnFamily,也就有几个Store。一个Store由一个memStore和0或者 多个StoreFile组成。 HBase以store的大小来判断是否需要切分region
7. MemStore
- memStore 是放在内存里的。保存修改的数据即keyValues。当memStore的大小达到一个阀值(默认128MB)时,memStore会被flush到文 件,即生成一个快照。目前hbase 会有一个线程来负责memStore的flush操作。
8. StoreFile
- memStore内存中的数据写到文件后就是StoreFile,StoreFile底层是以HFile的格式保存。
9. HFile
- HBase中KeyValue数据的存储格式,HFile是Hadoop的 二进制格式文件,实际上StoreFile就是对Hfile做了轻量级包装,即StoreFile底层就是HFile
10. HLog
- HLog(WAL log):WAL意为write ahead log,用来做灾难恢复使用,HLog记录数据的所有变更,一旦region server 宕机,就可以从log中进行恢复。
- 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. 整体的物理架构
- Table 中的所有行都按照 RowKsey 的字典序排列。
- Table 在行的方向上分割为多个 HRegion。
- HRegion 按大小分割的(默认 10G),每个表一开始只有一个 HRegion,随着数据不断插入 表,HRegion 不断增大,当增大到一个阀值的时候,HRegion 就会等分会两个新的 HRegion。 当表中的行不断增多,就会有越来越多的 HRegion。
- HRegion 是 Hbase 中分布式存储和负载均衡的最小单元。最小单元就表示不同的 HRegion 可以分布在不同的 HRegionserver 上。但一个 HRegion 是不会拆分到多个 server 上的。
- HRegion 虽然是负载均衡的最小单元,但并不是物理存储的最小单元。事实上,HRegion 由一个或者多个 Store 组成,每个 Store 保存一个 Column Family。每个 Strore 又由一个 memStore 和 0 至多个 StoreFile 组成
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 部分没有这么复杂的结构,就是纯粹的二进制数据了。
3. MemStore 和 StoreFile
- 一个 Hregion 由多个 Store 组成,每个 Store 包含一个列族的所有数据。
- Store 包括位于内存的一个 memstore 和位于硬盘的多个 storefile 组成。
- 写操作先写入 memstore,当 memstore 中的数据量达到某个阈值,HRegionServer 启动 flushcache 进程写入 storefile,每次写入形成单独一个 Hfile。
- 当总 storefile 大小超过一定阈值后,会把当前的 region 分割成两个,并由 HMaster 分配给相 应的 region 服务器,实现负载均衡。
- 客户端检索数据时,先在 memstore 找,找不到再找 storefile。
4. Hbase WAL HLog预写
- WAL 意为 Write ahead log(http://en.wikipedia.org/wiki/Write-ahead_logging),类似 mysql 中的 binlog,用来做灾难恢复之用,Hlog 记录数据的所有变更,一旦数据修改,就可以从 log 中 进行恢复。
- 每个 Region Server 维护一个 Hlog,而不是每个 Region 一个。这样不同 region(来自不同 table) 的日志会混在一起,这样做的目的是不断追加单个文件相对于同时写多个文件而言,可以减 少磁盘寻址次数,因此可以提高对 table 的写性能。带来的麻烦是,如果一台 region server 下线,为了恢复其上的 region,需要将 region server 上的 log 进行拆分,然后分发到其它 region server 上进行恢复。
- HLog 文件就是一个普通的 Hadoop Sequence File(序列化文件):
- HLog Sequence File 的 Key 是 HLogKey 对象,HLogKey 中记录了写入数据的归属信息,除 了 table 和 region 名字外,同时还包括 sequence number 和 timestamp,timestamp 是”写入 时间”,sequence number 的起始值为 0,或者是最近一次存入文件系统中 sequence number。
- HLog Sequece File 的 Value 是 HBase 的 KeyValue 对象,即对应 HFile 中的 KeyValue。
5. Region 寻址机制
既然读写都在 RegionServer 上发生,我们前面有讲到,每个 RegionSever 为一定数量的 Region 服务,那么 Client 要对某一行数据做读写的时候如何能知道具体要去访问哪个 RegionServer 呢?那就是接下来我们要讨论的问题
1. 老的 Region 寻址方式
-
在 HBase-0.96 版本以前,HBase 有两个特殊的表,分别是-ROOT-表和.META.表,其中-ROOT的位置存储在 ZooKeeper 中,-ROOT-本身存储了.META. Table 的 RegionInfo 信息,并且-ROOT不会分裂,只有一个 Region。而.META.表可以被切分成多个 Region。读取的流程如下图所示:
-
详细步骤:
- Client 请求 ZooKeeper 获得-ROOT-所在的 RegionServer 地址
- Client 请求-ROOT-所在的 RS 地址,获取.META.表的地址,Client 会将-ROOT-的相关 信息 cache 下来,以便下一次快速访问
- Client 请求.META.表的 RegionServer 地址,获取访问数据所在 RegionServer 的地址, Client 会将.META.的相关信息 cache 下来,以便下一次快速访问
- Client 请求访问数据所在 RegionServer 的地址,获取对应的数据
-
从上面的路径我们可以看出,用户需要 3 次请求才能直到用户 Table 真正的位置,这在一定 程序带来了性能的下降。在 0.96 之前使用 3 层设计的主要原因是考虑到元数据可能需要很 大。但是真正集群运行,元数据的大小其实很容易计算出来。在 BigTable 的论文中,每行 METADATA 数据存储大小为 1KB 左右,如果按照一个 Region 为 128M 的计算,3 层设计可以支持的 Region 个数为 2^34 个,采用 2 层设计可以支持 2^17(131072)。那么 2 层设计的情 况下一个集群可以存储 4P 的数据。这仅仅是一个 Region 只有 128M 的情况下。如果是 10G 呢? 因此,通过计算,其实 2 层设计就可以满足集群的需求。因此在 0.96 版本以后就去掉 了-ROOT-表了。
2. 新的 Region 寻址方式
-
如上面的计算,2 层结构其实完全能满足业务的需求,因此 0.96 版本以后将-ROOT-表去掉了。 如下图所示
-
访问路径变成了 3 步:
- Client 请求 ZooKeeper 获取.META.所在的 RegionServer 的地址。
- Client 请求.META.所在的 RegionServer 获取访问数据所在的 RegionServer 地址,Client 会将.META.的相关信息 cache 下来,以便下一次快速访问。
- Client 请求数据所在的 RegionServer,获取所需要的数据。
-
总结去掉-ROOT-的原因有如下 2 点:
- 提高性能
- 2 层结构已经足以满足集群的需求
-
这里还有一个问题需要说明,那就是 Client 会缓存.META.的数据,用来加快访问,既然有缓 存,那它什么时候更新?如果.META.更新了,比如 Region1 不在 RerverServer2 上了,被转移 到了 RerverServer3 上。Client 的缓存没有更新会有什么情况?
-
其实,Client 的元数据缓存不更新,当.META.的数据发生更新。如上面的例子,由于 Region1 的位置发生了变化,Client 再次根据缓存去访问的时候,会出现错误,当出现异常达到重试 次数后就会去.META.所在的 RegionServer 获取最新的数据,如果.META.所在的 RegionServer 也变了,Client 就会去 ZooKeeper 上获取.META.所在的 RegionServer 的最新地址。
三. 读写流程
1. 读数据流程
- Client先访问zookeeper,从meta表读取region的位置,然后读取meta表中的数据。meta中又存储了用户表的region信息;
- 根据namespace、表名和rowkey在meta表中找到对应的region信息;
- 找到这个region对应的regionserver;
- 查找对应的region;
- 先从MemStore找数据,如果没有,再到BlockCache里面读;
- BlockCache还没有,再到StoreFile上读(为了读取的效率);
- 如果是从StoreFile里面读取的数据,不是直接返回给客户端,而是先写入BlockCache,再返回给客户端。
2. 写数据流程
- Client 先根据 RowKey 找到对应的 Region 所在的 RegionServer
- Client 向 RegionServer 提交写请求
- RegionServer 找到目标 Region
- Region 检查数据是否与 Schema 一致
- 如果客户端没有指定版本,则获取当前系统时间作为数据版本
- 将更新写入 WAL Log
- 将更新写入 Memstore
- 判断 Memstore 的是否需要 flush 为 StoreFile 文件。
四. RegionServer 工作机制
1. Region 分配
- 任何时刻,一个 Region 只能分配给一个 RegionServer。master 记录了当前有哪些可用的 RegionServer。以及当前哪些 Region 分配给了哪些 RegionServer,哪些 Region 还没有分配。 当需要分配的新的 Region,并且有一个 RegionServer 上有可用空间时,Master 就给这个 RegionServer 发送一个装载请求,把 Region 分配给这个 RegionServer。RegionServer 得到请 求后,就开始对此 Region 提供服务。
2. RegionServer 上线
- Master 使用 zookeeper 来跟踪 RegionServer 状态。当某个 RegionServer 启动时,会首先在 ZooKeeper 上的 server 目录下建立代表自己的 znode。由于 Master 订阅了 server 目录上的变 更消息,当 server 目录下的文件出现新增或删除操作时,Master 可以得到来自 ZooKeeper 的实时通知。因此一旦 RegionServer 上线,Master 能马上得到消息。
3. RegionServer 下线
当 RegionServer 下线时,它和 zookeeper 的会话断开,ZooKeeper 而自动释放代表这台 server 的文件上的独占锁。Master 就可以确定:
- RegionServer 和 ZooKeeper 之间的网络断开了。
- RegionServer 挂了。
无论哪种情况,RegionServer都无法继续为它的Region提供服务了,此时Master会删除server 目录下代表这台 RegionServer 的 znode 数据,并将这台 RegionServer 的 Region 分配给其它还 活着的同志。
五. Master 工作机制
1. Master 上线
Master 启动进行以下步骤:
- 从 ZooKeeper 上获取唯一一个代表 Active Master 的锁,用来阻止其它 Master 成为 Master。
- 扫描 ZooKeeper 上的 server 父节点,获得当前可用的 RegionServer 列表。
- 和每个 RegionServer 通信,获得当前已分配的 Region 和 RegionServer 的对应关系。
- 扫描.META. Region 的集合,计算得到当前还未分配的 Region,将他们放入待分配 Region 列表。
2. Master 下线
由于 Master 只维护表和 Region 的元数据,而不参与表数据 IO 的过程,Master 下线仅 导致所有元数据的修改被冻结(无法创建删除表,无法修改表的 schema,无法进行 Region 的负载均衡,无法处理 Region 上下线,无法进行 Region 的合并,唯一例外的是 Region 的 split 可以正常进行,因为只有 RegionServer 参与),表的数据读写还可以正常进行。因此 Master 下线短时间内对整个 hbase 集群没有影响。
从上线过程可以看到,Master 保存的信息全是可以冗余信息(都可以从系统其它地方 收集到或者计算出来)
因此,一般 HBase 集群中总是有一个 Master 在提供服务,还有一个以上的 Master 在等 待时机抢占它的位置。
六. 数据flush过程
- 当MemStore数据达到阈值(默认是128M,老版本是64M),将数据刷到硬盘,将内存中的数据删除,同时删除HLog中的历史数据;
- 并将数据存储到HDFS中;
- 在HLog中做标记点。
七. 数据合并过程
- 当数据块达到3块,Hmaster触发合并操作,Region将数据块加载到本地,进行合并;
- 当合并的数据超过256M,进行拆分,将拆分后的Region分配给不同的HregionServer管理;
- 当HregionServer宕机后,将HregionServer上的hlog拆分,然后分配给不同的HregionServer加载,修改.META.;
- 注意:HLog会同步到HDFS。