日期:2014-05-16  浏览次数:20565 次

一种HBase上Region级别的二级索引存储

我们会经常谈及二级索引,这是对全表数据进行另外一种方式的组织存储,是针对table级别的。如果要为HBase上的表实现一个强一致性的二级索引,那么就无法逃避分布式事务,而这一直是用户最期待的功能。 而即使只需要保证最终一致性,这个索引也并不好实现,因为你需要额外的表以存储过程数据,需要解决宕机恢复问题等

?

撇开分布式事务,我们是否可以考虑对索引的要求进行降级,比如把Region看成是全表下的子表,实现一套Region级别的索引,通过功能上的牺牲以换取实现的简易及稳定。

?

在某些存在用户概念的场景下,比如消费记录,我们总是会在确定的用户下,进行数据查找。这意味着,在此类场景中,我们只需要一个用户级别的索引。

?

举个例子,对于一笔交易记录,我们至少会有这么几个维度:

用户Id,交易时间,交易金额,交易状态(还会有交易名称,交易号ID,对方ID等)

?

当存储于HBase时,一般可以这么组织:

RowKey= 用户Id+交易时间

列1=交易金额

列2=交易状态

?

所以当我们要读取某个用户的在某段时间内的交易记录的时候,我们可以设置一个Scan:

startRow=用户Id+开始时间

stopRow=用户Id+结束时间

?

如果我们要增加查找条件,进行过滤,比如要读取某个用户在某段时间内交易状态为取消的交易记录,我们可以为上述Scan设置一个Filter,来过滤不符合查询条件的结果。

?

如果这是一个大商户,某段时间内的交易记录数巨多,通过设置Filter来过滤的方式就显得效率低下,开销巨大。

为了优化此类查找,业务只能自建索引表,可以如下组织:

RowKey= 用户Id+交易状态+交易时间

列1=交易金额

?

由此产生的问题时,当产生一笔交易记录的时候,我们需要向2张表中写入数据,不用说原子性,为了保证最终一致性,也得会花费不少的力气

?

彼之痛,己之痛,或许一个Region级别的索引存储能有一定的疗效。

?

什么是Region级别的索引存储

我们知道在HBase的结构中,一个Region可以包含多个Store,而索引存储则也是Region下面的一个Store,我们称其为Assistant Store,但它会有一些不同点:

a.Assistant Store中的数据由Regionserver按照用户配置的规则自动写入,是源数据的一份拷贝,但是拥有不同的组织方式

b.Assistant Store中的数据可以不遵守Region的Row范围限制

c.Assistant Store中的数据由用户主动选择读取(不会智能的自动利用)

d.Assistant Store中的数据在Split时,遵守与源数据对应的原则

(可以先看例子)

?

一个简单的例子

假设现在表只有一个Region,往表写入以下6行数据:

r1/c1:q1/v1
r2/c1:q1/v2
r3/c1:q1/v1
r4/c1:q1/v2
r5/c1:q1/v1
r6/c1:q1/v2

?

如果我们已为这个表配置了一个简单的索引存储,该Assistant Store命名为c2,那么除了上面的数据,Region中还会包含以下数据:

v1/c2:q1/r1
v1/c2:q1/r3
v1/c2:q1/r5
v2/c2:q1/r2 (在插入源数据的时候自动生成,存在Assistant Store中)
v2/c2:q1/r4
v2/c2:q1/r6

?

显然,这些是简单的倒置索引数据(可以由用户定义生成的数据如何组织),当你对表进行正常的scan时候,你只能见到源数据,即r1,r2,...,r6。 但是你可以通过某种方式,访问Assistant Store中的数据,即v1,v2,以加快条件查找

?

?

Region分裂处理

如果我们将上面这个例子中的Region进行Split,Split row为'r4',那么源数据就会被分落在两个子Region中,Daughter_A 和 Daughter_B;

Daughter_A 包含如下源数据:

r1/c1:q1/v1
r2/c1:q1/v2
r3/c1:q1/v1
?

Daughter_B 包含如下源数据:

r4/c1:q1/v2
r5/c1:q1/v1
r6/c1:q1/v2

?

Assistant Store中的生成数据会遵守与源数据对应的原则,

Daughter_A 的Assistant Store中的索引数据为:

v1/c2:q1/r1
v1/c2:q1/r3

v2/c2:q1/r2

?

Daughter_B 的Assistant Store中的索引数据为:

v1/c2:q1/r5
v2/c2:q1/r4

v2/c2:q1/r6

?

?

原子性和一致性

解决了数据组织的问题,我们来看看如何保证源数据和生成数据间的原子性和一致性。

从上面的例子描述中,我们知道,设置了索引存储后,当我们写入一行数据时,实际上会存储多行数据,但这多行数据都是在同个Region中,这意味着可以用一个本地事务解决这多行数据的事务写入。或许有些用户不知道,HBase-0.94版本早就实现了本地Region的多行事务。

?

?

回看Region级别的索引存储的特点

a.Assistant Store中的数据由Regionserver按照用户配置的规则自动写入,是源数据的一份拷贝,但是拥有不同的组织方式

?

用户可以通过扩展类Assistant,来生成自己定义的数据格式,存储到Assistant Store中,

比如对于r1/c1:q1/v1,你可以生成一行v1/c1:q1/r1, 也可以生成一行v1r1/c1:q1/r1,也可以生成多行,但是生成的数据有一个限制,就是value值必须为源数据中的row值,这是为了保证源数据与生成数据之间能对应起来,当Region进行分裂的时候,索引数据和源数据仍然是对应的

?

?

b.Assistant Store中的数据可以不遵守Region的Row范围限制

从上面的例子中,我们可以看出,Assistant Store中的数据的Row是由用户自定义的,所以其Row是任意的,不会在Region的Row范围内

?

c.Assistant Store中的数据由用户主动选择读取(不会智能的自