HBase读取流程详解

news2024/11/18 11:20:59

读流程从头到尾可以分为如下4个步骤:Client-Server读取交互逻辑,Server端Scan框架体系,过滤淘汰不符合查询条件的HFile,从HFile中读取待查找Key。其中Client-Server交互逻辑主要介绍HBase客户端在整个scan请求的过程中是如何与服务器端进行交互的,理解这点对于使用HBase Scan API进行数据读取非常重要。了解Server端Scan框架体系,从宏观上介绍HBase RegionServer如何逐步处理一次scan请求。接下来的小节会对scan流程中的核心步骤进行更加深入的分析。

HBase读数据的流程更加复杂。主要基于两个方面的原因:一是因为HBase一次范围查询可能会涉及多个Region、多块缓存甚至多个数据存储文件;二是因为HBase中更新操作以及删除操作的实现都很简单,更新操作并没有更新原有数据,而是使用时间戳属性实现了多版本;删除操作也并没有真正删除原有数据,只是插入了一条标记为"deleted"标签的数据,而真正的数据删除发生在系统异步执行Major Compact的时候。很显然,这种实现思路大大简化了数据更新、删除流程,但是对于数据读取来说却意味着套上了层层枷锁:读取过程需要根据版本进行过滤,对已经标记删除的数据也要进行过滤。

一、Client-Server读取交互逻辑

Client-Server通用交互逻辑::Client首先会从ZooKeeper中获取元数据hbase:meta表所在的RegionServer,然后根据待读写rowkey发送请求到元数据所在RegionServer,获取数据所在的目标RegionServer和Region(并将这部分元数据信息缓存到本地),最后将请求进行封装发送到目标RegionServer进行处理。
在通用交互逻辑的基础上,数据读取过程中Client与Server的交互有很多需要关注的点。从API的角度看,HBase数据读取可以分为get和scan两类,get请求通常根据给定rowkey查找一行记录,scan请求通常根据给定的startkey和stopkey查找多行满足条件的记录。但从技术实现的角度来看,get请求也是一种scan请求(最简单的scan请求,scan的条数为1)。从这个角度讲,所有读取操作都可以认为是一次scan操作。

注意:
HBase Client端与Server端的scan操作并没有设计为一次RPC请求,这是因为一次大规模的scan操作很有可能就是一次全表扫描,扫描结果非常之大,通过一次RPC将大量扫描结果返回客户端会带来至少两个非常严重的后果:
(1)大量数据传输会导致集群网络带宽等系统资源短时间被大量占用,严重影响集群中其他业务。
(2)客户端很可能因为内存无法缓存这些数据而导致客户端OOM。

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

public static void scan() {
  HTable table=...;    
  Scan scan=new Scan();   
  scan.withStartRow(startRow)                   // 设置检索起始row    
  .withStopRow(stopRow)  // 设置检索结束row    
  .setFamilyMap(Map<byte[], Set<byte[]> familyMap>)       // 设置检索的列簇和对应列簇下的列集合    
  .setTimeRange(minStamp, maxStamp)           // 设置检索TimeRange 
  .setMaxVersions(maxVersions)                // 设置检索的最大版本号    
  .setFilter(filter)     // 设置检索过滤器    
  ...  
  scan.setMaxResultSize(10000);   
  scan.setBatch(100);  
  ResultScanner rs = table.getScanner(scan);  
  for (Result r : rs) {    
  	for (KeyValue kv : r.raw()) {    
  	......    
  	}  
  }
}

(1)for(Result r:rs)语句实际等价于Result r=rs.next()。每执行一次next()操作,客户端先会从本地缓存中检查是否有数据,如果有就直接返回给用户,如果没有就发起一次RPC请求到服务器端获取,获取成功之后缓存到本地。
(2)单次RPC请求的数据条数由参数caching设定,默认为Integer.MAX_VALUE。每次RPC请求获取的数据都会缓存到客户端,该值如果设置过大,可能会因为一次获取到的数据量太大导致服务器端/客户端内存OOM;而如果设置太小会导致一次大scan进行太多次RPC,网络成本高。
(3)对于很多特殊业务有可能一张表中设置了大量(几万甚至几十万)的列,这样一行数据的数据量就会非常大,为了防止返回一行数据但数据量很大的情况,客户端可以通过setBatch方法设置一次RPC请求的数据列数量。
(4)客户端还可以通过setMaxResultSize方法设置每次RPC请求返回的数据量大小(不是数据条数),默认是2G。

