HBase底层架构

1. HBase是什么

是一个分布式的、面向列存储的 NoSQL数据库,基于 Google 的 Bigtable 设计,用于处理海量的结构化数据。HBase 架构的独特性使其在大数据领域得到了广泛应用,主要用来存储非结构化和半结构化的松散数据

HBase 最早由 Apache Hadoop 的开发者开发,用于解决在 Hadoop 分布式文件系统(HDFS)上存储和检索大量数据时面临的挑战。传统的关系型数据库在处理大规模数据时效率低下,难以扩展。而 HBase 作为一个 NoSQL 数据库,提供了对大量数据的高效读写操作,并且具有高度的扩展性。

项目需求是构建一个可以处理数十亿条记录的大规模数据存储系统,要求系统能够承载高并发的读写请求,同时在数据量急剧增长的情况下,系统性能不会显著下降。HBase 的设计正是为此类需求量身定制。

2. HBase数据检索与存储

img

模块职责
HBase ClientHBase Client 为用户提供了访问 HBase 的接口,可以通过元数据表来定位到目标数据的 RegionServer,另外 HBase Client 还维护了对应的 cache 来加速 Hbase 的访问,比如缓存元数据的信息
HMasterHMaster 是 HBase 集群的主节点,负责整个集群的管理工作,主要工作职责如下分配Region:负责启动的时候分配Region到具体的 RegionServer;负载均衡:一方面负责将用户的数据均衡地分布在各个 Region Server 上,防止Region Server数据倾斜过载。另一方面负责将用户的请求均衡地分布在各个 Region Server 上,防止Region Server 请求过热维护数据:发现失效的 Region,并将失效的 Region 分配到正常的 RegionServer 上,并且在Region Sever 失效的时候,协调对应的HLog进行任务的拆分。
Region Serverimg直接对接用户的读写请求,干活的节点,每一个Region server大约可以管理1000个region,处理对这些HRegion的IO请求,也就是说客户端直接和HRegionServer打交道,主要职责如下管理 HMaster 为其分配的 Region;负责与底层的 HDFS 交互,存储数据到 HDFS;负责 Region 变大以后的拆分以及 StoreFile 的合并工作。
Region(table)Region Server 包含多个Region。每一个 Region 都有起始 RowKey 和结束 RowKey,代表了存储的Row的范围,保存着表中某段连续的数据。一开始Region可能只有一个,随着数据增多(StoreFile变大,默认256M),进行Region的水平切分,分成了多个Region。当 Region 很多时,HMaster 会将 Region 保存到其他 Region Server 上
HLog(WAL)每个Region Server会有一个HLog,负责记录着数据的操作日志,当HBase出现故障时可以进行日志重放、故障恢复。例如故障时MemStore没有写会磁盘
Store(列蔟)一个 Region 包含多个 Store ,每个 Store 都对应一个 Column Family, Store 包含 MemStore 和 StoreFile
MemStore(Store的内存存储)数据的写操作会先写到 MemStore 中,当MemStore 中的数据增长到一个阈值(默认64M)后。Region Server 会启动 flasheatch 进程将 MemStore 中的数据写人 StoreFile 持久化存储,每次写入后都形成一个单独的 StoreFile。找数据会先从MemStore开始找,找不到才去StoreFile
StoreFile(HFile)StoreFile底层是以 HFile 的格式保存。HBase以Store的大小来判断是否需要切分Region
HFile是HBase中KeyValue数据的存储格式,是hadoop的二进制格式文件。一个StoreFile对应着一个HFile。而HFile是存储在HDFS之上的
ZookeeperHBase 通过 ZooKeeper 来完成选举 HMaster、监控 Region Server、维护元数据集群配置等工作,主要工作职责如下选举Master:如果 HMaster 异常,则会通过选举机制(写入节点成功的节点作为HMaster)产生新的 HMaster 来提供服务监控Region Server: 通过 ZooKeeper 来监控 Region Server 的状态,当Region Server 有异常的时候,通过回调的形式通知 HMaster 有关Region Server 上下线的信息维护元数据和集群配置信息
HDFS为 HBase 提供底层数据存储服务,同时为 HBase提供高可用的支持

