您的位置 首页 java

一文理解HBase+HDFS的原理和架构

1. HBase 大纲

  • 简介(产生、历史和应用场景)
  • HBase VS 关系型数据库
  • 架构
  • 基本组件
  • 存储结构 (Key / Value,逻辑结构,物理结构)
  • 压缩算法
  • 读写流程
  • 数据备份和故障恢复
  • HDFS

2. HBase 简介

基于 Google 的BigTable, Java 开发的 一个 分布式 海量 列式 非关系型( NoSQL ,不支持 SQL ) Data Store (other than Database ) ,提供超大规模数据集(row 亿级别)的实时随机读写 。(通常结合 Hadoop HDFS使用)

2.1 特点

  • 多版本:版本号就是插入数据的时间戳,用户可以写入时指定,也可以由系统默认指定。
  • 强一致 ,但是 不是 最终一致性 ”的数据存储,这使得它非常适合高速的计算聚合
  • 自动分片,通过Region分散在集群中,当行数增长的时候,Region也会自动的切分和再分配
  • 自动的故障转移和备份
  • Hadoop HDFS集成
  • 丰富的“简洁,高效” API, Thrift /Rest API, Java API
  • 块缓存, 布隆过滤器 ,可以高效的列查询优化
  • 操作管理,Hbase提供了内置的web界面来操作,还可以监控JMX指标
  • 多Column family 灵活定义数据集,唯一Rowkey 索引

2.2 问题

  • WAL 回放很慢
  • 故障恢复很慢
  • Major Compaction 时候 I/O 会飙升
  • zookeeper 误杀RegionServer导致分区迁移

2.3 历史

  • 2006: In November, Google releases a paper on BigTable.
  • 2007: In February, The prototype was developed for HBase as a Hadoop contribution.
  • 2007: In October, HBase was released with Hadoop 0.15.
  • 2008: In January, HBase becomes subproject.
  • 2008: In October, HBase 0.18.1 version was released.
  • 2009: In January, HBase 0.19.0 version was released.
  • 2009: In September, HBase 0.20.0 version was released.
  • 2010: In May, HBase becomes an Apache top-level project.
  • 2010: In June, HBase 0.89.20100621, the first developer version was released.
  • 2011: In January, HBase 0.90.0 version was released.
  • 2011: In mid of 2011, HBase 0.92.0 version was released.

2.4 场景

  • 推荐画像:特别是用户的画像,是一个比较大的稀疏矩阵, 蚂蚁 的风控就是构建在HBase之上
  • 对象存储 :头条类、新闻类的新闻、网页、图片存储在HBase之中,一些病毒公司的病毒库也是存储在HBase之中
  • 时序数据:HBase之上有OpenTSDB模块,可以满足时序数据类场景的需求
  • 时空数据:主要是轨迹、气象网格之类,如: 滴滴 打车的轨迹数据,所有大一点的数据量的车联网企业,数据都是存在HBase之中
  • CubeDB OLAP:Kylin一个cube分析工具,底层的数据就是存储在HBase之中,客户基于离线计算构建cube存储在hbase之中,满足在线报表查询的需求
  • 消息/订单:在电信领域、银行领域,不少的订单查询底层的存储,另外不少通信、消息同步的应用构建在HBase之上
  • Feeds流:典型的应用 微博 知乎 ,今日头条 ,微信朋友圈
  • NewSQL :之上有Phoenix的插件,可以满足二级索引、SQL的需求,对接传统数据需要SQL非事务的需求。

2.5 HBase经典文章

  • HBase概述:
  • HBase原理-数据读取流程解析:
  • HBase抗战总结| 阿里巴巴 HBase高可用8年抗战回忆录:
  • HBase原理|HBase内存管理之MemStore 进化论
  • Hbase Region Split compaction 过程分析以及调优:
  • HBase分布式数据库概念与实操:

2.6 HBase和数据库的比较

3. 架构(HBase + HDFS)

3.1 HBase 架构

3.2 组件概述

3.2.1 Client

  • Client与HMaster进行通信管理类操作:HBase shell、Java编程接口和Thrift、Avro、Rest等等
  • Client与HRegionServer进行数据读写类操作

3.2.2 ZooKeeper : 提供一致性协调服务

  • 实现了HMaster的高可用,多HMaster间进行主备选举
  • 保存了HBase的元数据信息meta表,提供了HBase表中region的寻址入口的线索数据
  • 对HMaster和HRegionServer实现了监控