二、Server端Scan框架体系

一次scan可能会同时扫描一张表的多个Region,对于这种扫描,客户端会根据hbase:meta元数据将扫描的起始区间[startKey,stopKey)进行切分,切分成多个互相独立的查询子区间,每个子区间对应一个Region。比如当前表有3个Region,Region的起始区间分别为:[“a”,“c”),[“c”,“e”),[“e”,“g”),客户端设置scan的扫描区间为[“b”,“f”)。因为扫描区间明显跨越了多个Region,需要进行切分,按照Region区间切分后的子区间为[“b”,“c”),[“c”,“e”),[“e”,“f”)。

HBase中每个Region都是一个独立的存储引擎,因此客户端可以将每个子区间请求分别发送给对应的Region进行处理。下文会聚焦于单个Region处理scan请求的核心流程。

RegionServer接收到客户端的get/scan请求之后做了两件事情:首先构建scanner iterator体系;然后执行next函数获取KeyValue,并对其进行条件过滤。

1,构建Scanner Iterator体系

Scanner的核心体系包括三层Scanner:RegionScanner,StoreScanner,MemStoreScanner和StoreFileScanner。
在这里插入图片描述
三者是层级的关系:
(1)一个RegionScanner由多个StoreScanner构成。一张表由多少个列簇组成,就有多少个StoreScanner,每个StoreScanner负责对应Store的数据查找。
(2)一个StoreScanner由MemStoreScanner和StoreFileScanner构成。每个Store的数据由内存中的MemStore和磁盘上的StoreFile文件组成。相对应的,StoreScanner会为当前该Store中每个HFile构造一个StoreFileScanner,用于实际执行对应文件的检索。同时,会为对应MemStore构造一个MemStoreScanner,用于执行该Store中MemStore的数据检索。
注意:RegionScanner以及StoreScanner并不负责实际查找操作,它们更多地承担组织调度任务,负责KeyValue最终查找操作的是StoreFileScanner和MemStoreScanner。

构造好三层Scanner体系之后准备工作并没有完成,还需要以下几个非常核心的关键步骤:
1)过滤淘汰部分不满足查询条件的Scanner。StoreScanner为每一个HFile构造一个对应的StoreFileScanner,需要注意的事实是,并不是每一个HFile都包含用户想要查找的KeyValue,相反,可以通过一些查询条件过滤掉很多肯定不存在待查找KeyValue的HFile。主要过滤策略有:Time Range过滤、Rowkey Range过滤以及布隆过滤器,具体的过滤细节详见6.3.3节。图6-11中StoreFile3检查未通过而被过滤淘汰。
2)每个Scanner seek到startKey。这个步骤在每个HFile文件中(或MemStore)中seek扫描起始点startKey。如果HFile中没有找到starkKey,则seek下一个KeyValue地址。HFile中具体的seek过程比较复杂。
3)KeyValueScanner合并构建最小堆。将该Store中的所有StoreFileScanner和MemStoreScanner合并形成一个heap(最小堆),所谓heap实际上是一个优先级队列。在队列中,按照Scanner排序规则将Scanner seek得到的KeyValue由小到大进行排序。最小堆管理Scanner可以保证取出来的KeyValue都是最小的,这样依次不断地pop就可以由小到大获取目标KeyValue集合,保证有序性。
在这里插入图片描述

2,执行next函数获取KeyValue并对其进行条件过滤

经过Scanner体系的构建,KeyValue此时已经可以由小到大依次经过KeyValueScanner获得,但这些KeyValue是否满足用户设定的TimeRange条件、版本号条件以及Filter条件还需要进一步的检查。

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

三、过滤淘汰不符合查询条件的HFile