3. HBase数据模型与操作

3.1. 数据模型

基本存储单位是表(Table),表由行(Row)和列族(Column Family)组成。每个列族可以包含多个列(Column),而列的数据通过时间戳(Timestamp)进行版本控制

Row KeyColumn Family(Cloumn Family):Column(列限定符)ValueTimestamp
row1cf1:col1value11627871234000
row1cf1:col2value21627871235000
row2cf1:col1value31627871236000
字段含义
RowKey唯一标识一行记录,由于HBase都是按照rowKey进行region路由,因此针对rowKey的设计需要注意
Column FamilyHBase中的每个列都由Cloumn Family(列簇)和Cloumn Qualifier(列限定符)进行限定,例如info:name,info:age。建表时,只需指明列簇,而列限定符无需预先定义
Time Stamp用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会自动为其加上该字段,其值为写入HBase的时间

Cell

{ RowKey, ColumnFamily: ColumnQualifier, TimeStamp}

唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存贮

img

3.2. 主要操作

HBase 提供了丰富的 API 进行数据操作,包括 PutGetDeleteScanPut 用于写入数据,Get 用于读取数据,Delete 用于删除数据,Scan 用于批量读取数据。

  1. Put:将数据写入表中。
  2. Get:根据行键读取数据。
  3. Delete:删除指定行或列的数据。
  4. Scan:遍历表中的数据。

3.2.1. 写流程

img

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

更新操作:并没有真正更新原有数据,而是使用时间戳属性实现了多版本;

删除操作:没有真正删除原有数据,只是插入了一条标记为"deleted"标签的数据,而真正的数据删除发生在系统异步执行Major Compact的时候

3.2.2. 读流程

img

  1. 获取meta表信息:客户端先访问zookeeper,获取Meta表位于那个region server,根据根据请求的信息(namespace:table/rowkey),在meta表中查询出目标数据位于哪个region server的哪个region中,然后缓存在客户端中
  2. 请求对应region server
  3. 分别在Block Cache(读缓存)MemStore和 Store File查询目标数据,并将查到的数据进行**合并,**此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)
  4. 将从文件中查询到的数据块缓存到block cache
  5. 合并后的数据返回给客户端

API操作主要划分如下:

  1. GET
  2. SCAN - 一般HBase会根据设置条件将一次大的scan操作拆分为多个RPC请求,每个RPC请求称为一次next请求,每次只返回规定数量的结果,类似于ES的search-after。每次可以设置setBatch(条数)、setMaxResultSize(数据量大小)

3.2.2.1. Scan查询详细流程

Scan查询大致分为下述四个流程

  1. Client-Server读取交互逻辑
  2. HBase的scan框架
  3. HBase过滤淘汰不符合条件的HFile
  4. 从HFile中读取待查找Key
3.2.2.1.1. Client-Server读取交互逻辑

Client首先会从ZooKeeper中获取元数据hbase:meta表所在的RegionServer,然后根据待读写rowkey发送请求到元数据所在RegionServer,获取数据所在的目标RegionServer和Region(并将这部分元数据信息缓存到本地),最后将请求进行封装发送到目标RegionServer进行处理。

HBase Client端与Server端的scan操作并没有设计为一次RPC请求,这是因为一次大规模的scan操作很有可能就是一次全表扫描,扫描结果非常之大,通过一次RPC将大量扫描结果返回客户端会带来至少两个非常严重的后果:

•大量数据传输会导致集群网络带宽等系统资源短时间被大量占用,严重影响集群中其他业务。

•客户端很可能因为内存无法缓存这些数据而导致客户端OOM。

实际上HBase会根据设置条件将一次大的scan操作拆分为多个RPC请求,每个RPC请求称为一次next请求,每次只返回规定数量的结果。下面是一段scan的客户端示例代码:

3.2.2.1.2. HBase的scan框架

Scan中,会根据startRowKey、endRowKey查询多个Region server

