Hbase底层原理和预分区实践

简介:

HBase是一个典型的NOsql数据库,以其独特的列式存储和顺序读写(磁盘的顺序读写比内存的随机读写还要高效),能做到高效读取和存储海量数据,是大数据存储和数仓建设中很重要的工具

在讲rowkey设计和预分区之前,让我们来看看hbase数据是如何根据rowkwy找到属于自己的region进行存储

一、Hbase寻址和读写原理
架构分析

1、HMaster
负责管理HBase元数据,即表的结构、表存储的Region等元信息。
负责表的创建,删除和修改(因为这些操作会导致HBase元数据的变动)。
负责为HRegionServer分配Region,分配好后也会将元数据写入相应位置(后面会详细讲述放在哪)。
如果对可用性要求较高,它需要做HA高可用(通过Zookeeper)。但是HMaster不会去处理Client端的数据读写请求,因为这样会加大其负载压力,具体的读写请求它会交给HRegionServer来做。

2、HRegionServer
一个RegionServer里有多个Region。
处理Client端的读写请求(根据从HMaster返回的元数据找到对应的Region来读写数据)。
管理Region的Split分裂、StoreFile的Compaction合并。
一个RegionServer管理着多个Region,在HBase运行期间,可以动态添加、删除HRegionServer。

3、HRegion
一个HRegion里可能有1个或多个Store。
HRegionServer维护一个HLog。
HRegion是分布式存储和负载的最小单元。
表通常被保存在多个HRegionServer的多个Region中。
因为HBase用于存储海量数据,故一张表中数据量非常之大,单机一般存不下这么大的数据,故HBase会将一张表按照行水平将大表划分为多个Region,每个Region保存表的一段连续数据。 初始只有1个Region,当一个Region增大到某个阈值后,便分割为两个。

4、Store
Store是存储落盘的最小单元,由内存中的MemStore和磁盘中的若干StoreFile组成。
一个Store里有1个或多个StoreFile和一个memStore。
每个Store存储一个列族。

读写原理

写过程
1、Client访问ZK,根据ROOT表获取meta表所在Region的位置信息,并将该位置信息写入Client Cache。
(注:为了加快数据访问速度,我们将元数据、Region位置等信息缓存在Client Cache中。)

2、Client读取meta表,再根据meta表中查询得到的Namespace、表名和RowKey等相关信息,获取将要写入Region的位置信息(此过程即Region三层定位,如下图),最后client端会将meta表写入Client Cache。

3、Client向上一步HRegionServer发出写请求,HRegionServer先将操作和数据写入HLog(预写日志,Write Ahead Log,WAL),再将数据写入MemStore,并保持有序。
(联想:HDFS中也是如此,EditLog写入时机也是在真实读写之前发生)

4、当单个regiion里的多个MemStore的数据缓存大小超过阈值时(默认是128M,老版本是64M),将数据溢写磁盘,生成一个StoreFile文件。
当Store中StoreFile的数量超过阈值时,将若干小StoreFile合并(Compact)为一个大StoreFile。
当Region中最大Store的大小超过阈值时,Region分裂(Split),等分成两个子Region。

读过程:
1、获取将要读取Region的位置信息(同读的1、2步)。
2、Client向HRegionServer发出读请求。
3、HRegionServer先从MemStore读取数据,如未找到,再从StoreFile中读取。

寻址机制!!!(重要)
首先:rowkey寻址和数据存放是根据字典排序
在这里插入图片描述
现在假设我们要从用户表
里面查询一条RowKey是rk00888的数据。那么我们应该遵循以下步骤:

  1. 从.META.表里面查询哪个Region包含这条数据:有多个region,每个region存储的数据有startkey和endkey,是一个范围(例如:第一个region的数据范围是rk00000~rk99999,那么数据rk00888所在的位置就在第一个region里)
  2. 获取管理这个Region的RegionServer地址。
  3. 连接这个RegionServer, 查到这条数据。
二、rowkey设计和预分区

rowkey的设计和预分区的startkey、endkey决定数据存放是否均匀

rowkey设计:

前面了解到,hbase中数据存放是根据rowkey的字典顺序而存放进不同的region里面,所以合理的设计rowkey来优化读写就显得尤为重要:

rowkey唯一原则
必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。

rowkey长度原则
rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际业务中一般为10-100bytes,以 byte[] 形式保存,一般设计成定长。
建议越短越好,不要超过16个字节,原因如下:

数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;
MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
目前操作系统都是64位系统,内存8字节对齐,控制在16个字节,8字节的整数倍利用了操作系统的最佳特性。

rowkey散列原则
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。

预分区:

