Hbase架构与原理

大数据

Posted by Jesse on June 10, 2023

HBase架构与原理详解

1.1架构图

1.2 组件介绍

HBase由三种类型的服务器以主从模式构成:

  • Region Server:负责数据的读写服务,用户通过与Region server交互来实现对数据的访问。
  • HBase HMaster:负责Region的分配及数据库的创建和删除等操作。
  • ZooKeeper:负责维护集群的状态(某台服务器是否在线,服务器之间数据的同步操作及master的选举等)。

HDFS的DataNode负责存储所有Region Server所管理的数据,即HBase中的所有数据都是以HDFS文件的形式存储的。出于使Region server所管理的数据更加本地化的考虑,Region server是根据DataNode分布的。HBase的数据在写入的时候都存储在本地。但当某一个region被移除或被重新分配的时候,就可能产生数据不在本地的情况。这种情况只有在所谓的compaction之后才能解决。

1.3 数据模型

二、HMaster

HMaster负责region的分配,数据库的创建和删除操作,其职责包括:

  • 调控Region server的工作

  • 在集群启动的时候分配region,根据恢复服务或者负载均衡的需要重新分配region。

  • 监控集群中的Region server的工作状态。(通过监听zookeeper对于ephemeral node状态的通知)。

  • 管理数据库

  • 提供创建,删除或者更新表格的接口。

三、Zookeeper

HBase利用ZooKeeper维护集群中服务器的状态并协调分布式系统的工作,其功能如下:

  • 维护服务器是否存活,是否可访问的状态并提供服务器故障/宕机的通知。
  • 使用一致性算法来保证服务器之间的同步。
  • 负责Master选举的工作。

需要注意的是要保证良好的一致性及顺利的Master选举,集群中的服务器数目必须是奇数

四、Region Server

HBase中的表是根据row key的值水平分割成所谓的region的。一个region包含表中所有row key位于region的起始键值和结束键值之间的行。

集群中负责管理Region的结点叫做Region server。Region server负责数据的读写。每一个Region server大约可以管理1000个region。Region的结构如下图所示:

4.1 组成部分

Region server由如下几个部分组成:

  • WAL:既Write Ahead Log。WAL是HDFS分布式文件系统中的一个文件,即HLog。WAL用来存储尚未写入永久性存储区中的新数据。WAL也用来在服务器发生故障时进行数据恢复。
  • Block Cache:Block cache是读缓存。Block cache将经常被读的数据存储在内存中来提高读取数据的效率。当Block cache的空间被占满后,其中被读取频率最低的数据将会被杀出。
  • MemStore:MemStore是写缓存。其中存储了从WAL中写入但尚未写入硬盘的数据。MemStore中的数据在写入硬盘之前会先进行排序操作。每一个region中的每一个column family对应一个MemStore。
  • Hfiles:Hfiles存在于硬盘上,根据排序号的键存储数据行。

4.2 数据读写

4.2.1 META Table

HBase中有一个特殊的起目录作用的表格,称为META table。META table中保存集群region的地址信息。ZooKeeper中会保存META table的位置。

META table中保存了HBase中所有region的信息,格式类似于B tree。其结构如下:

  • 键:region的起始键,region id。
  • 值:Region server

4.2.2 第一次读写

  1. 客户端从ZooKeeper中得到保存META table的Region server的信息。
  2. 客户端向该Region server查询负责管理自己想要访问的row key的所在的region的Region server的地址。客户端会缓存这一信息以及META table所在位置的信息。
  3. 客户端与负责其row所在region的Region Server通信,实现对该行的读写操作。

在未来的读写操作中,客户端会根据缓存寻找相应的Region server地址。除非该Region server不再可达。这时客户端会重新访问META table并更新缓存。这一过程如下图所示:

4.2.3 写操作

步骤一

当HBase的用户发出一个 PUT 请求时(也就是HBase的写请求),HBase进行处理的第一步是将数据写入HBase的write-ahead log(WAL)中。

  • WAL文件是顺序写入的,也就是所有新添加的数据都被加入WAL文件的末尾。WAL文件存在硬盘上。
  • 当server出现问题之后,WAL可以被用来恢复尚未写入HBase中的数据(因为WAL是保存在硬盘上的)。

步骤二

当数据被成功写入WAL后,HBase将数据存入MemStore。这时HBase就会通知用户PUT操作已经成功了。

4.2.4 读合并(Read Merge)和读放大(Read amplification)

HBase中对应于某一行数据的cell可能位于多个不同的文件或存储介质中。比如已经存入硬盘的行位于硬盘上的HFile中,新加入或更新的数据位于内存中的MemStore中,最近读取过的数据则位于内存中的Block cache中。所以当我们读取某一行的时候,为了返回相应的行数据,HBase需要根据Block cache,MemStore以及硬盘上的HFile中的数据进行所谓的读合并操作。

  1. HBase会首先从Block cache(HBase的读缓存)中寻找所需的数据。
  2. 接下来,HBase会从MemStore中寻找数据。因为作为HBase的写缓存,MemStore中包含了最新版本的数据。