3.2.3 HMaster : RegionServer 中Region的管理以及表的创建、删除

  • 负责Table表和Region的相关管理工作,管理Client对Table的增删改的操作
  • 在Region分裂后,负责新Region分配到指定的HRegionServer上
  • 管理HRegionServer间的负载均衡,迁移region分布
  • 监控集群中所有 Region Server 实例(从 Zookeeper 获取通知信息), 当HRegionServer宕机后,负责其上的region的迁移
  • 管理员功能: 提供对 meta 信息管理的接口, 提供创建,删除和更新 HBase Table 的接口
  • 提供了一个WebUI 显示HBase Cluster的信息

3.2.4 RegionServers : 管理实际的用户数据

RegionServer 就是一个机器节点,包含多个 Region ,但是这些 Region 不一定是来自于同一个 Table。Region Server 和 HDFS DataNode 往往是分布在一起的,这样 Region Server 就能够实现 数据本地化 (data locality,即将数据放在离需要者尽可能近的地方)。HBase 的数据在写的时候是本地的,但是当 region 被迁移的时候,数据就可能不再满足本地性了,直到完成 compaction ,才能又恢复到本地。

主要功能如下:

  • 为Table分配Region。 Region:HBase集群中分布式存储的最小单元,一个Region对应一个Table表的部分数据。
  • 处理来自客户端的读写请求,和底层的HDFS进行交互,并将数据存储到HDFS中。
  • 负责单个Region变大后的拆分。
  • 负责StoreFile的合并工作。
  • 维护HLog

Region

每一个Region都包含多个Store, 一个Store就对应一个列族的数据 而一个Store可以有多个Store File 。Region 是 Hbase 中分布式存储和负载均衡的最小单元,但不是存储的最小单元。每一个Region都有开始的RowKey和结束的RowKey,代表着存储的Row的范围。

Region 分裂: 一开始每个 table 默认只有一个 region。当一个 region 逐渐变得很大时,它会分裂( split )成两个子 region,每个子 region 都包含了原来 region 一半的数据,这两个子 region 并行地在原来这个 region server 上创建,这个分裂动作会被报告给 HMaster。处于负载均衡的目的,HMaster 可能会将新的 region 迁移给其它 region server。

Splitting 一开始是发生在同一台 region server 上的,但是出于负载均衡的原因,HMaster 可能会将新的 regions 迁移 给其它 region server,这会导致那些 region server 需要访问离它比较远的 HDFS 数据,直到 major compaction 的到来,它会将那些远方的数据重新移回到离 region server 节点附近的地方。

MemStore

每个Store都包含一个MEMStore实例,MemStore是内存的存储对象,当 MemStore 的大小达到一个阀值(默认大小是 128M)时,如果超过了这个大小,那么就会进行刷盘,把内存里的数据刷进到 StoreFile 中,即生成一个快照。目前HBase 会有一个线程来负责MemStore 的flush操作。MemStore 在内存中缓存 HBase 的数据更新,以有序 KeyValues 的形式,这和 HFile 中的存储形式一样。每个 Column Family 都有一个 MemStore,所有的更新都以 Column Family 为单位进行排序。

Store

Store 对应着的是 Table 里面的 Column Family,不管有 CF 中有多少的数据,都会存储在 Store 中,这也是为了避免访问不同的 Store 而导致的效率低下。一个 CF 组成一个 Store ,默认是 10 G,如果大于 10G 会进行分裂。Store 是 HBase 的核心存储单元, 一个 Store 由 MemStore 和 StoreFile 组成

StoreFile

StoreFile底层是以 HFile 的格式保存数据

数据存储在 HFile 中,以 Key/Value 形式。当 MemStore 累积了足够多的数据后,整个有序数据集就会被写入一个新的 HFile 文件到 HDFS 上。整个过程是一个顺序写的操作,速度非常快, HFile 索引: 在 HFile 被打开时会被载入内存,这样数据查询只要一次硬盘查询

Catalog Tables : 记录所有RegionServer和Region的地址信息

  • 管理HBase所有的RegionServer 和Regions
  • Zookeeper 存储 Meta table的地址
  • -ROOT: 存储META Table的地址
  • .META:存储所有Region以及它的地址

4. 存储结构

HBase中 keyvalue 并不是简单的KV数据对,而是一个具有复杂元素的结构体,其中

Key由Row Length、Row、Column Family Length、Column Family、Column Qualifier(列名)、Time Stamp、Key Type七部分组成

Value是一个简单的二进制数据。

Key中元素KeyType表示该KeyValue的类型,取值分别为Put/Delete/Delete Column/Delete Family等。

TTL: TTL的概念只针对CELL

HBase中每个数据存储在一个CELL中,HBase可以设置每个CELL保存的最大version数,还可以设置version保存的最大时间TTL(Time To Live)