RegionServer接收到客户端的get/scan请求之后做了两件事情:

  1. 首先构建scanner iterator体系;
  2. 然后执行next函数获取KeyValue,并对其进行条件过滤
3.2.2.1.2.1. scanner iterator体系

Scanner的核心体系包括三层Scanner:RegionScanner,StoreScanner,MemStoreScanner和StoreFileScanner。三者是层级的关系:

  • 一个RegionScanner由多个StoreScanner构成。一张表由多少个列簇组成,就有多少个StoreScanner,每个StoreScanner负责对应Store的数据查找。
  • 一个StoreScanner由MemStoreScanner和StoreFileScanner构成。每个Store的数据由内存中的MemStore和磁盘上的StoreFile文件组成。相对应的,StoreScanner会为当前该Store中每个HFile构造一个StoreFileScanner,用于实际执行对应文件的检索。同时,会为对应MemStore构造一个MemStoreScanner,用于执行该Store中MemStore的数据检索。

需要注意的是,RegionScanner以及StoreScanner并不负责实际查找操作,它们更多地承担组织调度任务,负责KeyValue最终查找操作的是StoreFileScanner和MemStoreScanner。三层Scanner体系可以用图表示。

img

3.2.2.1.3. HBase过滤淘汰不符合条件的HFile

通过不同的scanner进行查找,过滤HFile->根据Key在HFile进行读取数据,最终产出一个keyValue(Cell)的优先队列(小顶堆),next的查询模式就是基于这个优先队列(Key ValueScanner)进行keyValue的查找,并最终从HFile中读取到数据

其中keyValue的优先队列的生成过程如下

  1. 过滤淘汰部分不满足查询条件的StoreScanner,可以先基于用户查询中的TimeRange,Rowkey Range过滤以及布隆过滤器过滤部份HFile

    1. TimeRange(时序数据):StoreFile中元数据有一个关于该File的TimeRange属性[ miniTimestamp, maxTimestamp ],如果待检索的TimeRange与该文件时间范围没有交集,就可以过滤掉该StoreFile;另外,如果该文件所有数据已经过期,也可以过滤淘汰
    2. Rowkey Range:因为StoreFile中所有KeyValue数据都是有序排列的,所以如果待检索row范围[ startrow,stoprow ]与文件起始key范围[ f irstkey,lastkey ]没有交集,比如stoprow < firstkey或者startrow > lastkey,就可以过滤掉该StoreFile
    3. 布隆过滤器:主要根据Bloom Block,待检索的rowkey获取对应的Bloom Block并加载到内存(通常情况下,热点Bloom Block会常驻内存的),再用hash函数对待检索rowkey进行hash,根据hash后的结果在布隆过滤器数据中进行寻址,即可确定待检索rowkey是否一定不存在于该HFile
  2. 每个Scanner seek到startKey。这个步骤在每个HFile文件中(或MemStore)中seek扫描起始点startKey(下文:从HFile中读取待查找Key)。如果HFile中没有找到starkKey,则seek下一个KeyValue地址,这个过程会比较复杂。

  3. KeyValueScanner合并构建最小堆。将该Store中的所有StoreFileScanner和MemStoreScanner合并形成一个heap(最小堆)

  4. 最后,执行最终读取,例如执行next函数获取KeyValue并对其进行条件过滤

    1. 检查该KeyValue的KeyType是否是Deleted/DeletedColumn/DeleteFamily等,如果是,则直接忽略该列所有其他版本,跳到下列(列簇)。
    2. 检查该KeyValue的Timestamp是否在用户设定的Timestamp Range范围,如果不在该范围,忽略。
    3. 检查该KeyValue是否满足用户设置的各种filter过滤器,如果不满足,忽略。
    4. 检查该KeyValue是否满足用户查询中设定的版本数,比如用户只查询最新版本,则忽略该列的其他版本;反之,如果用户查询所有版本,则还需要查询该cell的其他版本。

注意,其中一个KeyValue其实就是一个cell = {rowKey,colounm family,timeStamp,value}

img

