• 分布式NoSQL数据库HBase实践与原理剖析(二)



    title: HBase系列


    第五章 HBase核心原理

    5.1 系统架构

    在这里插入图片描述

    注意,其实上图中的HLog应该在HRegionServer里面,而不是在HRegion里面。所以图有点点问题。其实通过后面的物理存储的图也能发现这个问题。

    Client 职责

    1、HBase 有两张特殊表:
    .meta.:记录了用户表的 Region 信息,.META.可以有多个 regoin 
    -root-:记录了.META.表的 Region 信息,-ROOT-只有一个 region 。注意,0.98版本之后就没这个表了。
    
    2、Client 访问用户数据前需要首先访问 zookeeper,找到-root-表的 region 所在的位置,然后 访问-root-表,接着访问.meta.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过 client 端会做 cache 缓存。
    以上是老版本。
    
    下面是新版本(098版本以后)
    1、HBase 有一张特殊表:
    .meta.:记录了用户表的 Region 信息
    
    2、Client 访问用户数据前需要首先访问 zookeeper,找到.meta.表的 region 所在的位置,然后 访问.meta.表,才能找到用户数据的位置去访问,中间需要多次网络操作,不过 client 端会做 cache 缓存。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ZooKeeper 职责

    老版本:
    1、ZooKeeper 为 HBase 提供 Failover 机制,选举 master,避免单点 master 单点故障问题 
    2、存贮所有 Region 的寻址入口:-root-表在哪台服务器上。-root-这张表的位置信息 
    3、实时监控 RegionServer 的状态,将 RegionServer 的上线和下线信息实时通知给 Master 
    4、存储 HBase 的 schema,包括有哪些 table,每个 table 有哪些 column family 
    
    新版本:
    1、ZooKeeper 为 HBase 提供 Failover 机制,选举 master,避免单点 master 单点故障问题 
    2、存贮所有 Region 的寻址入口:.meta.表在哪台服务器上。.meta.这张表的位置信息 
    3、实时监控 RegionServer 的状态,将 RegionServer 的上线和下线信息实时通知给 Master 
    4、存储 HBase 的 schema,包括有哪些 table,每个 table 有哪些 column family 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Master 职责

    1、为 RegionServer 分配 region 
    2、负责 RegionServer 的负载均衡 
    3、发现失效的 RegionServer 并重新分配其上的 region 
    4、HDFS 上的垃圾文件(hbase)回收 
    5、处理 schema 更新请求(表的创建,删除,修改,列簇的增加等等) 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    RegionServer 职责

    1、RegionServer 维护 Master 分配给它的 region,处理对这些 region 的 IO 请求 
    2、RegionServer 负责 Split 在运行过程中变得过大的 region 。 负责 Compact 操作 
    
    可以看到,client 访问 hbase 上数据的过程并不需要 master 参与(寻址访问 zookeeper 和 RegioneServer,数据读写访问 RegioneServer), master 仅仅维护者 table 和 region 的元数据 信息,负载很低。
    
    .meta. 存的是所有的 region 的位置信息,那么 RegioneServer 当中 region 在进行分裂之后 的新产生的 region,是由 master 来决定发到哪个 RegioneServer,这就意味着,只有 master 知道 new region 的位置信息,所以,由 master 来管理.meta.这个表当中的数据的 CRUD 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    StoreFile

    保存的是实际数据的物理文件,StoreFile 以 HFile(一种文件存储格式,类似于TextFile、ORC、Parquet等,都属于数据格式)的形式存储在 HDFS 上。每个 Store 会有一个或多个 StoreFile(HFile),数据在每个 StoreFile 中都是有序的。
    
    • 1

    MemStore

    是写缓存,由于 HFile 中的数据要求是有序的,所以数据是先存储在 MemStore 中,排好顺序后,等到达刷写的条件的时候才会刷写形成 HFile 文件,每次刷写都会形成一个新的 HFile。 
    
    • 1

    WAL

    数据要经 MemStore 排序后才能刷写到 HFile,但是将数据保存在内存中,可能会数据丢失,为了解决这个问题,数据会先写在一个叫做 Write Ahead Log 的文件中,然后再写入 MemStore 中。所以在系统出现故障时候,数据可以通过这个日志文件重建。
    
    • 1

    Store

    Store是物理上的概念,对应逻辑上的概念列簇
    
    • 1

    HFile

    HFile是一种文件存储格式,和TextFile、Parquet、ORC 一样,都属于存储格式。刷写出来的文件形成了StoreFile,以HFile这种格式进行存储。是一种比较特殊的存储格式,内部是以键值对的方式来进行存储的。
    
    • 1

    5.2 物理存储

    5.2.1 整体物理结构

    在这里插入图片描述

    说明:

    1、Table 中的所有行都按照 rowkey 的字典序排列。 
    
    2、Table 在行的方向上分割为多个 HRegion。 
    
    3、HRegion 按大小分割的(默认 10G),每个表一开始只有一个 HRegion,随着数据不断插入表, HRegion 不断增大,当增大到一个阀值的时候,HRegion 就会等分会两个新的 HRegion。当表中的行不断增多,就会有越来越多的 HRegion。
    
    4、HRegion 是 HBase 中分布式存储和负载均衡的最小单元。最小单元就表示不同的 HRegion 可以分布在不同的 HRegion Server 上。但一个 HRegion 是不会拆分到多个 Server 上的。
    
    5、HRegion 虽然是负载均衡的最小单元,但并不是物理存储的最小单元。事实上,HRegion 由一个或者多个 Store 组成,每个 Store 保存一个 Column Family。每个 Store 又由一个 memStore 和 0 至多个 StoreFile 组成 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5.2.2 StoreFile和HFile结构

    ​ StoreFile 以 HFile 格式保存在 HDFS 上,请看下图 Hfile 的数据组织格式:

    在这里插入图片描述

    ​ 首先 HFile 文件是不定长的,长度固定的只有其中的两块:Trailer 和 FileInfo。正如图中所示 的,Trailer 中有指针指向其他数据块的起始点。
    ​ File Info 中记录了文件的一些 Meta 信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY 等。

    ​ HFile 分为六个部分:
    Data Block 段----保存表中的数据,这部分可以被压缩
    Meta Block 段 (可选的)–保存用户自定义的 kv 对,可以被压缩。
    File Info 段—–HFile 的元信息,不被压缩,用户也可以在这一部分添加自己的元信息。
    Data Block Index 段–Data Block 的索引。每条索引的 key 是被索引的 block 的第一条记录的 key。
    Meta Block Index 段 (可选的)–Meta Block 的索引。

    Trailer 段–—这一段是定长的。保存了每一段的偏移量,读取一个 HFile 时,会首先 读取 Trailer, Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index 会被读取到内存中,这样,当检索某个 key 时,不需要扫描整个 HFile,而只需从内存中找 到 key 所在的 block,通过一次磁盘 io 将整个 block 读取到内存中,再找到需要的 key。 DataBlock Index 采用 LRU 机制淘汰。
    HFile 的 Data Block,Meta Block 通常采用压缩方式存储,压缩之后可以大大减少网络 IO 和磁盘 IO,随之而来的开销当然是需要花费 cpu 进行压缩和解压缩。
    目标 Hfile 的压缩支持两种方式:Gzip,LZO。 Snappy

    ​ Data Index 和 Meta Index 块记录了每个 Data 块和 Meta 块的起始点。

    ​ Data Block 是 HBase I/O 的基本单元,为了提高效率,HRegionServer 中有基于 LRU 的 Block Cache 机制。每个 Data 块的大小可以在创建一个 Table 的时候通过参数指定,大号的 Block 有利于顺序 Scan,小号 Block 利于随机查询。 每个 Data 块除了开头的 Magic 以外就是一个 个 KeyValue 对拼接而成, Magic 内容就是一些随机数字,目的是防止数据损坏。

    ​ HFile 里面的每个 KeyValue 对就是一个简单的 byte 数组。但是这个 byte 数组里面包含了很 多项,并且有固定的结构。我们来看看里面的具体结构:

    在这里插入图片描述

    ​ 开始是两个固定长度的数值,分别表示 Key 的长度和 Value 的长度。紧接着是 Key,开始是固定长度的数值,表示 RowKey 的长度,紧接着是 RowKey,然后是固定长度的数值,表示 Family 的长度,然后是 Family,接着是 Qualifier,然后是两个固定长度的数值,表示 Time Stamp 和 Key Type(Put/Delete)。 Value 部分没有这么复杂的结构,就是纯粹的二进制数据了。

    5.2.3 MemStore和StoreFile

    ​ 一个 HRegion由多个 Store 组成,每个 Store 包含一个列族的所有数据

    ​ Store 包括位于内存的一个 memstore 和位于硬盘的多个 storefile 组成

    ​ 写操作先写入 memstore,当 memstore 中的数据量达到某个阈值,HRegionServer 启动 flushcache 进程写入 storefile,每次写入形成单独一个 Hfile

    ​ 当总 storefile 大小超过一定阈值后,会把当前的 region 分割成两个,并由 HMaster 分配给相 应的 region 服务器,实现负载均衡

    ​ 客户端找数据的时候,先在memstore 里面找,找不到再到storefile里面去找。

    第六章 HBase读写流程

    6.1 HBase写数据

    6.1.1 理论大图

    在这里插入图片描述

    6.1.2 文字补充

    1、client 先根据 rowkey 找到对应的 region 所在的 regionserver
    2、client 向 regionserver 提交写请求
    3、regionserver 找到目标 region
    4、region 检查数据是否与 schema 一致
    5、如果客户端没有指定版本,则获取当前系统时间作为数据版本
    6、将更新写入 WAL log
    7、将更新写入 Memstore
    8、判断 Memstore 的是否需要 flush 为 Store 文件。

    6.1.3 细节补充之MemStore Flush

    在这里插入图片描述

    补充说明:

    刷写时机:
    
    1、当一个 memstore 的大小达到了 hbase.hregion.memstore.flush.size(默认值 128M),它所在 region 的所有 memstore 都会刷写。
    并且这是一个阻塞写,也就是在刷写的过程中,写线程阻塞。
    
    2、当 region server 中 memstore 的总大小达到
    java_heapsize * hbase.regionserver.global.memstore.size(默认值 0.4) * hbase.regionserver.global.memstore.size.lower.limit(默认值 0.95)的时候, 
    region 会按照其所有 memstore 的大小顺序依次进行刷写。直到 RegionServer 中所有 memstore 的总大小减小到上述值以下。
    
    再次当 region server 中 memstore 的总大小达到 java_heapsize*hbase.regionserver.global.memstore.size(默认值 0.4)时,会阻止继续往所有的 memstore 写数据。
    
    其实就是两个缓冲阈值。
    
    3、当达到了自动刷写的时间,也会触发 memstore flush。
    自动刷新的时间间隔可以通过属性进行配置,属性为: hbase.regionserver.optionalcacheflushinterval(默认 1 小时)。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    有一个很详细的默认的配置文件 hbase-default.xml 。默认配置都在这个里面。

    6.2 HBase读数据

    6.2.1 理论大图

    在这里插入图片描述

    6.2.2 文字补充

    0、假如读取一条数据,get 'stu','1001'
    1、客户端问ZooKeeper请求获取meta表的位置信息(若是老的版本会先请求获取-root-表,然后通过-root-表里面的数据获取到.meta.表的位置),然后进一步通过.meta.表里面的数据,得到目标数据所在的RegionServer(就是数据 所在的 region 的主机地址) 
    2、联系 regionserver 查询目标数据 
    3、regionserver 定位到目标数据所在的 region,发出查询请求 
    4、region 先在 memstore 中查找,命中则返回 
    5、如果在 memstore 中找不到 , 则在 storefile 中扫描( 可能会扫描到很多的 storefile----BloomFilter) 
    
    (BloomFilter,**布隆过滤器:迅速判断一个元素是不是在一个庞大的集合内**,但是他有一个弱点:它有一定的误判率) 
    (**误判率**:原本不存在这个集合中的元素,布隆过滤器有可能会判断说它存在,但是,如果布隆过滤器,判断说某一个元素不存在这个集合,那么该元素就一定不在该集合内)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6.2.3 细节补充读取的先后顺序

    细节1:读数据是按照时间戳的顺序来读的,并不是按照我们平时所认为的先读内存,在读磁盘,这种常识思维来的。完全是按照时间戳的先后来读,如果磁盘中的数据比内存中的数据的时间戳新,那么读取的就是磁盘中的。

    下面测试一下这个细节:

    1、创建一下表先

    hbase(main):005:0> create 'stu','info'
    hbase(main):007:0> put 'stu','1001','info:name','zhangsan'
    Took 0.2334 seconds
    hbase(main):008:0> scan 'stu'
    ROW                                         COLUMN+CELL
     1001                                       column=info:name, timestamp=2021-10-13T17:38:09.520, value=zhangsan                                                            
    1 row(s)
    Took 0.0984 seconds
    hbase(main):009:0> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、去hdfs的查看一下

    /hbase/data/default/stu/abc159c5d597a821fb6b20f29c6e7704/info
    可以发现info这个列簇下面的没有数据
    
    • 1
    • 2

    在这里插入图片描述

    3、手动刷写到磁盘上

    hbase(main):009:0> flush 'stu'
    Took 0.4177 seconds
    hbase(main):010:0> 
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    可以看到已经刷写成zhangsan了。

    在这里插入图片描述

    也可以通过命令看一下这个HFile格式的文件。自己上网百度命令。

    4、在Linux窗口中给时间格式转化为时间戳的样式

    [root@hadoop11 bin]# date -d "2021-10-13T17:38:09.220" +%s
    1634117889
    [root@hadoop11 bin]# 
    上面的结果是秒
    可以加上3个0 变成 毫秒
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5、再次手动添加一条带有时间戳的数据,并且这个时间戳比前面一条数据小。

    hbase(main):021:0> put 'stu','1001','info:name','lisi',1634117889000
    Took 0.0094 seconds 
    hbase(main):022:0> 
    
    • 1
    • 2
    • 3

    6、再次查询,发现数据的结果还是zhangsan

    hbase(main):022:0> scan 'stu'
    ROW                                         COLUMN+CELL
     1001                                       column=info:name, timestamp=2021-10-13T17:38:09.520, value=zhangsan                                                            
    1 row(s)
    Took 0.0301 seconds
    hbase(main):023:0> 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    这个时候zhangsan的数据在磁盘上,而lisi的数据在内存中,但是磁盘上面的数据的时间戳新。所以返回的是磁盘上面的数据。

    6.3 HBase分分合合那些事

    6.3.1 StoreFile Compaction合并

    1、基本概念

    我们知道,memstore每次刷写都会生成一个新的HFile,并且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的 HFile 中,所以呀,在查询时候需要遍历所有的 HFile。那么为了减少 HFile 的个数,以及清理掉过期和删除的数据,会进行 StoreFile Compaction。这样显而易见效率会高。
    
    • 1

    2、Compaction 的分类

    分别是 Minor Compaction 和 Major Compaction。
    
    Minor Compaction(小合并)会将临近的若干个较小的 HFile 合并成一个较大的 HFile,但不会清理过期和删除的数据。其实就是:只选取一些小的、相邻的HFile将它们合并成一个更大的 HFile 。
    
    Major Compaction (大合并)会将一个 Store 下的所有的 HFile 合并成一个大 HFile,并且会清理掉过期和删除的数据。换句话说:将一个Store下的所有 HFile 合并成一个大文件,并执行物理删除操作。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.3.2 Region Split切分

    1、基本概念

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

    2、切分的时机

    1、当某个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize 的时候,该 Region 就会进行拆分(0.94 版本之前)。
    
    2、当某个 region 中的某个 Store 下所有 StoreFile 的总大小超过 Min(R^2 * "hbase.hregion.memstore.flush.size",hbase.hregion.max.filesize"),该 Region 就会进行拆分,其中 R 为当前 Region Server 中属于这个Table表的个数(0.94 版本之后)。
    
    • 1
    • 2
    • 3

    第七章 HBase补充性知识

    7.1 Rowkey的设计

    1、基本概念

    在HBase中间,一条数据的唯一标识就是 RowKey,那么这条数据存储于哪个分区,取决于 RowKey 处于哪个一个预分区的区间内,设计 RowKey 的主要目的其实就是让数据均匀的分布于所有的region 中,这样的话,可以在一定程度上防止数据倾斜。
    下面看看怎么设计行键比较合适
    
    • 1
    • 2

    2、设计方法

    1、Hash散列
    对rowkey进行hash,这样传递过来的数据就可以随机了。
    
    2、字符串反转
    这样可以在一定程度上将 put 进来的顺序的值分散开。
    
    3、加随机前缀
    也是给顺序均匀分散开
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    7.2 内存优化

    ​ HBase 在操作过程中需要大量的内存开销,这是因为 Table表 是可以缓存在内存中的,一般会分配整个可用内存的 70%-80% 给 HBase 使用即可。但是不建议分配非常大的堆内存,了解java的小伙伴可能知道, GC 过程持续太久会导致 RegionServer 处于长期不可用状态,其实一般设置 20~50G 内存就可以了。这只是建议,仅供参考。

    7.3 预先分区

    7.3.1 概念

    在HBase中间,每一个 region 维护着起始行 StartRow 与结束行 EndRow ,如果加入的数据符合某个 Region 维护的RowKey 范围,则该数据就交给这个 Region 维护。
    依照这个原则,我们可以提前将数据所要投放的分区提前大致的规划好,从而提高 HBase 性能。
    
    • 1
    • 2

    7.3.2 具体操作示例

    1、创建表

    hbase(main):023:0> create 'stu2','info',SPLITS =>['1000','2000','3000']
    Created table stu2
    Took 2.2349 seconds
    => Hbase::Table - stu2
    hbase(main):024:0> 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意,上面命令是创建有4个分区的表stu2 。

    2、去页面中查看

    在这里插入图片描述

    3、往里面put数据看看

    put 'stu2','1001','info:name','zhangsan'
    put 'stu2','2001','info:name','lisi'
    put 'stu2','3001','info:name','wangwu'
    put 'stu2','0001','info:name','zhaoliu'
    
    hbase(main):024:0> put 'stu2','1001','info:name','zhangsan'
    Took 0.0816 seconds
    hbase(main):025:0> put 'stu2','2001','info:name','lisi'
    Took 0.0101 seconds
    hbase(main):026:0> put 'stu2','3001','info:name','wangwu'
    Took 0.0370 seconds
    hbase(main):027:0> put 'stu2','0001','info:name','zhaoliu'
    Took 0.0281 seconds
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4、再次查看页面

    分区的范围是 左闭右开

    会发现WriteRequests里面分别在对应的Region里面有对应的一条数据。这就说明数据按照规则插入到对应的分区里面去了。

    在这里插入图片描述



    声明:
            文章中代码及相关语句为自己根据相应理解编写,文章中出现的相关图片为自己实践中的截图和相关技术对应的图片,若有相关异议,请联系删除。感谢。转载请注明出处,感谢。


    By luoyepiaoxue2014

    B站: https://space.bilibili.com/1523287361 点击打开链接
    微博地址: http://weibo.com/luoyepiaoxue2014 点击打开链接

  • 相关阅读:
    【深度学习之模型优化】模型剪枝、模型量化、知识蒸馏概述
    Unity记录5.5-地图-随机生成连续洞穴块
    有一种密码学专用语言叫做ASN.1
    第一章 C语言知识点(程序)
    2.2python 常用的数据结构之列表(list)_python量化实用版教程(初级)
    路由器ARP和ARP-proxy(华为)
    什么是RPA?一文了解RPA发展与进程!
    C#.NET 国密SM2 签名验签 与JAVA互通 ver:20230807
    es : java 查询
    网站如何优化
  • 原文地址:https://blog.csdn.net/luoyepiaoxue2014/article/details/128059530