Hbase读写流程及架构

1.首次读写的基本过程

有一个特殊的HBase目录表,叫做META table,保存了集群中各个region的位置。zookeeper中保存了这个meta table 的位置信息。

当我们第一次访问HBase集群时,会做以下操作:

1)客户端从zk中获取保存meta table的位置信息,知道meta table保存在了哪个region server,并在客户端缓存这个位置信息;

2)client会查询这个保存meta table的特定的region server,查询meta table信息,在table中获取自己想要访问的row key所在的region在哪个region server上。

3)客户端直接访问目标region server,获取对应的row
在这里插入图片描述
进一步,我们了解一下meta table的存储结构。

Meta table保存了所有region信息的一张表
Meta table存储的数据形式类似一颗b树
以keyvalue形式保存数据
Key: region的table name, start key等信息 Values: region server的相关信息

1.1 写请求

从上文我们知道了,client如何找到目标region server发起请求。

接下来,就是正式的写操作了。

当client将写请求发送到客户端后,会执行以下流程。

(1)获取行锁: HBase中使用行锁保证对同一行数据的更新都是互斥操作,用以保证更新的原子性。

(2)Append HLog:顺序写入HLog中,并执行sync。

(3)写缓存memstore

(4)释放行锁

这里需要重点关注WAL。

WAL(Write-Ahead Logging)是一种高效、高可靠的日志机制。

基本原理就是在数据写入时,通过先顺序写入日志,然后再写入缓存,等到缓存写满之后统一落盘。

为什么可以提高写入性能和可靠性呢?

众所周知,对于磁盘的写入,顺序写性能是远高于随机写的。因此,WAL将将一次随机写转化为了一次顺序写加一次内存写,提高了性能。

至于可靠性,我们可以看到,因为先写日志再写缓存,即使发生宕机,缓存数据丢失,那么我们也可以通过恢复日志还原出丢失的数据。

另一方面,我们需要关注一下HBase中的各个结构的关系。

在这里插入图片描述
每个region server上只有一个HLog,但是有多个region。

每个HRegion里面有多个HStore,每个HStore会有一个写入缓存memstore,memstore是根据columnfamily来划分。

因此,在一个写入操作中,我们对任意一行的改变是落在memstore上,然后HBase并不会直接将数据落盘,而是先写入缓存,等缓存满足一定大小之后再一起落盘,生成新的HFile。

1.1.2读请求

HBase-client上的读请求分为 两种,Get和Scan。

Get是一种随机查询的模式,根据给定的rowkey返回一行数据,虽然Get也支持输入多个rowkey返回多个结果,但是本质上是多次随机查询。具体rpc次数,看查询list的数据分布,如果都分布在一个region server上,就是一次rpc,如果是分布在3个rs,就是3次rpc,但是是并发请求和返回的,时间取决于最慢的那个。

Scan是一种批量查询的模式,根据指定的startRow和endRow进行范围扫描,获取区间内的数据。

而对于hbase服务端来说,当一个Get请求过来后,还是会转换为一个特殊的scan请求,即startrow和endrow一致的Scan请求。所以,下文的介绍,就围绕scan展开。

首先,我们要知道,HBase的写入很快,是追加多版本的形式,删除也很快,只是插入一条打上“deteled”标签的数据。因此,hbase的读操作比较复杂的,需要处理各种状态和关系。

因为Store是按照columfamily来划分的,一张表由N个列族组成,就有N个StoreScanner负责该列族的数据扫描。

当client要查询一个region,那么就会有一个RegionScanne,这个regionscannerr会创建N个StoreScanner。

而一个store由多个storefile和一个memstore组成,

因此,StoreScanner对象会创建一个MemStoreScanner和多个StoreFileScanner进行实际数据的读取。

这些scanner首先根据TimeRange和RowKey Range过滤掉一部分肯定无用的StoreFileScanner。

剩下的scanner组成一个最小堆KeyValueHeap。这个最小堆的实际数据结构是一个优先级队列,队列中所有元素是scanner,根据scanner指向的keyvalue进行排序(scanner类似游标,每次查询一个结果后,通过next下移找下一个kv值)。

举个简单的例子。

在这里插入图片描述

当某个scanner 内部数据完全检索之后会就会被 close 掉,或者 rowA 所有数据检索完毕,则查询下一条。

默认情况下返回的数据需要经过 ScanQueryMatcher 过滤返回的数据需要满足下面的条件:

该KeyValue不是已经删除的数据(KeyType不是Deleted/DeletedCol等)如果是就直接忽略该列所有其他版本,跳到下个列族;

该KeyValue的Timestamp是在用户设定的Timestamp Range范围内
该KeyValue满足用户设置的各种filter过滤器
该KeyValue满足用户查询中设定的版本数,比如用户只查询最新版本,则忽略该cell的其他版本;反正如果用户查询所有版本,则还需要查询该cell的其他版本。

我们经常听说HBase数据读取要读Memstore、HFile和Blockcache,为什么我们这里说Scanner只有StoreFileScanner和MemstoreScanner,而没有BlockcacheScanner呢?

因为HBase中数据仅独立地存在于Memstore和StoreFile中,Blockcache作为读缓存,里面有StoreFile中的部分热点数据,因此,如果有数据存在于Blockcache中,那么这些数据必然存在StoreFile中。因此使用MemstoreScanner和StoreFileScanner就可以覆盖到所有数据。