1、为什么要进行预分区?
HBase默认建表时有一个region,这个region的rowkey是没有边界的,即没有startkey和endkey,在数据写入时,所有数据都会写入这个默认的region,当一个region中数据超过阈值时(默认10G),此region已经不能承受不断增长的数据量,会进行split,分成2个region。在此过程中,会产生两个问题:
a.我们的数据会不断的往一个region上写,会有写热点问题。
b.region split会消耗宝贵的集群I/O资源。
c.如果在任务执行时region分裂,会导致任务失败

基于此我们可以控制在建表的时候,创建多个空region,并确定每个region的起始和终止rowky,这样只要我们的rowkey设计能均匀的命中各个region,就不会存在写热点问题。自然split的几率也会大大降低。当然随着数据量的不断增长,该split的还是要进行split。像这样预先创建hbase表分区的方式,称之为预分区

首先看没有预分区的:
一个region里面就保存了7000w(虽然7000w对hbase来说是小case)的数据,读写压力全在这一个region上,会很容易造成热点问题

在这里插入图片描述
2、使用Hbase shell进行预分区
前面说到了rowkey的排列是根据字典排序,所以rowkey寻址的时候是和startkey、endkey进行比较,在字典排序在哪个region的startkey、endkey之间,就落在哪个region

			create 't1','f1',SPLITS => ['10','20','30']

创建了一个列簇 f 的表 t1,预分4个区,第一个 region 包含从 ‘\x00’ - ‘\x30’ 的所有 key(’\x31’ 是 ASCII码中的 1)
在这里插入图片描述
四个分区,rowkey如果是‘0232534’,就落在第一个分区,‘130034
24’落在第二个分区(字典排序!!!!)

自定义分区:

	hbase>create 't14','f',SPLITS_FILE=>'splits.txt'

可以使用 SPLITS_FILE 来指定一个文本文件,文件内写入拆分点(即startkey,endkey),通过这种方式来自定义拆分点
splits.txt文件截图:
在这里插入图片描述
预分区截图:
在这里插入图片描述
3、预分区的实际应用
在实际的业务场景中,因为数据量多,正常预分区个数在200~2000的范围,分区个数参考:
hbase数据日增1G:200region

hbase自带的分区算法

# 基于随机算法创建一个有4默认个分区的表
hbase>create 't2','f1', { NUMREGIONS => 200 , SPLITALGO => 'UniformSplit' }
 
# 基于 hex keys 创建一个有500个默认分区的表
hbase>create 't3','f1', { NUMREGIONS => 500, SPLITALGO => 'HexStringSplit' }

HexStringSplit、UniformSplit 说明

UniformSplit(占用空间小):将可能的键的空间平均分割的聚合体。当键是近似一致的随机字节时(例如散列),建议使用这个。行是范围为 00 => FF 的原始字节值,用0右填充以保持相同的 memcmp()顺序。对于byte[]环境来说,这是一种自然的算法,可以节省空间,但是对于可读性来说,它并不一定是最简单的。
HexStringSplit(占用空间大):HexStringSplit 是一个典型的 RegionSplitter.SplitAlgorithm来选择 region 边界。HexStringSplit region 边界的格式是MD5校验和或任何其他均匀分布的十六进制值的ASCII表示形式。Row是十六进制编码的长值,其范围为“00000000”=>“FFFFFFFF”,并左填充0,以使其在字典上保持与二进制相同的顺序。由于这种分割算法使用十六进制字符串作为键,所以在 shell 中方便读写,但是占用更多的空间,而且可能不够直观。

但是这两种可能都不适合你们的业务场景
我一开始使用的是HexStringSplit算法,贴上截图在这里插入图片描述
这种算法的确做到了把字符尽可能的分散,但是对分布不均匀的业务数据来说,还是不够随机,以上就是,当字符排序大于最后一个分区的startkey时,数据就会都集中在最后一个分区,还是没有解决数据均匀存储的目的

一个通用的解决办法:(全文重点!!!!!!)

自定义分区+rowkey随机前缀
1、我们先自定义500个分区,切分点是100~600的字符(自定义分区在上边)
在这里插入图片描述
2、rowkey随机前缀:
因为rowkey是根据字典顺序寻址,👎我们预分区的startkey、endkey是100~600的字符
第一个region:··· ~100
第二个region:100 ~101
第三个region:101 ~102
第n个region:···~···
第601个region:599 ~600
最后一个region:600~···

所以我们只要保证,rowkey的前三个字符是在100~600之间的随机值,那么数据就可以随机且均匀的落在500个分区上了。。嘿嘿(我好了)
在这里插入图片描述
然后,数据就会像这个样子,特别均匀的落在500个region上(大家都找到了属于自己的region,而你还是个没有region的野孩子)
在这里插入图片描述
谢谢,,,
都看到这了,给个赞,点个关注再走吧

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值