Hbase 二级索引

Hbase 使用二级索引原因:

目前 HBase 主要应用在结构化和半结构化的大数据存储上,其在插入和读取上都具有极高的性能表现,这与它的数据组织方式有着密切的关系,在逻辑上,HBase   的表数据按RowKey  进行字典排序,  RowKey  实际上是数据表的一级索引(Primary  Index,由于HBase  本身没有二级索引(Secondary  Index)机制,基于索引检索数据只能单纯地依靠RowKey,为了能支持多条件查询,开发者需要将所有可能作为查询条件的字段一一拼接到RowKey ,这是 HBase 开发中极为常见的做法,但是无论怎样设计,单一 RowKey 固有的局限性决定了它不可能有效地支持多条件查询。

通常来说,RowKey 只能针对条件中含有其首字段的查询给予令人满意的性能支持,在查询其他字段时,表现就差强人意了,在极端情况下某些字段的查询性能可能会退化为全表扫描的水平,这是因为字段在 RowKey 中的地位是不等价的,它们在 RowKey 中的排位决定了它们被检索时的性能表现,排序越靠前的字段在查询中越具有优势,特别是首位字段具有特别的先发优势,如果查询中包含首位字段,检索时就可以通过首位字段的值确定RowKey   的前缀部分,从而大幅度地收窄检索区间,如果不包含则只能在全体数据的RowKey 上逐一查找,由此可以想见两者在性能上的差距

受限于单一 RowKey 在复杂查询上的局限性,基于二级索引(Secondary Index)的解决方案成为最受关注的研究方向,并且开源社区已经在这方面已经取得了一定的成果,像ITHBase、IHBase 以及华为的 hindex 项目,这些产品和框架都按照自己的方式实现了二级索引,各自具有不同的优势,同时也都有一定局限性。

关系型数据库中索引数据与原始数据之间的数据一致性是通过关系型数据库中的组件负责实现的,对于数据库数据的插入和删除都会在索引中展现出来,对于原始数据和索引的操作会是一个原子/事务操作,但是在 HBase 中没有框架自身提供的机制,只能靠开发人员自己去实现。

由于在 HBase 中的二级索引是通过建表的方式实现的,当需要更新时,就是两个表的数据原子更新,也就是跨表的事务功能,而 Hbase 只提供行级事务,没有跨表和跨行的事务功能,这就需要开发者自己去实现,如果对数据一致性要求较高,那么就可能需要自己去实现一套分布式的事务机制,之所以是分布式的事务机制,是因为原始数据可能由一些

HRegionserver 维护,而索引表由另外一些 HRegionserver 维护,这个事务机制就涉及到了多个 HRegionserver,也就是分布式的事务机制。因此,二级索引是 HBase 自身存在的一个短板。

一、 二级索引设计

二级索引的本质就是建立各列值与行键之间的映射关系,以列的值为键,以记录的

RowKey 为值

 

如图所示,当要对 F:C1 这列建立索引时,只需要建立 F:C1 各列值到其对应行键的映射关系,如 C11->RK1 等,这样就完成了对 F:C1 列值的二级索引的构建,当要查询符合 F:C1=C11 对应的 F:C2 的列值时(即根据 C1=C11 来查询 C2 的值,图 2-23 青色部分)。

其查询步骤如下:

  1. 根据 C1=C11 到索引数据中查找其对应的 RK,查询得到其对应的 RK=RK1;
  2. 得到 RK1 后就自然能根据 RK1 来查询 C2 的值了 这是构建二级索引大概思路,其他组合查询的联合索引的建立也类似。

二、二级索引设计剖析

“二级多列索引”是针对目标记录的某个或某些列建立的“键-值”数据,以列的值为键,以记录的 RowKey 为值,当以这些列为条件进行查询时,引擎可以通过检索相应的“键-值”数据快速找到目标记录。由于 HBase 本身并没有索引机制,为了确保非侵入性,引擎将索引视为普通数据存放在数据表中,所以,如何解决索引与主数据的划分存储是引擎第一个需要处理的问题

为了能获得最佳的性能表现,我们并没有将主数据和索引分表储存,而是将它们存放在了同一张表里,通过给索引和主数据的 RowKey 添加特别设计的 Hash 前缀,实现了在Region 切分时,索引能够跟随其主数据划归到同一 Region 上,即任意 Region 上的主数据其索引也必定驻留在同一 Region 上,这样我们就能把从索引抓取目标主数据的性能损失降低到最小

与此同时,特别设计的 Hash 前缀还在逻辑上把索引与主数据进行了自动的分离,当全体数据按 RowKey 排序时,排在前面的都是索引,我们称之为索引区,排在后面的均为主数据,我们称之为主数据区。最后,通过给索引和主数据分配不同的 Column Family,又在物理存储上把它们隔离了起来。逻辑和物理上的双重隔离避免了将两类数据存放在同一

张表里带来的副作用,防止了它们之间的相互干扰,降低了数据维护的复杂性,可以说这是在性能和可维护性上达到的最佳平衡。

表  Hbase表格设计

让我们通过一个示例来详细了解一下二级多列索引表的结构:

假定有一张 Sample 表,使用四位数字构成 Hash 前缀,范围从 0000 到 9999,规划切分

100 个 Region,则 100 个 Region 的 RowKey 区间分别为[0000,0099],[0100,0199],……,

[9900,9999]。

以第一个 Region 为例,请看图 2-23,所有数据按 RowKey 进行字典排序,自动分成了索引区和主数据区两段,主数据区的 Column Family 是 d,下辖 q1,q2,q3 等 Qualifier,为了简单起见,我们假定 q1,q2,q3 的值都是由两位数字组成的字符串,索引区的 Column Family 是 i,它不含任何 Qualifier,这是一个典型的“Dummy Column Family“,作为区别于 d 的

另一个 Column Family,它的作用就是让索引独立于主数据单独存储

接下来是最重要的部分,即索引和主数据的 RowKey,我们先看主数据的 RowKey,它由四位 Hash 前缀和原始 ID 两部分组成,其中 Hash 前缀是由引擎分配的一个范围在 0000 9999 之间的随机值,通过这个随机的Hash 前缀可以让主数据均匀地散列到所有的Region ,我们看图 1,因为 Region 1 的 RowKey 区间是[0000,0099],所以没有任何例外,凡是且必须是前缀从 0000 到 0099 的主数据都被分配到了 Region 1 上。

接下来看索引的 RowKey,它的结构要相对复杂一些,格式为:RegionStartKey-索引名-索引键-索引值,与主数据不同,索引 RowKey 的前缀部分虽然也是由四位数字组成,但却不是随机分配的,而是固定为当前 Region StartKey,这是非常重要而巧妙的设计,一方面,这个值处在 Region RowKey 区间之内,它确保了索引必定跟随其主数据被划分到同一个 Region 另一方面,这个值是 RowKey 区间内的最小值,这保证了在同一 Region 里所有索引会集中排在主数据之前。接下来的部分是“索引名”,这是引擎给每类索引添加的一个标识,用于区分不同类型的索引,图 1 中展示了两种索引:a b,索引 a 是为字q1 q2 设计的两列联合索引,索引 b 是为字段 q2 q3 设计的两列联合索引,依次类推,我们可以根据需要设计任意多列的联合索引。再接下来就是索引的键和值了,索引键是由目标记录各对应字段的值组成,而索引值就是这条记录的 RowKey

现在,假定需要查询满足条件 q1=01 and  q2=02 的 Sample 记录,分析查询字段和索引匹配情况可知应使用索引 a,也就是说我们首先确定了索引名,于是在 Region 1 上进行 scan 的区间将从主数据全集收窄至[0000-a, 0000-b),接着拼接查询字段的值,我们得到了索引键:0102,scan  区间又进一步收窄为[0000-a-0102,  0000-a-0103),于是我们可以很快地找到0000-a-0102-0000|63af51b2  这条索引,进而得到了索引值,也就是目标数据的 RowKey:0000|63af51b2,通过在 Region 内执行 Get 操作,最终得到了目标数据。需要特别说明的是这个 Get 操作是在本 Region 上执行的,这和通过 HTable 发出的 Get 有很大的不同,它专门用于获取 Region 的本地数据,其执行效率是非常高的,这也是为什么我们一定要将索引和它的主数据放在同一张表的同一个 Region 上的原因

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

javafanwk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值