如果HBase从Block cache和MemStore中没有找到行所对应的cell所有的数据,系统会接着根据索引和 bloom filter(布隆过滤器) 从相应的HFile中读取目标行的cell的数据。

这里一个需要注意读放大效应(Read amplification)。根据前文所说,一个MemStore对应的数据可能存储于多个不同的HFile中(由于多次的flush),因此在进行读操作的时候,HBase可能需要读取多个HFile来获取想要的数据。这会影响HBase的性能表现。

4.3 HBase Region Flush

缓冲区有一定的大小,如果缓冲区满了,那么缓冲区的数据就会被flush到一个新的HFile文件中永久保存。HBase中,对于每个列族可以有多个HFile,HFile里的数据跟缓冲区中的格式也是一样的,都是一个Key对应一个Value的结构。

需要注意的是,如果一个MemStore满了,那么所有的MemStore都要将存储的数据flush到HFile中,这也就是为什么官方建议一个表中不要有太多列族,因为每个列族对应一个MemStore,如果列族太多的话会导致频繁刷新缓冲区等性能问题。

缓冲区在刷新写入到HFile的时候,还会保存一个序列数(sequence number),这个东西是干嘛的呢?其实是为了让系统知道目前HFile上保存了哪些数据。这个序列数作为元数据(meta field)存在HFile中,所以每次在刷新的时候都等于给HFile做了个标记。

4.4HBase HFile

缓冲区的数据都是根据key进行排序的,所以在flush到HFile上的时候,就直接按书序一条一条记录往下写就行了,这样顺序写的过程是非常快速的,因为他避免了磁盘磁头的移动。

4.5HBase HFile Structure

HFile的组成就相对来说比较复杂了,因为要考虑到查询的性能,最好别出现把整个文件都扫描一遍后才发现要访问的数据不再这个HFile中的情况。因此就需要在文件的组织形式上花点心思。怎样在不完全扫描文件的情况下知道要访问的数据在不再文件中呢?我们想到的答案可能是使用索引(Index)。HFile实际上也是这种思想,它使用的是多级索引,在形式上类似于b树。

  • 键值对根据键大小升序排列。
  • 索引指向64KB大小的数据块。
  • 每一个数据块还有其相应的叶索引(leaf-index)。
  • 每一个数据块的最后一个键作为中间索引(intermediate index)。
  • 根索引(root index)指向中间索引。

文件结尾指向meta block。因为meta block是在数据写入硬盘操作的结尾写入该文件中的。文件的结尾同时还包含一些别的信息。比如 bloom filter 及时间信息。bloom filter可以帮助HBase加速数据查询的速度。因为HBase可以利用 bloom filter 跳过不包含当前查询的键的文件。时间信息则可以帮助HBase在查询时跳过读操作所期望的时间区域之外的文件。

4.6HFile Index

当一个HFile被打开的时候,这个HFile的索引就被加载到BlockCache中了,还记得我们之前说BlockCache是什么吗?就是读缓冲区。

4.7 Compaction

4.7.1 Minor Compaction

HBase会自动选取一些较小的HFile进行合并,并将结果写入几个较大的HFile中。这一过程称为Minor compaction。Minor compaction通过Merge sort的形式将较小的文件合并为较大的文件,从而减少了存储的HFile的数量,提升HBase的性能。这一过程如下图所示:

4.7.2 Major Compaction

所谓Major Compaction指的是HBase将对应于某一个Column family的所有HFile重新整理并合并为一个HFile,并在这一过程中删除已经删除或过期的cell,更新现有cell的值。这一操作大大提升读的效率。但是因为Major compaction需要重新整理所有的HFile并写入一个HFile,这一过程包含大量的硬盘I/O操作以及网络数据通信。这一过程也称为写放大(Write amplification)。在Major compaction进行的过程中,当前Region基本是处于不可访问的状态。

Major compaction可以配置在规定的时间自动运行。为避免影响业务,Major compaction一般安排在夜间或周末进行。

需要注意的一点是,Major compaction会将当前Region所服务的所有远程数据下载到本地Region server上。这些远程数据可能由于服务器故障或者负载均衡等原因而存储在于远端服务器上。

Region的分割

最初, 每张表只有一个region, 当一个region变得太大时, 它就分裂成2个子region. 2个子region, 各占原始region的一半数据, 仍然被相同的region server管理. Region server向HBase master节点汇报拆分完成.

如果集群内还有其他region server, master节点倾向于做负载均衡, 所以master节点有可能调度新的region到其他region server, 由其他region管理新的分裂出的region.

读操作的负载均衡(Read Load Balancing)

Region的分割最初是在Region server本地发生的。但是出于负载均衡的原因,HMaster可能会将新产生的region分配给其他的Region server进行管理。这也就导致了Region server管理存储在远端服务器上的region情况的产生。这一情况会持续至下一次Major compaction之前。如上文所示,Major compaction会将任何不在本地的数据下载至本地。