3.2.2.1.3.1. 各种Filter过滤器

在HBase里,ScanFilter 支持多种过滤操作,以下为你详细介绍:

\1. 比较过滤器

  • SingleColumnValueFilter:此过滤器用于筛选特定列族和列限定符的值,它依据比较运算符和比较器来判定是否保留该行。示例代码如下:
SingleColumnValueFilter filter = new SingleColumnValueFilter(
    Bytes.toBytes("cf"),
    Bytes.toBytes("column"),
    CompareOperator.EQUAL,
    new BinaryComparator(Bytes.toBytes("value"))
);
scan.setFilter(filter);
  • RowFilter:按照行键对数据进行过滤,同样借助比较运算符和比较器来实现。示例如下:
RowFilter rowFilter = new RowFilter(
    CompareOperator.EQUAL,
    new BinaryComparator(Bytes.toBytes("rowkey"))
);
scan.setFilter(rowFilter);
  • FamilyFilter:用于筛选特定列族的数据,通过比较运算符和比较器达成。示例如下:
FamilyFilter familyFilter = new FamilyFilter(
    CompareOperator.EQUAL,
    new BinaryComparator(Bytes.toBytes("cf"))
);
scan.setFilter(familyFilter);
  • QualifierFilter:该过滤器可筛选特定列限定符的数据,利用比较运算符和比较器完成。示例如下:
QualifierFilter qualifierFilter = new QualifierFilter(
    CompareOperator.EQUAL,
    new BinaryComparator(Bytes.toBytes("column"))
);
scan.setFilter(qualifierFilter);

\2. 组合过滤器

  • FilterList:能够把多个过滤器组合起来使用,支持 MUST_PASS_ALL(所有过滤器都必须通过)和 MUST_PASS_ONE(只要有一个过滤器通过即可)两种组合方式。示例如下:
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
filterList.addFilter(filter1);
filterList.addFilter(filter2);
scan.setFilter(filterList);

\3. 专用过滤器

  • PageFilter:用于实现分页功能,可指定每页返回的行数。示例如下:
PageFilter pageFilter = new PageFilter(10); // 每页返回 10 行
scan.setFilter(pageFilter);
  • PrefixFilter:按照行键的前缀进行过滤,只返回行键以指定前缀开头的行。示例如下:
PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("prefix"));
scan.setFilter(prefixFilter);
  • ColumnPrefixFilter:根据列限定符的前缀来筛选数据,只返回列限定符以指定前缀开头的列。示例如下:
ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes("prefix"));
scan.setFilter(columnPrefixFilter);
  • FirstKeyOnlyFilter:仅返回每行的第一个键值对,常用于快速统计行数。示例如下:
FirstKeyOnlyFilter firstKeyOnlyFilter = new FirstKeyOnlyFilter();
scan.setFilter(firstKeyOnlyFilter);

这些过滤器能让你在HBase中精准地筛选数据,以满足不同的业务需求。

3.2.2.1.3.2. 其他API

时间处理HBase的Scan操作中是有与时间相关的过滤器的,下面为你详细介绍:

\1. TimestampsFilter

该过滤器允许你指定一系列时间戳,只有当单元格的时间戳与指定的时间戳相匹配时,才会返回该单元格。以下是Java代码示例:

import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.TimestampsFilter;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class TimeStampFilterExample {
    public static void main(String[] args) throws IOException {
        // 创建连接
        Connection connection = ConnectionFactory.createConnection();
        // 获取表
        Table table = connection.getTable(TableName.valueOf("your_table_name"));

        // 创建Scan对象
        Scan scan = new Scan();

        // 指定时间戳列表
        List<Long> timestamps = new ArrayList<>();
        timestamps.add(1609459200000L); 
        timestamps.add(1609545600000L); 

        // 创建TimestampsFilter
        TimestampsFilter filter = new TimestampsFilter(timestamps);
        scan.setFilter(filter);

        // 执行扫描
        ResultScanner scanner = table.getScanner(scan);
        for (Result result : scanner) {
            // 处理结果
            System.out.println(result);
        }

        // 关闭资源
        scanner.close();
        table.close();
        connection.close();
    }
}

