phoenix-4.8.0本地索引实现原理

1. 前言

phoenix有全局索引以及本地索引(可变与不可变等其它的且不谈),全局索引理解应该比较简单,如果让我自己去实现hbase的索引应该想到的也是全局索引这种方式。本地索引适用于写比较频繁,储存空间受限的情况。

Local indexing targets write heavy, space constrained use cases.

phoenix-4.8.x的本地索引与之前版本的实现方式完全不同,在官网中也没有比较详细的说明(感觉phoenix的文档跟不上版本更新)。以4.8版本为分水岭,分别说一下新旧版本的两种不同实现方式。

2. 本地索引前置条件

实现本地索引需要考虑以下3个方面的因素:

  1. 索引数据与原表数据需要位于同一regionserver(colocation),这是为了性能而考虑,可以避免不必要的网络传输。同时,它也是条件2的前置条件:意味着数据region和索引region都以原子性的方式被同一个regionserver打开(要么一起提供服务,要么一起offline)。
  2. 一致性:指的是索引表与原表可以被原子更新数据,有两种方式:1)使用事务 ;2)索引表与原表共享相同的wal同时使用MVCC机制。
  3. 需要考虑客户端如何使用索引数据:如果本地索引是保存在一个单独的表中,那么客端直接进行scan等一些操作,但如果索引数据隐藏在原数据region当中,那么就需要另外的机制去保障如何从原数据region中访问到索引数据。

3. 旧版本实现方式

4.8以前版本的实现方式与全局索引有点相似(索引数据与原表数据分离),不同的是本地索引有自己的split逻辑,具体的细节可以参考华为hbase二级索引的实现原理,这种实现方式的特点有:

  1. 由一个单独的表去保存本地索引数据
  2. 原表以及索引表各自拥有自己的WAL,这意味着一致性得不到保障,有可能原表数据多于索引数据,也有可能相反。
  3. 使用balancer来尽量保证colocation,但只能提供一个很弱的保证,最坏的情况下它将会失去原表数据与索引数据co-locate特性。
  4. 使用本地索引就像对一个常规的phoenix进行操作,例如查询的时候根据查询条件可以快速定位到索引数据处于哪个region上(这点比4.8的实现方式好)。

4. 4.8版本实现方式

4.1 特点

引用官网的描述:

From 4.8.0 onwards we are storing all local index data in the separate shadow column families in the same data table

这里说明了索引的数据是与原表数据储存在同一个表的,不过索引数据是一个独立的”阴影列族”,在网上是搜索不到这个概念的,个人理解是因为这个一列族客观是存在的,但使用时对用户不可见,这种方式的特点有:

  1. 这种存储特性使得colocation得到强保证,因为原表数据与索引数据的RowKey都在同一个范围内,使得它们会位于同一个region中。
  2. 因为原数据与索引数据保存在同一个region,所以它们共享同一个WAL与MVCC标志,使得原子性也得到保证:当插入一行记录时,会同时解析出其中的索引数据并附加到本次的插入,所以,实际插入的行=业务数据+索引。
  3. 索引数据的生成以及读取都依赖于原表以及索引表(索引表已经是一个逻辑表),原因在下面介绍本地索引RowKey的组成会说明。
  4. 这种实现方式不需要对hbase作任何改变,最主要的是不用再去维护region的split以及merge(以前的实现方式需要phoenix自己实现逻辑以保证split/merge后索引数据与原数据依然在同一个region中)

4.2 索引构成

1.创建一个测试表

CREATE TABLE USER (ID BIGINT NOT NULL PRIMARY KEY, NAME VARCHAR  NULL,AGE INTEGER  NULL, ADDRESS VARCHAR NULL) ;

2.为测试表创建一个本地索引

CREATE LOCAL INDEX USER_IDX ON USER(NAME);

3.插入一条记录

UPSERT INTO USER(ID,NAME,AGE,ADDRESS) VALUES(1,'name1',11,'china');

4.进入hbase shell并scan

这里写图片描述

观察scan结果,只upsert了一条记录,却出现了2 row(s),那是因为多了一行索引数据,可以看到第一行记录即为索引数据:
\x00\x00name1\x00\x80\x00\x00\x00\x00\x00\x00\x01

分析一下索引的组成部分,上面以3种颜色表示不同的部分:

\x00\x00:这是代表索引的编号,如果再创建多一个索引,那么会变成\x00\x01
name1\x00:索引列的值,后面的\x00其实是空格,不知是不是为了起标记作用
\x80\x00\x00\x00\x00\x00\x00\x01:这串就是RowKey即ID值了,可以参考scan正面的记录值。

注意,上面的分析是基于一个region的情况,如果原表是加盐表,RowKey=加盐前缀+ID,而索引数据也会一样在索引编号前面加上同样的加盐前缀,这样可以保证了索引与原表数据保存在同一个桶(region)中,保证了colocate特性。这里的加盐前缀其实相当于该region的starRowKey。其中还涉及了很多实现细节,例如当region进行split时会怎么样,可以去参考一下奇虎360Hbase二级索引的实现原理。

4.3 不足之处

  1. 当确定查询条件进行查询,此时是无法确定我们要检索的数据位于哪一个region上面,根据上面所分析的索引构成,索引还有一部分是加盐前缀(region的startRowKey),这个部分就是查询时无法确定的,所以当一个查询请求到达时,所有region都会响应,这会带来额外的开销。相比于全局索引或者旧版本的本地索引实现方式,由于是一个单独的表来保存索引数据,所以根据查询条件可以快速定位到索引数据所在的region.
  2. 使用本地索引一般都是先根据查询条件获取到数据rowkey,然后再根据rowkey到原表查询其它列的详细信息,所以,每一次查询都包含了scan以及seek操作,这也是它不适用于读频繁的原因之一。

4. 参考

https://phoenix.apache.org/secondary_indexing.html
https://issues.apache.org/jira/browse/PHOENIX-1734

猜你喜欢

转载自blog.csdn.net/czmacd/article/details/53958488