生存期(TTL)设置了一个基于时间戳的临界值,内部的管理会自动检查TTL值是否达到上限,超过TTL的值会被删除;TTL参数设置的单位为秒,默认值是 Integer .MAX_VALUE,即2147483647秒。使用默认值的数据即为永久保留;

存储结构 – 逻辑结构

存储结构 – 物理结构

5. 压缩算法

Minor Compaction

HBase 会自动合并一些小的 HFile,重写成少量更大的 HFiles。这个过程被称为 minor compaction 。它使用归并排序算法,将小文件合并成大文件,有效减少 HFile 的数量。

Flush触发条件

  • memstore级别限制: 当Region中任意一个MemStore的大小达到了上限(hbase.hregion.memstore.flush.size,默认128MB),会触发Memstore刷新。
  • region级别限制:当Region中所有Memstore的大小总和达到了上限(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size,默认 2* 128M = 256M),会触发memstore刷新。
  • Region Server级别限制:当一个Region Server中所有Memstore的大小总和超过低水位 阈值 hbase.regionserver.global.memstore.size.lower.limit*hbase.regionserver.global.memstore.size(前者默认值0.95),RegionServer开始强制flush;
  • HLog数量上限: hbase.regionserver.maxlogs
  • 定期刷新Memstore: 默认周期为1小时
  • 手动flush: flush ‘tablename’或者flush ‘region name’分别对一个表或者一个Region进行flush。

Major Compaction

Major Compaction 合并重写每个 Column Family 下的所有的 HFiles,成为一个单独的大 HFile,在这个过程中,被删除的和过期的 cell 会被真正从物理上删除,这能提高读的性能

6. 读写流程

当一个table刚被创建的时候,Hbase默认的分配一个region给table。也就是说这个时候,所有的读写请求都会访问到同一个regionServer的同一个region中,这个时候就达不到负载均衡的效果了,集群中的其他regionServer就可能会处于比较空闲的状态。 解决这个问题可以用 pre- splitting,在创建table的时候就配置好,生成多个region。

写入流程

1、客户端先访问zookeeper,获取Meta表位于那个region server
2、访问Meta表对应的region server服务器,根据请求的信息(namespace:table/rowkey),在meta表中查询出目标数据位于哪个region server的哪个region中。
并将该表的region信息以及meta表的位置信息缓存到客户端的meta cache,方便下次访问。
3、与目标数据的region server进行通讯
4、将数据写入到 WAL
5、将数据写入到对应的memstore中,
6、向客户端发送写入成功的信息
7、等达到memstore的刷写时机后,将数据刷写到HFILE中

put 操作的时候,可以手动指定 version 的时间戳的数值(一般可以指定一个比较小的数值),即可以手动指定版本,使最新的操作不是最新的 version。

读流程

Get -> Scan

1、Client客户端先访问zookeeper,获取 hbase:meta 表位于哪个Region Server
2、访问hbase:meta 表对应的region server服务器,根据请求的信息(namespace,table,rowkey),查询出目标表位于哪个Region Server中的哪个region。
并将该表的region信息,以及meta表的位置信息缓存在客户端的缓存中,以便下次访问。
3、与目标表所在的region server 进行通讯
4、分别在
Block Cache(读缓存) MemStore和 Store File查询目标数据 ,并 将查到的数据 进行 合并 ,此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)
5、 将从文件中查询到的数据块缓存到block cache
6、 将
合并后的数据 返回给 客户端

Scan

scan查询总是一行一行查询的,先查第一行的所有数据,再查第二行的所有数据

对于一行数据的查询,又可以分解为多个列族的查询,比如RowKey=row1的一行数据查询,首先查询列族1上该行的数据集合,再查询列族2里该行的数据集合

scanner体系的核心在于三层scanner:RegionScanner、StoreScanner以及StoreFileScanner。三者是层级的关系,一个RegionScanner由多个StoreScanner构成,一张表由多个列族组成,就有多少个StoreScanner负责该列族的数据扫描。一个StoreScanner又是由多个StoreFileScanner组成。每个Store的数据由内存中的MemStore和磁盘上的StoreFile文件组成,相对应的,StoreScanner对象会雇佣一个MemStoreScanner和N个StoreFileScanner来进行实际的数据读取,每个StoreFile文件对应一个StoreFileScanner,注意:StoreFileScanner和MemstoreScanner是整个scan的最终执行者。

该表有两个列族cf1和cf2(我们只关注cf1),cf1只有一个列name,表中有5行数据,其中每个cell基本都有多个版本。cf1的数据假如实际存储在三个区域,memstore中有r2和r4的最新数据,hfile1中是最早的数据。现在需要查询RowKey=r2的数据,按照上文的理论对应的Scanner指向就如图所示:

