HBase 行键设计

HBase 有两种基本的键结构:行键(row key)和列键(column key)。两者都可以存储有意义的信息,一种是键本身存储的内容,另一种是键的排列顺序。

概念

HBase 的表中数据分隔主要是使用列族(column family)而不是列,并非列式存储,如下图,表示了用户逻辑上把一个单元格的数据存到了一张表中,实际上底层存储是按列族线性地存储单元格,并包含了一些必要信息:

HBase

左上图是数据的逻辑视图,列键包含了列族和列(cf1:c1、cf2:c1...),每一行必须有一个行键(r1、r2...),可以通过行键获取到一行的所有列。

右上图表示逻辑视图转换为实际物理存储,每一行的每个单元格被有序存储,不同列族的数据存储在不同的文件(store file)中,同一个列族的数据存储在一个文件中(实际上会有多个 HFile,逻辑上属于同一个)。并且单元格的每个历史版本也会在存储文件中存在。

HBase 不存储 NULL 值,所以表可以很稀疏,没有值的单元格不会占用存储空间。每个单元格在存储文件中都存储了该记录在表中所处位置的相关信息。

如右下图所示,每一个单元格都包含:行键、列族、列键、写入时间戳和列值。并且按照时间戳降序排列。在读取 HFile 时最新的值会先被读到。单元格在 HBase 中被叫做 KeyValue,存储时先按行键排序,再按列键排序。因此用户可以按行键、或按行键的范围指定查询,同时指定列族,可以有效的减少检索的文件。

列和时间戳也可以用来筛选数据。查询数据时可以指定返回的列(可以配合过滤器 Filter 实现比较复杂的筛选),也可以指定时间范围进行过滤。

列值是最后一个筛选条件,与列的筛选类似,对每一个 KeyValue 都要经过过滤器检查,对性能的提升不明显,会减少返回给客户端的数据量。

下图展示了使用不同字段对筛选的性能影响大小。筛选的效率从左至右明显下降,所以在 KeyValue 设计时,可以考虑把更重要的筛选信息移动位置,如上图左下图展示的,在不改变数据量的情况下提高查询性能。

HBase

高表与宽表

HBase 的表可以设计为高表(tall-narrow table)和宽表(flat-wide table)两类。前者行多列少,后者行少列多。根据之前介绍的 KeyValue 的信息筛选效率,应该尽量的将需要查询的维度或信息存储在行键中,筛选的效率是最高的。

此外,HBase 按行进行数据分片,因此高表更有优势。设想将用户的所有电子邮件都存储在一行,以用户ID作为行键,当邮件数量大时,极限场景下一个用户的数据超过了 HFile 的最大限制,而此次 HFile 是无法根据行键拆分的。(下面表示的是一行数据在物理存储上的形式)

<userId> : <colfam> : <messageId> : <timestamp> : <email-message>
12345 : data : 5fc38314-e290-ae5da5fc375d : 1307097848 : "Hi Lars, ..." 
12345 : data : 725aae5f-d72e-f90f3f070419 : 1307099848 : "Welcome, and ..."
12345 : data : cc6775b3-f249-c6dd2b1a7467 : 1307101848 : "To Whom It ..."
12345 : data : dcbee495-6d5e-6ed48124632c : 1307103848 : "Hi, how are ..."

更好的方式转换为高表,每个邮件一行,是行键以用户ID(userId)和消息ID(messageId)组合。消息ID在 KeyValue 中向左移动,不仅解决了 HFile 拆分的问题,同时提高了消息ID的检索效率。

<userId>-<messageId> : <colfam> : <qualifier> : <timestamp> : <email-message>
12345-5fc38314-e290-ae5da5fc375d : data : : 1307097848 : "Hi Lars, ..."
12345-725aae5f-d72e-f90f3f070419 : data : : 1307099848 : "Welcome, and ..."
12345-cc6775b3-f249-c6dd2b1a7467 : data : : 1307101848 : "To Whom It ..."
12345-dcbee495-6d5e-6ed48124632c : data : : 1307103848 : "Hi, how are ..."

部分键扫描

HBase 的扫描功能和基于 HTable 的API更适合在高表上筛选数据,可以通过只包含部分键的扫描检索数据,同时不丢失查询的粒度。

在上面的例子中,在宽表的场景中,可以使用用户ID取到用户行,并通过列名(消息ID)筛选数据。在高表的场景中,需要有用户ID和消息ID同时确定一行数据,否则不能找到一个特定的邮件。可以使用包含部分键的扫描解决这个问题:扫描操作设置一个开始和结束的用户ID(默认结果是不包含终止键的,所以结束键一般需要设为 userId + 1)。

HBase 内部会按字典序找到第一个行键的位置(要么是起始键,要么是起始键的下一个键):<userId>-<lowest-messageId>,开始扫描这个用户的所有邮件,紧接着下一个用户的所有邮件,直至到扫描到结束键。