而在实际的读操作时,StoreFileScanner通过索引定位到待查找key所在的block之后,会先去查看该block是否存在于Blockcache中,如果存在,那么就会去BlockCache中取出,避免IO,如果BlockCache中不存在,才会再到对应的StoreFile中读取。

2.深入region server

一个region server运行在一个HDFS的DataNode 上,并且拥有以下组件:
在这里插入图片描述

1、WAL:全称Write Ahead Log, 属于分布式系统上的文件。主要用来存储还未被持久化到磁盘的新数据。如果新数据还未持久化,节点发生宕机,那么就可以用WAL来恢复这些数据。

2、BlockCache:是一个读缓存。它存储了被高频访问的数据。当这个缓存满了后,会清除最近最少访问的数据。

3、MenStore: 是一个写缓存。它存储了还未被写入磁盘的数据。它会在写入磁盘前,对自身数据进行排序,从而保证数据的顺序写入。每个region的每个colum family会有一份对应的memstore。(没错,如果节点宕机了,存在这个缓存里的数据没有落盘,可以通过WAL保证这些数据不会丢失)
4、HFiles:按照字典序存储各个row的键值。

2.1 HBase写数据与region server的交互

整个写的过程更加复杂,而与region server的交互式最重要的一部分,这里只介绍跟region server的交互。

主要分为两个步骤,写WAL 和 写缓存。
“实际上,这里除了保证数据不丢,还跟提高写入效率有关,具体后续专门写一个相关文档进行展开说明”
1)写WAL

当客户端提交了一个put 请求,那么在region server上需要首先写WAL(write-ahead-log)。

需要注意三点

Hlog是一个region server上一个,并不是一个region一个
写入数据是添加在log尾部
log上的数据主要为了保证没有落盘的数据能在server崩溃后不丢失

在这里插入图片描述
2)写缓存

数据写入WAL成功,才会继续写入MemStore。

然后才会返回ack给客户端,表示写入成功了。

在这里插入图片描述
MemStore主要保存数据更新在内存中,以字典序的KeyValue形式,跟HFile里面保存的一样。

每一个column family会有一个对应的memstore

更新的数据会在memstore中以key-value形式排好序存储,注意看图,按字典序排,同时按version的倒序排列。

我们可以看到,key的组成包括rowkey-cf-col-version
在这里插入图片描述

2.3 HBase region flush

当MemStore存储了足够多的数据,整个有序集会被写入一个新的HFile文件中,保存在HDFS。

HBase中每个colum family会有多个HFile,用来存储实际的keyValue。

注意,这里解释了为什么HBase中columfaily的数量是有限制的(具体是多少?)。

每一个cf有一个对应的MemStore,当一个MemStore满了,所属region的所有memstore都会被flush到磁盘。所以MemStore的flush的最小单位是一个region,而不是一个MemStore。

flush的同时,它还会存储一些额外的信息,比如最后一个写的序列号,让系统知道它当前持久化到什么位置了。

最大的序列号作为元数据,会被存储在每个HFile中,表示持久化到哪个位置了,下一次持久化应该从哪里继续。一个region启动时,会读取每个HFile的序列号,然后最大的序列号会被用来作为新的起始序列号。

在这里插入图片描述

3. 深入HFile

3.1 HFile的写入

HBase中,数据以有序KV的形式,存储在HFile中。当MemStore存储了足够的数据,全部kv对被写入HFile存入HDFS。

这里写文件的过程是顺序写,避免了硬盘大量移动磁头的过程,比随机写高效很多。

HFile的逻辑结构如图:
在这里插入图片描述
主要分为四个部分:Scanned block section,Non-scanned block section,Opening-time data section和Trailer。

1、Scanned block section:表示扫描HFile时,这部分所有数据块都会被读取,包括Leaf Index Block和Bloom Block。

2、Non-scanned block section:表示在扫描HFile时不会被读取,主要包括Meta Block和Intermediate Level Data Index Blocks两部分。

3、Load-on-open-section:表示在HBase的region server启动时,会被加载到内存中。包括FileInfo、Bloom filter block、data block index和meta block index。

4、Trailer:表示HFile的基本信息、各个部分的偏移值和寻址信息。

文件中采用类似b+树都多层索引:

1、Kv对按递增顺序存储;
2、Root index指向非叶子结点
3、每个数据块的最后一个key被放入中间索引(b+树的非叶子结点)
4、每个数据块有自己的叶子索引(b+树的叶子结点)
5、叶子索引通过row key指向64kb的kv数据块

在这里插入图片描述
文件的末尾有个trailer节点,指向了meta block。trailer节点还拥有其他信息,比如布隆过滤器和时间范围信息。

布隆过滤器帮助我们过滤那些不包含在这个HFilfe中的rowkey。

时间范围信息用来跳过那些不在这个HFilie时间范围内的row。

因此,当一个HFile被读取后,HFile的索引信息就会被缓存在BlockCache中,这样使得查询只需要一次磁盘查询操作,后续查找只需要读取blockcache内的索引信息即可。
在这里插入图片描述
region server上的实体结构关系如下:

regionserver : region = 1 : n,每个region server上有多个region。

region : store= 1 : n,每个region里面有多个store

store : memstore = 1 : 1。

Memstore:Hfile = 1:n。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值