![[HBase] - 理解 HBase Rowkey 字典排序,第1张 [HBase] - 理解 HBase Rowkey 字典排序,第1张](/aiimages/%5BHBase%5D+-+%E7%90%86%E8%A7%A3+HBase+Rowkey+%E5%AD%97%E5%85%B8%E6%8E%92%E5%BA%8F.png)
我们都知道 HBase 的数据根据 rowkey 字典序排序的,理解这个概念很重要。
根据 wiki 解释:
通俗的理解,字典序是把字符左对齐,从 左到右比 大小的排序,一旦比出大小就停止比较后续的字符。
那么排序规则是什么?如果只考虑字母的话,就是: a < b < c < < z,当然这个规则也是 人为约定 的,举例以下就是字典序:
有了之前的概念,理解 HBase Rowkey 字典序会容易一点:
下面这里从实现层面举个例子:假如 HBase 要比较 rowkey "19" 和 "2" ,怎么做?
这里值得一提的是:ASCII 可见字符都是单字节表示,所以字符与 ASCII 字节数组是一一对应的。
但如果你用非 ASCII 可见字符做 rowkey ,例如汉字,就会出现不可控因素,因为一个汉字对应的是多个字节。所以建议 rowkey 就采用常见的可见字符,避免用特殊字符。
ASCII 码可显示字符有 95 个,大致顺序如下
第一个是:「空格」,最后一个是:「~」
下一篇将讲讲 rowkey 的设计与优化,包括预分区的选择策略。
wiki >
文中可能涉及到的API:
Hadoop/HDFS:>
[TOC]
HBase 中,表会被划分为1n 个 Region,被托管在 RegionServer 中。
Region 二个重要的属性:StartKey 与 EndKey 表示这个 Region 维护的 RowKey 范围,当读/写数据时,如果 RowKey 落在某个 start-end key 范围内,那么就会定位到目标region并且读/写到相关的数据。
默认,HBase 在创建表的时候,会自动为表分配一个 Region,正处于混沌时期,start-end key 无边界,所有 RowKey 都往这个 Region里分配。
当数据越来越多,Region 的 size 越来越大时,达到默认的阈值时(根据不同的拆分策略有不同的阈值),HBase 中该 Region 将会进行 split,会找到一个 MidKey 将 Region 一分为二,成为 2 个 Region。而 MidKey 则为这二个 Region 的临界,左为 N 无下界,右为 M 无上界。< MidKey 被分配到 N 区,> MidKey 则会被分配到 M 区。
随着数据量进一步扩大,分裂的两个 Region 达到临界后将重复前面的过程,分裂出更多的 Region。
Region 的分割 *** 作是不可见的,Master 不会参与其中。RegionServer 拆分 Region的步骤是:先将该 Region 下线,然后拆分,将其子 Region 加入到 META 元信息中,再将他们加入到原本的 RegionServer 中,最后汇报 Master。
执行 split 的线程是 CompactSplitThread。
在 205 版本中,HBase 提供了 7 种自动拆分策略:
他们之间的继承关系如下:
有三种配置方法:
0940 之前的默认拆分策略,这种策略非常简单,只要 Region 中的任何一个 StoreFile 的大小达到了 hbasehregionmaxfilesize 所定义的大小 ,就进行拆分。
1)相关参数:
hbasehregionmaxfilesize
2)部分源码 :
拆分的阈值大小可在创建表的时候设置,如果没有设置,就取 hbasehregionmaxfilesize 这个配置定义的值,如果这个配置也没有定义,取默认值 10G。
3)拆分效果:
经过这种策略的拆分后,Region 的大小是均匀的,例如一个 10G 的Region,拆分为两个 Region 后,这两个新的 Region 的大小是相差不大的,理想状态是每个都是5G。
ConstantSizeRegionSplitPolicy 切分策略对于大表和小表没有明显的区分,阈值(hbasehregionmaxfilesize):
4)创建表时配置:
该策略继承自 ConstantSizeRegionSplitPolicy,是 0940 到 200 版本的默认策略,其 优化了原来 ConstantSizeRegionSplitPolicy 只是单一按照 Region 文件大小的拆分策略,增加了对当前表的分片数作为判断因子 。当Region中某个 Store Size 达到 sizeToCheck 阀值时进行拆分,sizeToCheck 计算如下:
如果表的分片数为 0 或者大于 100,则切分大小还是以设置的单一 Region 文件大小为标准。如果分片数在 1~99 之间,则由 min(单一 Region 大小, Region 增加策略的初始化大小 当前 Table Region 数的3次方) 决定 。
Region 增加策略的初始化大小计算如下:
1)相关参数:
hbasehregionmaxfilesize
hbaseincreasingpolicyinitialsize
hbasehregionmemstoreflushsize
2)部分源码:
在默认情况,使用IncreasingToUpperBoundRegionSplitPolicy 策略拆分 Region 的过程是:
3)拆分效果:
和 ConstantSizeRegionSplitPolicy 一样,也是均匀拆分。
不同的是, IncreasingToUpperBoundRegionSplitPolicy 切分策略弥补了ConstantSizeRegionSplitPolicy 的短板,能够自适应大表和小表,并且在大集群条件下对于很多大表来说表现很优秀。
但并不完美,这种策略下很多小表会在大集群中产生大量小 Region,分散在整个集群中。而且在发生 Region 迁移时也可能会触发 Region 分裂。
4)创建表时配置:
20 版本默认切分策略。SteppingSplitPolicy 是IncreasingToUpperBoundRegionSplitPolicy 的子类,其对 Region 拆分文件大小做了优化,如果只有1个 Region 的情况下,那第1次的拆分就是 256M,后续则按配置的拆分文件大小(10G)做为拆分标准。
1)相关参数:
同 IncreasingToUpperBoundRegionSplitPolicy 。
2)全部源码:
它的源码只有一个方法,优化了 getSizeToCheck 方法,其他都是继承 自IncreasingToUpperBoundRegionSplitPolicy 类。
3)拆分效果:
在 IncreasingToUpperBoundRegionSplitPolicy 策略中,针对大表的拆分表现很不错,但是针对小表会产生过多的 Region,SteppingSplitPolicy 则将小表的 Region 控制在一个合理的范围,对大表的拆分也不影响。
4)创建表时配置:
KeyPrefixRegionSplitPolicy 是 IncreasingToUpperBoundRegionSplitPolicy 的子类,该策略除了具备其父类自动调整 Region 拆分阈值大小、适应大小表的特点外,增加了对拆分点(splitPoint,拆分点就是 Region 被拆分处的 RowKey)的定义,可以保证有相同前缀的 RowKey不会被拆分到两个不同的 Region 里面。
1)相关参数:
在 IncreasingToUpperBoundRegionSplitPolicy 的配置之上增加了一个参数。
KeyPrefixRegionSplitPolicyprefix_length
2)部分源码:
先从父类获取拆分点,如果设置了 prefixLength > 0,就从父类拆分点中截取需要的前缀作为新的拆分点返回。
3)拆分效果:
KeyPrefixRegionSplitPolicy (SteppingSplitPolicy、DelimitedKeyPrefixRegionSplitPolicy、BusyRegionSplitPolicy (HBase-2x Only))按照 RowKey 的前缀去拆分 Region,但是什么时候拆分,原 Region 容量的最大值是多少还是需要使用 IncreasingToUpperBoundRegionSplitPolicy 的方法去计算 。
如果所有数据都只有一两个前缀,那么采用默认的策略较好。 如果前缀划分的比较细,查询就比较容易发生跨 Region 查询的情况,此时采用KeyPrefixRegionSplitPolicy 较好。
所以这个策略适用的场景是:
4)创建表时配置:
继承自 IncreasingToUpperBoundRegionSplitPolicy,也是根据 RowKey 前缀来进行拆分的。不同就是:KeyPrefixRegionSplitPolicy 是根据 RowKey 的固定前几位字符来进行判断,而 DelimitedKeyPrefixRegionSplitPolicy 是根据分隔符来判断的。
1)相关参数:
在 IncreasingToUpperBoundRegionSplitPolicy 的配置之上增加了一个参数。
DelimitedKeyPrefixRegionSplitPolicydelimiter
2)部分源码:
先找到分隔符下标位置,然后从父类的拆分点截取出来。
3)拆分效果:
DelimitedKeyPrefixRegionSplitPolicy 根据 RowKey 中指定分隔字符做为拆分,显得更加灵活,如 RowKey 的值为“userid_eventtype_eventid”,userId 不是定长的,则 DelimitedKeyPrefixRegionSplitPolicy 可以取 RowKey 值中从左往右且第一个分隔字符串之前的字符做为拆分串,在该示例中就是“userid”。
4)创建表时配置:
之前的策略都未考虑 Region 热点问题,考虑某些 Region 可能被频繁访问,负荷很大,BusyRegionSplitPolicy 策略同样继承自 IncreasingToUpperBoundRegionSplitPolicy,但主要针对 Region 问题,是在 2x 中新增加的拆分策略。
1)相关参数:
在 IncreasingToUpperBoundRegionSplitPolicy 的配置之上增加了如下参数:
hbasebusypolicyblockedRequests
hbasebusypolicyminAge
hbasebusypolicyaggWindow
2)部分源码:
在判断是否需要进行拆分的时候,先调用父类的 shouldSplit 方法检验,如果需要则直接返回 true,否则需要判断当前时间是否比开始时间大于 minAge 值,如果是的,则计算请求阻塞率 blockedReqRate,如果阻塞率大于设定的阈值,则进行拆分。
阻塞率的计算如下:
主要的计算逻辑是:请求的被阻塞率(aggBlockedRate) = curTime - prevTime 时间内新增的阻塞请求 / 这段时间的总请求。
3)拆分效果:
如果系统常常会出现热点 Region,又对性能有很高的追求,那么这种策略可能会比较适合。
它会通过拆分热点 Region 来缓解热点 Region 的压力,但是根据热点来拆分Region 也会带来很多不确定性因素,因为不能确定下一个被拆分的 Region 是哪个。
4)创建表时配置:
DisabledRegionSplitPolicy 就是不使用 Region 拆分策略,将所有的数据都写到同一个 Region 中。
1)全部源码:
源码很简单,就是直接返回 false。
2)拆分效果:
这个策略极少使用。
即使在建表的时候合理的进行了预拆分,还没有写入的数据的时候就已经手动分好了 Region,但是随着数据的持续写入,我预先分好的 Region 的大小也会达到阈值,那时候还是要依靠 HBase 的自动拆分策略去拆分 Region。
但这种策略也有它的用途:
假如有一批静态数据,一次存入以后不会再加入新数据,且这批数据主要是用于查询,为了性能好一些,可以先进行预分区后,各个 Region 数据量相差不多,然后设置拆分策略为禁止拆分,最后导入数据即可。
3)创建表时配置:
已经有自动分区了,为什么还需要预分区?
HBase 在创建表的时候,会自动为表分配一个Region,当一个 Region 达到拆分条件时(shouldSplit 为 true),HBase 中该 Region 将会进行 split,分裂为2个 Region,以此类推。表在进行 split 的时候,会耗费很多的资源,有大量的 io *** 作,频繁的分区对 HBase 的性能有很大的影响。
所以,HBase 提供了预分区功能,让用户可以在创建表的时候对表按照一定的规则分区。
假设初始 10 个 Region,那么导入大量数据的时候,就会均衡到 10 个 Region 里面,显然比初始 1 个 Region 要好很多, 合理的预分区可以减少 Region 热点问题,提升写数据的性能和速度,而且也能减少后续的 split *** 作 。
首先要明白数据的 RowKey 是如何分布的,然后根据 RowKey 的特点规划要分成多少 Region,每个 Region 的 startKey 和 endKey 是多少,接着就可以预分区了。
比如,RowKey 的前几位字符串都是从 0001~0010 的数字,这样可以分成10个Region:
第一行为第一个 Region 的 stopKey。为什么后面会跟着一个"|",是因为在ASCII码中,"|"的值是124,大于所有的数字和字母等符号。
shell中建分区表
也可以通过指定 SPLITS_FILE 的值指定分区文件,从文件中读取分区值,文件格式如上述例子所示:
预分区后,可以从 HBase ui 页面观察到:
HBase API 建预分区表
为防止热点问题,同时避免 Region Split 后,部分 Region 不再写数据或者很少写数据。也为了得到更好的并行性,希望有好的 load blance,让每个节点提供的请求处理都是均等的,并且 Region 不要经常 split,因为 split 会使 server 有一段时间的停顿,随机散列加上预分区是比较好的解决方式。
预分区一开始就预建好了一部分 Region,这些 Region 都维护着自已的 start-end keys,再配合上随机散列,写数据能均等地命中这些预建的 Region,就能通过良好的负载,提升并行,大大地提高了性能。
hash + 预分区
在 RowKey 的前面拼接通过 hash 生成的随机字符串,可以生成范围比较随机的 RowKey,可以比较均衡分散到不同的 Region 中,那么就可以解决写热点问题。
假设 RowKey 原本是自增长的 long 型,可以将 RowKey 先进行 hash,加上本身 id ,组成rowkey,这样就生成比较随机的 RowKey 。
那么对于这种方式的 RowKey 设计,如何去进行预分区?
partition + 预分区
partition 顾名思义就是分区式,这种分区有点类似于 mapreduce 中的 partitioner,将区域用长整数作为分区号,每个 Region 管理着相应的区域数据,在 RowKey 生成时,将 id 取模后,然后拼上 id 整体作为 RowKey 。
1 HBase Region 自动拆分策略
2 hbase预分区
RowKey
与nosql数据库们一样,RowKey是用来检索记录的主键。访问HBASE table中的行,只有三种方式:
通过单个RowKey访问(get)
通过RowKey的range(正则)(like)
全表扫描(scan)
RowKey行键 (RowKey)可以是任意字符串(最大长度是64KB,实际应用中长度一般为 10-100bytes),在HBASE内部,RowKey保存为字节数组。存储时,数据按照RowKey的字典序(byte order)排序存储。设计RowKey时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)
Column Family
列族:HBASE表中的每个列,都归属于某个列族。列族是表的schema的一部 分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如 courses:history,courses:math都属于courses 这个列族。
Cell
由{rowkey, column Family:columu, version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。
关键字:无类型、字节码
Time Stamp
HBASE 中通过rowkey和columns确定的为一个存贮单元称为cell。每个 cell都保存 着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由HBASE(在数据写入时自动 )赋值,此时时间戳是精确到毫秒 的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版 本冲突,就必须自己生成具有唯一性的时间戳。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,HBASE提供 了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段 时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。
命名空间
命名空间的结构:
Table:表,所有的表都是命名空间的成员,即表必属于某个命名空间,如果没有指定,则在default默认的命名空间中。
RegionServer group:一个命名空间包含了默认的RegionServer Group。
Permission:权限,命名空间能够让我们来定义访问控制列表ACL(Access Control List)。例如,创建表,读取表,删除,更新等等 *** 作。
Quota:限额,可以强制一个命名空间可包含的region的数量。
举一个场景,安全领域的溯源分析,查询维度包括ip,时间戳,端口,协议,可能根据前两的维度的一个或者几个进行原始日志查询,我们可以把原始日志存储到hbase中,而前面提到的几个维度可以分别作为key的一部分。
首先我们应该考虑的是rowkey的设置,第一:散列或者反转,保证数据会随机分布到不同的region当中。第二:预分区,先对数据做一个基本的统计,比如我们预分十个区,我们可以统计一下每个区的startrow和endrow,这样保证每个区的数据相当,另外这样的好处是当我们根据rowkey查询的时候,可以保证直接定位到某个分区。我们线上的数据就是采用的第二种方式。
然后我们应该考虑rowkey的组成。分两种情况,第一种情况:维度不是特别多,我们完全可以把各个维度分别作为rowkey的一部分,比如上文提到的需求,就是采用的这种方式,因为一共四个维度,相对来说比较少。第二种情况:维度过多,如果都作为rowkey的一部分的话长度太大,此时建议考虑二级索引,举个例子:比如对于上面提到的四个维度,如果现在进行扩展,ip,端口,协议需要定位到源和目的,这样的话,整个维度提升到了七个,此时就建议采用二级索引。
目前我们已经确定了hbase存储,并且采用预分区的方式并且采用rowkey进行过滤查询,那么现在考虑rowkey的设计。从技术角度考虑,预分区的方式时间戳不能作为第一部分,这样一定会出现数据倾斜的现象;从业务角度考虑,我们定位日志的时候,首先需要定位ip,然后是端口,最后才是协议,也就是说我们的用户去定位日志的时候,如果定位到端口,那必须先定位ip,如果定位协议的话,必须先定位ip和端口。
综上所述,我们的rowkey设计为ip+timestamp+port+prot
设计搞定之后,我们再考虑查询的问题。我们知道对于hbase的查询,最快的方式就是get,这样的话,可以迅速定位到一条数据。而get查询其实就是scan的特殊情况,只是startRow和endRow一样。所以此时我们可以采用scan+startRow+endRow的方式进行 *** 作。
eg
这样的话就可以吧该范围的数据查出来,当然我们可以再在内存中进行过滤
当着startRow和endRow需要注意一些情况。
请参考:>
一.工具开发背景:
业务上目前主要计算逻辑的数据源是hbase,但是我们没有工具对hbase的数据进行单条更改造数据来验证逻辑,之前的做法是把hbase的数据都导出来,改完再重新载入回去,或使用hbase shell接口进行更改(有一个限制就是hbase shell get 出来的数据汉字是看不出来的),效率低,也不便于自动化的回归。测试非常的被动。
于是在师姐的建议下期望有 *** 作hbase数据的工具来提高我们的效率,及大数据的验证。
二.工具简介:
工具使用java编写的jar包,在ihbasesh进行简单数据处理对jar包进行调用。主要功能为数据的增删改查,支持gbk,utf8编码。通过配置一个xml格式的配置文件 (也可以不配置)。
三.使用方法:
1propertiessh:在里面配置hbase,hadoop等环境变量,里面目前默认是我们测试集群的配置作为参考。注意一些基础的jar包一定要有。
2config:xml格式的配置hbase导出数据的信息。在海量导出数据或根据rowkey到处数据的时候使用。
3ihbasesh工具的使用接口。
四.简要使用介绍:
*** 作均在bin目录下。
一.查询数据功能
1 /ihbase –t table_name -rowkey rowkey -enc encoding -s
-enc encoding这个的目的是指定以什么编码读出hbase数据,目前支持utf8,gbk。如果不加该参数则默认以utf读出。
查看表名为table_name,rowkey为rowkey的数据。
2 /ihbase –t 表名 –k CF:COLUMN=value –k CF:COLUMN=value –w column -s
/ihbasesh –t "test" –k "a:name=jkfs" –k "a:id=111" -w "nid" -s
查询满足a:name=jkfs且a:id=111的记录,若加上-w参数,则只显示 -w 传进去的列。若不加-w 显示所有列。
(这个命令一般很少用,因为使用这个=的filer需要扫hbase全表,因为这种方式很少使用,所以暂时没考虑如何优化)
二.删除数据功能
1 /ihbase –t table_name –rowkey rowkey –delete
根据rowkey进行删除。
2 /ihbase –t table_name –k CF:COLUMN=value –k CF:COLUMN=value –delete
/ihbase –t test –k "a:id=jaks" -k "name=a:jkasj" -delete
删除满足a:id=jaks且a:name=jkasf的数据(目前支持and进行删除,不支持or,原理同该方式的查询)
三.添加数据功能
/ihbase –t table_name –rowkey rowkey –v CF:COLUMN=value –v CF:COLUMN=value -a
/ihbasesh –t "a:test" -rowkey "111" –v "a:name=jkfs" –v "id=111" -a
添加rowkey为111的数据,后面key,value可以任意指定。如果rowkey已经存在,则报错不进行添加。
添加数据要求必须指定rowkey
四.修改数据功能
1 /ihbase –t table_name –rowkey rowkey –v key=value -v key=value -u
/ihbasesh -t "test" -rowkey "1111" –v "name=jkasjd" -u
根据rowkey进行修改一条记录的列数据
2 /ihbase –t table_name –k key=value –k CF:COLUMN=value -v CF:COLUMN=value -u
/ihbasesh –t test –k "a:name=jksdj" –k "mge=kjdk" –v "a:name=huanyu" -u
根据非rowkey进行修改,-k 提供修改的条件,-u 提供要修改的列的数数据。(原理同查询,scan全表)
五.导出hbase指定列的数据(所有数据)
/ihbase -f config 此处有一个限制:就是导出表的配置文件必须放在bin的目录下。如果不喜欢这样也可以修改ihbase脚本进行调整。
config为配置导出表信息的xml配置
<xml version="10">
<configuration>
<table>
<in_enc>gbk</in_enc>
<out_enc>utf8</out_enc>
<tablename>test</tablename>
<field_separator>\001</field_separator>
<record_separator>\002</record_separator>
<column>
bmw_shops:title
</column>
<outpath>/test/huanyu/hbase</outpath>
</table>
</configuration>
in_enc:hbase中的编码,以解析hbase中数据使用的编码。
out_enc:输出到hdfs路径上的编码。
tablename: *** 作的表名。
field_separator:如果导出多个字段则作为多个字段间的分隔符。
record_separator:导出数据的行分隔符。(除了\n的字符,因为默认会分行)。
column:导出的字段。如果不存在该字段则导出''。
outpath:导出数据的路径。(其实应在代码中把这个路径先删除的,但是怕用户忘记修改路径误删除,所以没有这么做)
有多少个region 启动多少个map。
六.导出hbase指定行的指定列的数据
/ihbase -f config -rf rfile
config里面配置导出的列,字符编码表名等信息。
rfile 配置导出哪些rowkey的数据。(一个rowkey一行)
类似上面。
七.帮助信息
/ihbase –h
显示帮助信息
以上就是关于[HBase] - 理解 HBase Rowkey 字典排序全部的内容,包括:[HBase] - 理解 HBase Rowkey 字典排序、HBASE 1.0、hbase value 支持多大字符串等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)