在这个示例中,我们创建了一个**TimestampsFilter**,并指定了两个时间戳。只有时间戳与这两个值相匹配的单元格才会被返回。

\2. 在Scan对象中直接设置时间范围

除了使用过滤器,你还可以在**Scan**对象中直接设置时间范围,只有时间戳在该范围内的单元格才会被返回。示例代码如下:

import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

public class TimeRangeScanExample {
    public static void main(String[] args) throws IOException {
        // 创建连接
        Connection connection = ConnectionFactory.createConnection();
        // 获取表
        Table table = connection.getTable(TableName.valueOf("your_table_name"));

        // 创建Scan对象
        Scan scan = new Scan();
        // 设置时间范围
        long startTime = 1609459200000L; 
        long endTime = 1609545600000L; 
        scan.setTimeRange(startTime, endTime);

        // 执行扫描
        ResultScanner scanner = table.getScanner(scan);
        for (Result result : scanner) {
            // 处理结果
            System.out.println(result);
        }

        // 关闭资源
        scanner.close();
        table.close();
        connection.close();
    }
}

在上述代码里,我们使用**setTimeRange**方法设置了一个时间范围,只有时间戳在这个范围内的单元格才会被返回。

3.2.2.1.4. 从HFile中读取待查找Key

最后根据KevValue去HFIle中查找具体数据,查询流程如下:

img

3.2.3. MemStore刷写

img

MemStore触发刷写的场景如下:

  1. 单一MemStore超大小128M:某个MemStore的大小达到了hbase.hregion.memstore.flush.size(默认值 128M),其所在 region 的所有 memstore (对应的列簇)都会刷写,当达到128 * N 还没有刷写,此时会拒绝写入。

两个相关参数的默认值如下:

hbase.hregion.memstore.flush.size=128M(默认)

hbase.hregion.memstore.block.multiplier=4(默认)

  1. memstore总大小超过堆内存:当 region server 中 memstore 的总大小达到java_heapsize(应用的堆内存)*hbase.regionserver.global.memstore.size时

hbase.regionserver.global.memstore.size=0.4(默认值)

  1. 定时刷写:到达自动刷写的时间,也会触发 memstore flush,默认时1h
  2. WAL文件超大:WAL 文件的数量超过 hbase.regionserver.maxlogs,region 会按照时间顺序依次进行刷写

3.2.4. 数据合并(StoreFile Compaction)

img

为什么需要执行数据合并?

由于MemStore每次刷写都会生成一个新的HFile,同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清除掉过期和删除的数据,会进行StoreFile Compaction

Compaction分为两种

  1. Minor Compaction:会将临时的若干较小的HFile合并成一个较大的HFile,但不会清理过期和删除的数据
  2. Major Compaction:会将一个Store下的所有HFile合并为一个大HFile,并且会清理掉过期和删除的数据

3.2.5. 数据拆分

img

默认情况下,每个 Table 起初只有一个 Region,随着数据的不断写入,Region 会自动进行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但处于负载均衡的考虑,HMaster 有可能会将某个 Region 转移给其他的 Region Server

拆分时机:

  1. Region中总Store大小超限制:hbase.hregion.max.filesize,该 Region 就会进行拆分(0.94 版本之前)。
  2. Region中某个Store下所有StoreFile大小超限

4. HBase的扩展性和高可用

HBase 的架构设计使其具备良好的扩展性和高可用性。

特性描述
扩展性HBase 可以通过增加 RegionServer 节点来实现水平扩展。当数据量增长时,HMaster 可以将 Region 划分为更小的 Region,并将其分配到新的 RegionServer 上。
高可用性通过 Zookeeper 监控集群中的各个节点,HBase 实现了自动故障恢复机制。当一个 RegionServer 发生故障时,HMaster 会将其管理的 Region 重新分配给其他健康的 RegionServer。

相关文档:

https://cloud.tencent.com/developer/article/2184702

https://cloud.tencent.com/developer/article/2448014

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×