也就是说,HBase中的数据在写入时总是存储在本地的。但是随着region的重新分配(由于负载均衡或数据恢复),数据相对于Region server不再一定是本地的。这种情况会在Major compaction后得到解决。

五、事务

HBase目前只支持行级事务,强一致性,满足的ACID特性。其采用了WAL(Write Ahead Log)策略,以及通过锁和MVCC机制来实现并发控制。

5.1 事务原子性的保证

HBase数据会首先写入WAL,再写入MemStore。写入MemStore出现异常时,很容易回滚,因此保证写入/更新原子性,只需要保证写入WAL的原子性即可。

每个事务只会产生一个WAL单元,以此来保证WAL写入的原子性。

WAL持久化策略:

  • SKIP_WAL:表示不写WAL,这样写入更新性能最好,但在RegionServer宕机的时候有可能会丢失部分数据;
  • ASYNC_WAL:表示异步将WAL持久化到硬盘,因为是异步操作所以在异常的情况下也有可能丢失少量数据;
  • SYNC_WAL:表示同步将WAL持久化到操作系统缓存,再由操作系统将数据异步持久化到磁盘,这种场景下RS宕掉并不会丢失数据,当操作系统宕掉会导致部分数据丢失;
  • FSYNC_WAL:表示WAL写入之后立马落盘,性能相对最差。目前实现中FSYNC_WAL并没有实现(是说HBase会丢数据?而且不就一行代码的实现了吗?)!用户可以根据业务对数据丢失的敏感性在客户端配置相应的持久化策略。

5.2 写写并发控制

多个写入/更新同时进行会导致数据不一致的问题,HBase通过获取行锁来实现写写并发,如果获取不到,就需要不断重试等待或者自旋等待,直至其他线程释放该锁。拿到锁之后开始写入数据,写入完成之后释放行锁即可。这种行锁机制是实现写写并发控制最常用的手段,MySQL也使用了行锁来实现写写并发。

HBase支持批量写入/更新,实现批量写入的并发控制也是使用行锁。但这里需要注意的是必须使用两阶段锁协议,步骤如下:

(1) 获取所有待写入/更新行记录的行锁。
(2) 开始执行写入/更新操作。
(3) 写入完成之后再统一释放所有行记录的行锁。

不能更新一行锁定(释放)一行,多个事务之间容易形成死锁。两阶段锁协议就是为了避免死锁,MySQL事务写写并发控制同样使用两阶段锁协议。

5.3 读写并发控制

读写之间也需要一种并发控制来保证读取的数据总能够保持一致性,读写并发不能通过加锁的方式,性能太差,采用的是MVCC机制(Mutil Version Concurrent Control)。HBase中MVCC机制实现主要分为两步:

(1) 为每一个写入/更新事务分配一个Region级别自增的序列号。
(2) 为每一个读请求分配一个已完成的最大写事务序列号。

六 6.2 异常恢复(Crash Recovery)

HDFS中所有的数据读写操作都是针对主节点进行的。HDFS会自动备份WAL和HFile。HBase以来HDFS来提供可靠的安全的数据存储。当数据被写入HDFS本地时,另外两份备份数据会分别存储在另外两台服务器上。

当region server宕机, 崩溃的region server管理的region不能再提供服务, HBase监测到异常后, 启动恢复程序, 恢复region.

Zookeeper发现region server的heartbeat停止, 判断region server宕机并通知master节点. Hbase master节点得知该region server停机后, 将崩溃的region server管理的region分配给其他region server. HBase从预写文件(WAL)里恢复memstore里的数据.

HBase master知道老的region被重新分配到哪些新的region server. Master把已经crash的Region server的预写日志(WAL)拆分成多个. 参与故障恢复的每个region server重放的预写日志(WAL), 重新构建出丢失Memstore.

数据恢复

预写日志(WAL)记录了HBase的每个操作, 每个操作代表一个Put或者删除Delete动作. 所有的操作按照时间顺序在预写日志(WAL)排列, 文件头记录最老的操作, 最新的操作处于文件末尾.

如何恢复在memstore里, 但还没有写到HFile的数据? 重新执行预写日志(WAL)就可以. 从前到后依次执行预写日志(WAL)里的操作, 重建memstore数据. 最后, Flush memstore数据到的HFile, 完成恢复.

Apache Hbase架构优点

Apache Hbase架构优点

强一致模型

  • 当写返回时, 确保所有读操作读到相同的值

自动扩展

  • 数据增长过大时, 自动分裂region

  • 利用HFDS分散数据和备份数据

内建自动回复

  • 预写日志(WAL)

集成Hadoop生态

  • 在HBase上运行map reduce

Apache HBase存在的问题…

  • Business continuity reliability:
  • 重放预写日志慢
  • 故障恢复既慢又复杂
  • Major compaction容易引起IO风暴(写放大)