简介:

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寻址和数据存放是根据字典排序

hbase region多大会分区 hbase中的分区是如何定位的_hbase


现在假设我们要从用户表

里面查询一条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上,会很容易造成热点问题

hbase region多大会分区 hbase中的分区是如何定位的_云数据存储_02


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)

hbase region多大会分区 hbase中的分区是如何定位的_hbase_03


四个分区,rowkey如果是‘0232534’,就落在第一个分区,‘130034

24’落在第二个分区(字典排序!!!!)

自定义分区:

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

可以使用 SPLITS_FILE 来指定一个文本文件,文件内写入拆分点(即startkey,endkey),通过这种方式来自定义拆分点

splits.txt文件截图:

hbase region多大会分区 hbase中的分区是如何定位的_云数据存储_04


预分区截图:

hbase region多大会分区 hbase中的分区是如何定位的_云数据存储_05


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算法,贴上截图

hbase region多大会分区 hbase中的分区是如何定位的_云数据存储_06


这种算法的确做到了把字符尽可能的分散,但是对分布不均匀的业务数据来说,还是不够随机,以上就是,当字符排序大于最后一个分区的startkey时,数据就会都集中在最后一个分区,还是没有解决数据均匀存储的目的

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

自定义分区+rowkey随机前缀

1、我们先自定义500个分区,切分点是100~600的字符(自定义分区在上边)

hbase region多大会分区 hbase中的分区是如何定位的_大数据_07


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个分区上了。。嘿嘿(我好了)

hbase region多大会分区 hbase中的分区是如何定位的_hbase_08


然后,数据就会像这个样子,特别均匀的落在500个region上(大家都找到了属于自己的region,而你还是个没有region的野孩子)

hbase region多大会分区 hbase中的分区是如何定位的_数据_09