这三个Scanner组成的heap为<MemstoreScanner,StoreFileScanner2, StoreFileScanner1>,Scanner由小到大排列。查询的时候首先pop出heap的堆顶元素,即MemstoreScanner,得到keyvalue = r2:cf1:name:v3:name23的数据,拿到这个keyvalue之后,需要进行如下判定:

1. 检查该KeyValue的KeyType是否是Deleted/DeletedCol等,如果是就直接忽略该列所有其他版本,跳到下列(列族)

2. 检查该KeyValue的 Timestamp 是否在用户设定的Timestamp Range范围,如果不在该范围,忽略

3. 检查该KeyValue是否满足用户设置的各种filter过滤器,如果不满足,忽略

4. 检查该KeyValue是否满足用户查询中设定的版本数,比如用户只查询最新版本,则忽略该cell的其他版本;反正如果用户查询所有版本,则还需要查询该cell的其他版本。

现在假设用户查询所有版本而且该keyvalue检查通过,此时当前的堆顶元素需要执行next方法去检索下一个值,并重新组织最小堆。即图中MemstoreScanner将会指向r4,重新组织最小堆之后最小堆将会变为<StoreFileScanner2, StoreFileScanner1, MemstoreScanner>,堆顶元素变为StoreFileScanner2,得到keyvalue=r2:cf1:name:v2:name22,进行一系列判定,再next,再重新组织最小堆…

不断重复这个过程,直至一行数据全部被检索得到。继续下一行…

7. 数据备份

所有的读写都发生在 HDFS 的主 DataNode 节点上。 HDFS 会自动备份 WAL 和 HFile 的文件 blocks。HBase 依赖于 HDFS 来保证数据完整安全。当数据被写入 HDFS 时,一份会写入本地节点,另外两个备份会被写入其它节点。

8. 故障恢复

Zookeeper 依靠心跳检测发现节点故障,然后 HMaster 会收到 region server 故障的通知。

当 HMaster 发现某个 region server 故障,HMaster 会将这个 region server 所管理的 regions 分配给其它健康的 region servers。为了恢复故障的 region server 的 MemStore 中还未被持久化到 HFile 的数据,HMaster 会将 WAL 分割成几个文件,将它们保存在新的 region server 上。每个 region server 然后回放各自拿到的 WAL 碎片中的数据,来为它所分配到的新 region 建立 MemStore。

WAL 包含了一系列的修改操作,每个修改都表示一个 put 或者 delete 操作。这些修改按照时间顺序依次写入, 持久化 时它们被依次写入 WAL 文件的尾部。

当数据仍然在 MemStore 还未被持久化到 HFile 怎么办呢?WAL 文件会被回放。操作的方法是读取 WAL 文件,排序并添加所有的修改记录到 MemStore,最后 MemStore 会被刷写到 HFile。

9. 数据复制

10. HDFS

原理和架构与 Google 的 GFS 基本一致。它的特点主要有以下几项:

  • 和本地文件系统一样的目录树视图
  • Append Only 的写入(不支持随机写)
  • 顺序和随机读
  • 超大数据规模
  • 易扩展,容错率高

10.1 架构

几个阶段:

最早,使用社区版本,其 Switch Read 以读取一个 packet 的时长为统计单位,当读取一个 packet 的时间超过阈值时,认为读取当前 packet 超时。如果一定时间窗口内超时 packet 的数量过多,则认为当前节点是慢节点。但这个问题在于以 packet 作为统计单位使得算法不够敏感,这样使得每次读慢节点发生的时候,对于小 IO 场景(字节跳动的一些业务是以大量随机小 IO 为典型使用场景的),这些个积攒的 Packet 已经造成了问题。

后续,我们研发了 Hedged Read 的读优化。Hedged Read 对每一次读取设置一个超时时间。如果读取超时,那么会另开一个线程,在新的线程中向第二个副本发起读请求,最后取第一第二个副本上优先返回的 response 作为读取的结果。但这种情况下,在慢节点集中发生的时候,会导致读流量放大。严重的时候甚至导致小范围带宽短时间内不可用。

基于之前的经验,我们进一步优化,开启了 Fast Switch Read 的优化,该优化方式使用吞吐量作为判断慢节点的标准,当一段时间窗口内的吞吐量小于阈值时,认为当前节点是慢节点。并且根据当前的读取状况动态地调整阈值,动态改变时间窗口的长度以及吞吐量阈值的大小。 下表是当时线上某业务测试的值:

文章来源:智云一二三科技

文章标题:一文理解HBase+HDFS的原理和架构

文章地址:https://www.zhihuclub.com/185821.shtml

关于作者: 智云科技

热门文章

网站地图