HBase RowKey 设计与查询实践

2022-06-10 17:53:14 浏览数 (1)

RowKey 设计

HBase 作为一款分布式的NoSQL数据库,数据的分布根据rowKey range方式来划分,每个Region 存储了一定范围rowKey 的数据, 数据的读写通常情况下需要指定rowKey 来定位到具体的Region 与 RegionServer, 如果大量的请求根据rowKey都打到同一个Region或者很少的Region上,那么这些Region就会形成热点, 无法使用集群特性有效负载均衡。因此,RowKey 的设计在实践中至关重要。

一些原则


长度原则

HBase 实际按照KV的方式进行存储,K 是rowKey、Column Family、Column Qualifier、Timestamp 的组合, 如果Qualifier 比较多, 那么对应rowKey也会被多次存储,既占用内存也占用磁盘存储,因此RowKey 长度尽可能精简。

唯一原则

RowKey 本身具有唯一性, 写入相同RowKey的数据下相同的列会被覆盖。

排序原则

数据的存储是按照RowKey进行字典方式升序存储,主要是为了方便检索。

散列原则

设计的RowKey应均匀的分布在各个HBase节点上,避免产生热点。

散列方式


为了使整个集群负载均衡, 需要将rowKey 尽可能的打散分布到各个节点上去,通常使用的打散方式:

Reversing(反转)

如果经初步设计出的RowKey在数据分布上不均匀,但RowKey尾部的数据却呈现出了良好的随机性,此时,可以考虑将RowKey的信息翻转,或者直接将尾部的bytes提前到RowKey的开头。Reversing可以有效的使RowKey随机分布,但是牺牲了RowKey的有序性。

Salting(加盐)

Salting的原理是在原RowKey的前面添加固定长度的随机数,也就是给RowKey分配一个随机前缀使它和之间的RowKey的开头不同。随机数能保障数据在所有Regions间的负载均衡。

Hashing

基于 RowKey 的完整或部分数据进行 Hash,而后将Hashing后的值完整替换或部分替换原RowKey的前缀部分。这里说的 hash 包含 MD5、sha1、sha256 或 sha512 等算法。

预分区


HBase建表时默认只有一个region,刚开始数据都会写入到该region中,随着数据写入的不断增加,达到单个region数据量上限,会进行split,分成2个region。在此过程中,会产生两个问题:1.数据往一个region上写,会有写热点问题。2.region split会消耗宝贵的集群I/O资源。

因此,我们可以提前预估表的数据量进行预分区操作,也就是提前创建多个region,并确定每个region的起始和终止rowkey,一方面避免数据热点,另一方面降低了region split所带来的消耗。

HexStringSplit

HBase 自带的十六进制的字符串预分区算法,那么在rowKey 设计时通常使用hash后字符串作为前缀或者完整的RowKey。

UniformSplit

HBase 自带的二进制byte的预分区算法, 那么rowKey 需要设计为字节数据模式。

指定位点方式

代码语言:javascript复制
create 'staff','info','partition1',SPLITS => ['1','2','3','4']

那么在设计rowKey时可通过加盐方式,对rowKey 进行hash然后根据region的个数取余拼接在rowKey 的前面。

使用实践

实时维表


维度字段补充在实时处理链路里面是比较常见的一种操作,例如根据商品ID补齐商品名称、描述等信息,可将商品信息表存储在HBase 中, 查询方式根据商品ID 做Get操作, 商品ID 通常是一串数字,但是无法保证这串数据本身分布比较均衡,因此可通过对商品ID进行MD5 操作取前5位然后与商品ID拼接作为rowKey , 即:substr(md5(itemId),0,5) itemId

在线指标查询


在线指标查询可分为两种使用场景:大屏类的点查场景、看板类的多维查询场景。

点查场景

点查场景比较简单,比喻广告实时总消耗、总点击量, 这类数据特点一个是存储数据量比较少,另外一个查询频次低,因为通常是面向内部。因此其rowKey 的设计可以比较随意。

多维查询场景

多维查询也就是多条件查询,需要任意维度的组合查询,但是HBase 并不擅长做数据分析,为了保证查询性能,因此通常会在离线侧或者实时侧将多维任意组合的数据指标提前加工好写入HBase 中(即cube), 也就是空间换时间方式。下面以实际广告报表数据为例子讲解。

  • 维度&指标:广告维度:商家ID(company_id)、广告投放计划ID(campaign_id)、投放时间(stat_date), 数据指标: 消耗(cost)。
  • 看数方式: 某个商家某一天的消耗、某个商家某一天下计划消耗明细

数据加工

company_id/campaign_id/stat_date 与company_id/stat_date, 生产这两种维度数据。

rowKey 设计

在两种查询场景下company_id 与 stat_date 为必要查询条件,campaign_id可有可无,那么rowKey 组合为:company_id、stat_date、campaign_id,为了使数据分布更加均衡需要加一个随机前缀,有以下几种不同方式:

  • 根据company_id、stat_date、campaign_id 进行MD5 取前5位作为前缀, campaign_id并不是一个必须的入参条件,那么在计划消耗明细场景下无法查询
  • 根据company_id 进行MD5 取前5位作为前缀,那么这个商家的所有stat_date数据很有可能会分布在同一个region中,可能会导致热点
  • 根据company_id 、stat_date 进行MD5 取前5位作为前缀,既保证了计划明细消耗查询,又保证了数据更加均匀分布。

因此最终rowKey 设计为:

代码语言:javascript复制
substr(md5(company_id  stat_date),0,5)   company_id stat_date campaign_id。

查询商家级别的消耗

只需要指定company_id、stat_date, 并且将rowKey 还原做Get查询即可。

查询商家的计划明细消耗

两种查询方式:

  • 以 substr(md5(company_id stat_date),0,5) company_id stat_date 作为前缀进行 scan 操作。
  • 查询前将所有的campaign_id 查询出来,还原所有的rowKey , 将scan查询转换为批量的Get查询。

比较有意思的分页问题


分页查询在页面上是比较常见的一个功能,通常有两种使用方式:上下翻页、可指定页码, 同时需要根据不同的字段排序。

不同字段排序存储

HBase 是按照rowKey 进行排序的,如果要按照不同字段排序就需要在rowKey中添加对应的字段,并且提前加工好其排序的方式,排序的字段越多冗余的数据就越多。

上下翻页

上下翻页表示的是页面上只能通过上一页、下一页方式查询,通常的实现方式就是scan方式 startRowKey , 每次查询都指定起始的游标startRowKey,这种方式能够保证翻页的正确性是因为HBase 本身存储是按照RowKey 字典顺序排序的, 在数据扫描时也是按照startRowKey作为起始值顺序查询出数据。

指定页码

指定页码即可以跳转到指定的分页数据,常见以下几种方式:

  • 内存分页:在MySQL 可通过 limit offset 方式使用,其可以直接跳过指定的行数进行查询,但是对HBASE 却不能这么使用,只能通过扫描全表数据进行然后进行内存分页, 因此这种方式只能数据量较少的情况下使用
  • 二级索引分页:使用es 做索引,通过es分页查询查询出rowKey, 然后查询HBase 中数据, 将scan操作转换为批量Get操作。这种方式也解决掉了按照不同字段排序问题,不需要存储多份数据。但是同时也引入查询的复杂性与数据一致性问题。

0 人点赞