我们可以使用包含部分键的扫描机制设计出比较有效的左对齐索引,当一个字段被加到行键中,就多了一个可以检索的维度(注意检索必须包含从左到右的某几个字段,如果只检索中间的某个字段,效率还是不高):<userId>-<date>-<messageId>-<attachmentId>

需要保证行键中的每个字段都设置一个固定的长度,并且每个字段的值都补齐到这个长度,这样才能保证字典序按预期排列。如果用户ID:2和10,期望得到的行键是:"02XXXX"、"10XXXX",与预期一致。如果不补齐长度,得到的行键为:"10XXXX"、"2XXXX",与预期不同。

上面设计的行键可以实现一下索引:

<userId>                                        扫描一个特定用户下的所有消息
<userId>-<date>                                 扫描一个特定用户下特定日期的所有消息
<userId>-<date>-<messageId>                     扫描一个特定用户特定日期下的指定消息的全部内容
<userId>-<date>-<messageId>-<attachmentId>      扫描一个特定用户特定日期下的指定消息的一个附件

类似的,可以控制每个字段的内容以达到控制排序的目的。例如可以把日期反转,得到按日期降序排列的效果:Long.MAX_VALUE - <date-as-long>

行键的设计并非有固定模式,需要视业务需要而定,例如上面的行键设计看似不错,但是如果业务上需要统一修改一个用户所有邮件或邮件所有附件的属性值,是无法保证原子性的操作的(HBase 只支持行数据原子操作),宽表可能更加适合。

分页

使用扫描可以很方便的遍历查询数据子集的行。可以设定起始键和终止键限制扫描的范围,同时在 Client 端添加 offsetlimit 参数筛选数据。

具体过程如下:

  1. 在起始键位置打开一个扫描器
  2. 跳过 offset 数目的行
  3. 读取 limit 数码的行,并返回
  4. 关闭扫描器

通过不通的行键设计,可以实现多种方式的子集扫描和分也。例如按时间逆序排列,总是会先读到最新到达的数据。

时间序列

当处理流式数据时,最常见的数据就是按时间序列组织的数据。数据可能来自传感器,监控系统,实时交易系统等。这些数据的特点是行键都代表了事件发生的时间。由于 HBase 的数据组织方式,这样的数据在存储时会出现一个问题:数据热点问题,由于业务的特性在某些特定范围内数据量会非常大,会造成 Region 的数据量分布不均匀,同时数据写入会集中到特定的 Region 上,导致性能下降。

为了解决这类问题,需要将数据能够更均匀的分散到各个 Region 上,在此介绍几种方法:

salting 方式

可以使用 salting 前缀来保证数据分散到所有 Region 上:

byte prefix = (byte) (Long.hashCode(timestamp) % <number of region servers>);
byte[] rowkey = Bytes.add(Bytes.toBytes(prefix), Bytes.toBytes(timestamp);

上面前缀的计算方法,利用时间戳的离散哈希值,并假设 RegionServer 数量固定的情况,能够使数据均匀的分布在各个 Region 上。

缺点是,单次对原始的行键进行范围扫描(原始连续的行键添加了随机前缀后,离散到各个 Region 中)效率比较低,但是可以通过多线程并行读取的方式提高效率。


字段交换/提升权重

如果行键设计包含了多个字段,可以将时间戳字段(如果是一个字段,或其他不离散的第一个字段)在不影响业务的前提下,调整位置。如果行键只包含时间戳,可以将其他字段提取出来放到行键的第一位。

参考时序数据库 OpenTSDB,数据存储在 HBase 中,行键类似以下结构:

<metric-id>-<base-timestamp>

通过指标ID将数据离散,得到与 salting 前缀相同的效果。


随机化

另一种完全不同的方式是将行键随机化(哈希或MD5算法):

byte[] rowkey = MD5(timestamp)

能够将行键分散到所有 Region 上,缺点就是不能在按照顺序进行扫描,不适用与时序的数据。该方式比较适合随机读写的场景。

HBase

使用时需要在行键设计和读写性能上找到平衡点,如上图与读写方式的关系。


References:
《HBase 权威指南》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 简介 HBase是高可靠性,高性能,面向列,可伸缩的分布式存储系统,利用HBase技术可在廉价PC Serve...
    九世的猫阅读 2,179评论 1 6
  • 转 # https://www.cnblogs.com/easypass/archive/2010/12/ 08/...
    吕品㗊阅读 9,709评论 0 44
  • 本文首先简单介绍了HBase,然后重点讲述了HBase的高并发和实时处理数据 、HBase数据模型、HBase物理...
    达微阅读 2,730评论 1 13
  • Zookeeper用于集群主备切换。 YARN让集群具备更好的扩展性。 Spark没有存储能力。 Spark的Ma...
    Yobhel阅读 7,258评论 0 34
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,256评论 0 9