过滤StoreFile发生在KeyValueScanner合并构建最小堆,过滤手段主要有三种:根据KeyRange过滤,根据TimeRange过滤,根据布隆过滤器进行过滤。
(1)根据KeyRange过滤:因为StoreFile中所有KeyValue数据都是有序排列的,所以如果待检索row范围[startrow,stoprow]与文件起始key范围[firstkey,lastkey]没有交集,比如stoprow<firstkey或者startrow>lastkey,就可以过滤掉该StoreFile。
(2)根据TimeRange过滤:StoreFile中元数据有一个关于该File的TimeRange属性[miniTimestamp,maxTimestamp],如果待检索的TimeRange与该文件时间范围没有交集,就可以过滤掉该StoreFile;另外,如果该文件所有数据已经过期,也可以过滤淘汰。
(3)根据布隆过滤器进行过滤:StoreFile中布隆过滤器相关Data Block结构在第5章已经做过介绍,系统根据待检索的rowkey获取对应的Bloom Block并加载到内存(通常情况下,热点BloomBlock会常驻内存的),再用hash函数对待检索rowkey进行hash,根据hash后的结果在布隆过滤器数据中进行寻址,即可确定待检索rowkey是否一定不存在于该HFile。

四、从HFile中读取待查找Key

在这里插入图片描述

1,根据HFile索引树定位目标Block

HRegionServer打开HFile时会将所有HFile的Trailer部分和Load-on-open部分加载到内存,Load-on-open部分有个非常重要的Block——Root Index Block,即索引树的根节点。
BlockKey是整个Block的第一个rowkey,如Root Index Block中"a",“m”,“o”,"u"都为BlockKey。Block Offset表示该索引节点指向的Block在HFile的偏移量。
HFile索引树索引在数据量不大的时候只有最上面一层,随着数据量增大开始分裂为多层,最多三层。

一次查询的索引过程,基本流程可以表示为:
(1)用户输入rowkey为’fb’,在Root Index Block中通过二分查找定位到’fb’在’a’和’m’之间,因此需要访问索引’a’指向的中间节点。因为Root Index Block常驻内存,所以这个过程很快。
(2)将索引’a’指向的中间节点索引块加载到内存,然后通过二分查找定位到fb在index’d’和’h’之间,接下来访问索引’d’指向的叶子节点。
(3)同理,将索引’d’指向的中间节点索引块加载到内存,通过二分查找定位找到fb在index’f’和’g’之间,最后需要访问索引’f’指向的Data Block节点。
(4)将索引’f’指向的Data Block加载到内存,通过遍历的方式找到对应KeyValue。
上述流程中,Intermediate Index Block、Leaf Index Block以及Data Block都需要加载到内存,所以一次查询的IO正常为3次。但是实际上HBase为Block提供了缓存机制,可以将频繁使用的Block缓存在内存中,以便进一步加快实际读取过程。

2,BlockCache中检索目标Block

BlockCache组件在第5章做过详细介绍,根据内存管理策略的不同经历了LRUBlockCache、SlabCache以及BucketCache等多个方案的发展,在内存管理优化、GC优化方面都有了很大的提升。
但无论哪个方案,从BlockCache中定位待查Block都非常简单。Block缓存到BlockCache之后会构建一个Map,Map的Key是BlockKey,Value是Block在内存中的地址。其中BlockKey由两部分构成——HFile名称以及Block在HFile中的偏移量。BlockKey很显然是全局唯一的。根据BlockKey可以获取该Block在BlockCache中内存位置,然后直接加载出该Block对象。如果在BlockCache中没有找到待查Block,就需要在HDFS文件中查找。

3,HDFS文件中检索目标Block

在这里插入图片描述
根据文件索引提供的Block Offset以及Block DataSize这两个元素可以在HDFS上读取到对应的Data Block内容(核心代码可以参见HFileBlock.java中内部类FSReaderImpl的readBlockData方法)。这个阶段HBase会下发命令给HDFS,HDFS执行真正的Data Block查找工作。
整个流程涉及4个组件:HBase、NameNode、DataNode以及磁盘。其中HBase模块做的事情上文已经做过了说明,需要特别说明的是FSDataInputStream这个输入流,HBase会在加载HFile的时候为每个HFile新建一个从HDFS读取数据的输入流——FSDataInputStream,之后所有对该HFile的读取操作都会使用这个文件级别的InputStream进行操作。

