title: HBase系列
注意,其实上图中的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 缓存。
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
Master 职责
1、为 RegionServer 分配 region
2、负责 RegionServer 的负载均衡
3、发现失效的 RegionServer 并重新分配其上的 region
4、HDFS 上的垃圾文件(hbase)回收
5、处理 schema 更新请求(表的创建,删除,修改,列簇的增加等等)
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
StoreFile
保存的是实际数据的物理文件,StoreFile 以 HFile(一种文件存储格式,类似于TextFile、ORC、Parquet等,都属于数据格式)的形式存储在 HDFS 上。每个 Store 会有一个或多个 StoreFile(HFile),数据在每个 StoreFile 中都是有序的。
MemStore
是写缓存,由于 HFile 中的数据要求是有序的,所以数据是先存储在 MemStore 中,排好顺序后,等到达刷写的条件的时候才会刷写形成 HFile 文件,每次刷写都会形成一个新的 HFile。
WAL
数据要经 MemStore 排序后才能刷写到 HFile,但是将数据保存在内存中,可能会数据丢失,为了解决这个问题,数据会先写在一个叫做 Write Ahead Log 的文件中,然后再写入 MemStore 中。所以在系统出现故障时候,数据可以通过这个日志文件重建。
Store
Store是物理上的概念,对应逻辑上的概念列簇
HFile
HFile是一种文件存储格式,和TextFile、Parquet、ORC 一样,都属于存储格式。刷写出来的文件形成了StoreFile,以HFile这种格式进行存储。是一种比较特殊的存储格式,内部是以键值对的方式来进行存储的。
说明:
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 组成
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 部分没有这么复杂的结构,就是纯粹的二进制数据了。
一个 HRegion由多个 Store 组成,每个 Store 包含一个列族的所有数据
Store 包括位于内存的一个 memstore 和位于硬盘的多个 storefile 组成
写操作先写入 memstore,当 memstore 中的数据量达到某个阈值,HRegionServer 启动 flushcache 进程写入 storefile,每次写入形成单独一个 Hfile
当总 storefile 大小超过一定阈值后,会把当前的 region 分割成两个,并由 HMaster 分配给相 应的 region 服务器,实现负载均衡
客户端找数据的时候,先在memstore 里面找,找不到再到storefile里面去找。
1、client 先根据 rowkey 找到对应的 region 所在的 regionserver
2、client 向 regionserver 提交写请求
3、regionserver 找到目标 region
4、region 检查数据是否与 schema 一致
5、如果客户端没有指定版本,则获取当前系统时间作为数据版本
6、将更新写入 WAL log
7、将更新写入 Memstore
8、判断 Memstore 的是否需要 flush 为 Store 文件。
补充说明:
刷写时机:
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 小时)。
有一个很详细的默认的配置文件 hbase-default.xml 。默认配置都在这个里面。
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:读数据是按照时间戳的顺序来读的,并不是按照我们平时所认为的先读内存,在读磁盘,这种常识思维来的。完全是按照时间戳的先后来读,如果磁盘中的数据比内存中的数据的时间戳新,那么读取的就是磁盘中的。
下面测试一下这个细节:
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>
2、去hdfs的查看一下
/hbase/data/default/stu/abc159c5d597a821fb6b20f29c6e7704/info
可以发现info这个列簇下面的没有数据
3、手动刷写到磁盘上
hbase(main):009:0> flush 'stu'
Took 0.4177 seconds
hbase(main):010:0>
可以看到已经刷写成zhangsan了。
也可以通过命令看一下这个HFile格式的文件。自己上网百度命令。
4、在Linux窗口中给时间格式转化为时间戳的样式
[root@hadoop11 bin]# date -d "2021-10-13T17:38:09.220" +%s
1634117889
[root@hadoop11 bin]#
上面的结果是秒
可以加上3个0 变成 毫秒
5、再次手动添加一条带有时间戳的数据,并且这个时间戳比前面一条数据小。
hbase(main):021:0> put 'stu','1001','info:name','lisi',1634117889000
Took 0.0094 seconds
hbase(main):022:0>
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>
这个时候zhangsan的数据在磁盘上,而lisi的数据在内存中,但是磁盘上面的数据的时间戳新。所以返回的是磁盘上面的数据。
1、基本概念
我们知道,memstore每次刷写都会生成一个新的HFile,并且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的 HFile 中,所以呀,在查询时候需要遍历所有的 HFile。那么为了减少 HFile 的个数,以及清理掉过期和删除的数据,会进行 StoreFile Compaction。这样显而易见效率会高。
2、Compaction 的分类
分别是 Minor Compaction 和 Major Compaction。
Minor Compaction(小合并)会将临近的若干个较小的 HFile 合并成一个较大的 HFile,但不会清理过期和删除的数据。其实就是:只选取一些小的、相邻的HFile将它们合并成一个更大的 HFile 。
Major Compaction (大合并)会将一个 Store 下的所有的 HFile 合并成一个大 HFile,并且会清理掉过期和删除的数据。换句话说:将一个Store下的所有 HFile 合并成一个大文件,并执行物理删除操作。
1、基本概念
在默认的情况下,每个 Table 起初只有一个 Region(因为数据量小啊),随着数据的不断写入,Region 会自动进行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,考虑到负载均衡,HMaster 有可能会将某个 Region 转移给其他的 Region Server。
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、基本概念
在HBase中间,一条数据的唯一标识就是 RowKey,那么这条数据存储于哪个分区,取决于 RowKey 处于哪个一个预分区的区间内,设计 RowKey 的主要目的其实就是让数据均匀的分布于所有的region 中,这样的话,可以在一定程度上防止数据倾斜。
下面看看怎么设计行键比较合适
2、设计方法
1、Hash散列
对rowkey进行hash,这样传递过来的数据就可以随机了。
2、字符串反转
这样可以在一定程度上将 put 进来的顺序的值分散开。
3、加随机前缀
也是给顺序均匀分散开
HBase 在操作过程中需要大量的内存开销,这是因为 Table表 是可以缓存在内存中的,一般会分配整个可用内存的 70%-80% 给 HBase 使用即可。但是不建议分配非常大的堆内存,了解java的小伙伴可能知道, GC 过程持续太久会导致 RegionServer 处于长期不可用状态,其实一般设置 20~50G 内存就可以了。这只是建议,仅供参考。
在HBase中间,每一个 region 维护着起始行 StartRow 与结束行 EndRow ,如果加入的数据符合某个 Region 维护的RowKey 范围,则该数据就交给这个 Region 维护。
依照这个原则,我们可以提前将数据所要投放的分区提前大致的规划好,从而提高 HBase 性能。
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>
注意,上面命令是创建有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
4、再次查看页面
分区的范围是 左闭右开 。
会发现WriteRequests里面分别在对应的Region里面有对应的一条数据。这就说明数据按照规则插入到对应的分区里面去了。
声明:
文章中代码及相关语句为自己根据相应理解编写,文章中出现的相关图片为自己实践中的截图和相关技术对应的图片,若有相关异议,请联系删除。感谢。转载请注明出处,感谢。
B站: https://space.bilibili.com/1523287361 点击打开链接
微博地址: http://weibo.com/luoyepiaoxue2014 点击打开链接