使用FSDataInputStream读取HFile中的数据块,命令下发到HDFS,首先会联系NameNode组件。NameNode组件会做两件事情:
(1)找到属于这个HFile的所有HDFSBlock列表,确认待查找数据在哪个HDFSBlock上。众所周知,HDFS会将一个给定文件切分为多个大小等于128M的Data Block,NameNode上会存储数据文件与这些HDFSBlock的对应关系。
(2)确认定位到的HDFSBlock在哪些DataNode上,选择一个最优DataNode返回给客户端。HDFS将文件切分成多个HDFSBlock之后,采取一定的策略按照三副本原则将其分布在集群的不同节点,实现数据的高可靠存储。HDFSBlock与DataNode的对应关系存储在NameNode。

NameNode告知HBase可以去特定DataNode上访问特定HDFSBlock,之后,HBase会再联系对应DataNode。DataNode首先找到指定HDFSBlock,seek到指定偏移量,并从磁盘读出指定大小的数据返回。
DataNode读取数据实际上是向磁盘发送读取指令,磁盘接收到读取指令之后会移动磁头到给定位置,读取出完整的64K数据返回。

4,从Block中读取待查找KeyValue

HFile Block由KeyValue(由小到大依次存储)构成,但这些KeyValue并不是固定长度的,只能遍历扫描查找。

文章来源:《HBase原理与实践》 作者:胡争;范欣欣

文章内容仅供学习交流,如有侵犯,联系删除哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/388923.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重构·改善既有代码的设计.01

前言近期在看Martin Fowler著作的《重构.改善既有代码的设计》这本书&#xff0c;这是一本经典著作。书本封面誉为软件开发的不朽经典。书中从一个简单的案例揭示了重构的过程以及最佳实践。同时给出了重构原则&#xff0c;何时重构&#xff0c;以及重构的手法。用来改善既有代…

Vue2.0开发之——购物车案例-Goods组件封装-商品名称和图片(46)

一 概述 循环渲染Goods组件为Goods组件封装title属性为Goods组件封装pic属性 二 循环渲染Goods组件 2.1 App.vue中导入Goods组件 import Goods from /components/Goods/Goods.vue2.2 App.vue中注册Goods组件 components: {Header,Goods}2.3 循环渲染每一个商品的信息 <…

记录--在Vue3这样子写页面更快更高效

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 在开发管理后台过程中&#xff0c;一定会遇到不少了增删改查页面&#xff0c;而这些页面的逻辑大多都是相同的&#xff0c;如获取列表数据&#xff0c;分页&#xff0c;筛选功能这些基本功能。而…

windows下neo4j安装及配置,并绘制人物关系图谱

neo4j安装及配置&#xff0c;绘制人物关系图谱 先升级pip&#xff0c;安装py2neo pip install py2neo2021.0.1依赖 jdk1.8&#xff0c; neo4j 3.xx&#xff1b; 或者jdk18&#xff0c;neo4j 4.x&#xff0c;5.x&#xff1b; 官网下载了neo4j4.x,5.x 因为jdk版本原因都不行&am…

段错误排查方法与防御性措施~

什么是段错误 首先我们需要知道什么是段错误&#xff0c;才能对症下药。 段错误是一种在程序运行时发生的错误&#xff0c;通常是由于程序试图访问不在其地址空间范围内的内存引起的。 例如&#xff0c;当一个程序访问空指针或者已经被释放的内存时&#xff0c;就有可能触发…

Mybatis框架的搭建与使用

Mybatis框架的搭建 一.创建新模块 二、在pom.xml导入依赖 <dependencies><!-- Mybatis核心 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version></dependenc…

软件研发管理经验总结 - 技术管理

软件研发管理经验总结 - 技术管理 技术管理主要负责有技术团队建设、管理团队成员技术相关事务、帮助团队成员成长、负责团队成员交付的代码质量、以及负责产品技术方向、以及产品相关前沿技术调研&#xff1b;管理团队成员技术相关事务有代码Review、故障率跟踪、分析及根据分…

算法系列之数值积分的目的

PLC算法里的数字积分器详细介绍请参看下面的文章链接: PLC算法系列之数值积分器(Integrator)_RXXW_Dor的博客-CSDN博客数值积分和微分在工程上的重要意义不用多说,闭环控制的PID控制器就是积分和微分信号的应用。流量累加也会用到。有关积分运算在流量累加上的应用,请参看下…

LeetCode 349. 两个数组的交集和 692. 前K个高频单词

两个数组的交集 难度 简单 题目链接 这道题的难度不大&#xff0c;我们可以把数组里的数据存到set里面。这样就完成了排序和去重&#xff0c;然后我们再把一个set里面的数据和另外一个set数据进行比较。如果相同就插入到数组里。 代码如下&#xff1a; 但是这个算法的时间复…

自学大数据第四天~hadoop集群的搭建

Hadoop集群安装配置 当hadoop采用分布式模式部署和运行时,存储采用分布式文件系统HDFS,此时HDFS名称节点和数据节点位于不同的机器上; 数据就可以分布到多个节点,不同的数据节点上的数据计算可以并行执行了,这时候MR才能发挥其本该有的作用; 没那么多机器怎么办~~~~多几个虚拟…

网络安全之资产及攻击面管理

“摸清家底&#xff0c;认清风险”做好资产管理是安全运营的第一步。那么什么是资产&#xff0c;资产管理的难点痛点是什么&#xff0c;如何做好资产管理&#xff0c;认清风险。带着这些问题我们来认识一下资产及攻击面管理。 一、资产的定义 《GBT 20984-2007信息安全技术信…

论文阅读_善用Midjourney

论文信息 name_en: Grimm in Wonderland: Prompt Engineering with Midjourney to Illustrate Fairytales name_ch: 用Midjourney生成格林童话插图 paper_addr: http://arxiv.org/abs/2302.08961 date_publish: 2023-02-17 author: Martin Ruskov,米兰大学 读后感 针对生成图…

力扣-计算特殊奖金

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;1873. 计算特殊奖金二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总…

【C++】stl_list介绍和实现,list和vector区别,list vector string 迭代器失效

本篇博客详细介绍list的实现&细节讲解&#xff0c;并且在文章末对list和vector&#xff0c;string进行区分和复习 list的基本结构就是双向带头循环链表&#xff0c;链表和顺序表的差别我们在前面数据结构的时候早就学过了&#xff0c;不再赘述 在使用stl库里面list时&…

3.1 第一个外设GPIO GPIO输出

GPIO简介•GPIO是通用输入输出口&#xff08;俗称IO口&#xff09;•可配置为8种输入输出模式•引脚电平&#xff1a;0V~3.3V&#xff08;数据0是0V&#xff0c;数据1是3.3V&#xff09;&#xff0c;部分引脚可容忍5V.•输出模式可控制端口输出高低电平&#xff0c;用以驱动LED…

Javascript周学习小结(初识,变量,数据类型)

JS的三大书写方式行内式如图所示&#xff1a;几点说明&#xff1a;JS的行内式写在HTML的标签内部&#xff0c;(常以on开头)&#xff0c;如onclick行内式常常使用单引号括住字符串以区分HTML的双引号可读性差&#xff0c;不建议使用引号易出错&#xff0c;不建议使用特殊情况下使…

【冲刺蓝桥杯的最后30天】day2

大家好&#x1f603;&#xff0c;我是想要慢慢变得优秀的向阳&#x1f31e;同学&#x1f468;‍&#x1f4bb;&#xff0c;断更了整整一年&#xff0c;又开始恢复CSDN更新&#xff0c;从今天开始更新备战蓝桥30天系列&#xff0c;一共30天&#xff0c;如果对你有帮助或者正在备…

Java代码是如何被CPU狂飙起来的?

&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3; &#x1f38d;大家好&#xff0c;我是慕枫 &#x1f38d;前阿里巴巴高级工程师&#xff0c;InfoQ签约作者、阿里云专家博主&#xff0c;一直致力于用大白话讲解技术知识 &#x…

Stable Diffusion WebUI安装instruct-pix2pix插件

instruct-pix2pix作者团队提出了一种通过人类自然语言指令编辑图像的方法。他们的模型能够接受一张图像和相应的文字指令(也就是prompt)&#xff0c;根据指令来编辑图像。作者团队使用两个预训练模型&#xff08;一个是语言模型GPT-3, 另一个是文本到图像模型Stable Diffusion)…

Python---正则表达式

专栏&#xff1a;python 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;Python在学&#xff0c;希望能够得到各位的支持&#xff01;&#xff01;&#xff01; 正则表达式前言概念作用和特点使用场景正则符号re模块re.compile()match()search()span()findall()group()sub()…