HBase是一个类Bigtable系统,按照Google的论文对 Bigtable的定义是“一种稀疏的,分布式的,持久的多为维度的有序Map。这个Map由row key,column key和timestamp做为索引,Map中的值是连续的byte数组”。HBase的多维度,包括table和column family等。
不是所有维度都是等同的,不同的维度有其特殊的意义。例如,row这个维度用于region切分,所以可以增长到海量。而column则不用于分 片,和row不同的是,一个row中的多个columns的put或者delete操作是一个原子事务(当然,同一个原子事务中不可能同事put和 delete)。而这篇文章将重点关注时间维度。
1. 基本概念
在Bigtable论文中,一个{row key,column key}对应的是一个cell。每个cell可能包含多个版本的数据,以timestamp索引,这就是本文要讲述的时间索引。
1.1 时间/版本维度中的key
Row key)和column key(在HBase中也称为qualifier)是bytes类型,而时间维度的key则是long integer类型,比较典型的是使用 java.util.Date.getTime()或者System.currentTimeMillis()来做为时间维度的key。
时间维度的各个版本是倒序排列后存储的,所以从storefile中读取的时候,最先读到的就是最新的时间,这个特性在系统设计的时候如果能有效利用,会非常有用。
下面我们从几个核心操作get,put和delete来看看时间维度的具体细节。
1.2 Get
默认情况下,Get操作会返回每个cell的timestamp值最大的版本(但这并不一定代表是最新写入的版本,后面会提到)。这个默认行为可以通过下面两种方式改变:
1. 通过设置 Get.setMaxVersions()可以返回多个版本的数据。
2. 通过设置 Get.setTimeRange()可以返回其他时间范围的版本。
如果要返回除最新版本之外的某个特定版本,可以通过组合上面两个条件来达成,首先设置时间范围为0到希望返回的时间,然后设置最多返回一个版本即可。
1.3 Put
每次执行Put,对应的cell都会插入一个新的版本。默认使用的是currentTimeMillis。应用也可以使用自定义的值来做为每个列的 timestamp,只需要是一个long integer的值即可,不一定是时间,如果是时间,也可以是过去或者未来某个时间,所以前面说到方Get返回的是timestamp最大值的版本,而不 一定是最新写入的版本。
HBase的架构中,数据文件的写入只能附加(Append)而不能修改。数据文件只有在执行compact操作是才重写。数据文件中存储的是键值 对,其中键是{row key,column key,timestamp}的组合键,每次put新的值的时候,会产生新的键值对附加在数据文件中,即时你使用一个已经存在的timestamp也一 样。因此短时间内对同一行做操作可能导致大量的键值对存储到文件中,根据gc的策略,下一次执行compact的时候才会回首老的版本。
1.4 Delete
关于Delete和时间维度有很多事要讲。
1.4.1 GC(垃圾收集)
HBase有两种方式回收旧的版本
1. 可以设置最多可以保留的版本数量。如果超过,则最老的版本会被抛弃,默认设置是3个版本,这个可以在创建Column Family时通过HColumnDescriptor.setMaxVersions(int versions)设置,因此这个是Column Family级别的。当然这个限制中读取的时候是逻辑限制,即时生效,但老版本的物理删除还是需要等到major compact操作中执行。将这个值设置为1并不是说就禁用了多版本,每次Put的时候还是同样的会生成新的版本,只是最后只保留一个版本而已。
2. 可以设置TTL(Time To Live),如果版本存在的时间超过TTL,则会被删除。默认的TTL是forever。通过 HColumnDescriptor.setTimeToLive(int seconds)可以设置TTL,物理清除版本还是要等到major compact,但Get/Scan等读取操作逻辑是即时生效的。需要注意的是,如果row中所有的cell都被TTL失效以后,这一行记录就被删除了 (HBase中不需要显示的建立或者删除行,如果行中的cell有值,行就存在)。
对于上面的两种回收方式,会有一种有趣的场景,假设你对一个cell插入了三个版本t1,t2,t3,而最大版本数设置是2,这时候请求所有版本, 则只有t2和t3会返回,这是正常的。但这时如果删除t2或者t3,再请求所有版本,则t1可能又会出现在结果中了。当然,在major compact操作之后,就不会有这个问题了。因此major compact并不是对用户透明的操作,可能会影响用户的查询结果。
1.4.2 Manule delete
中HBase中执行delete操作时,有两种方式可以删除某个特定的版本
1. 删除某个timestamp之前的所有老版本
2. 删除某个timstamp点的版本
delete操作可以针对row,column family,或者column,只有最后一种情况下可以删除某个timestamp点的版本。而针对row和column family的delete,则只能删除某个timestamp之前的所有版本。
举个例子,假设我们要删除一整行数据,你可以指定timestamp,或者使用currentTimeMillis,那“删除某个 timestamp之前的所有老版本”意味着什么呢?HBase并不会去修改原有数据,因此删除不是立即在原数据对应的文件中执行物理删除或者物理标记删 除,而是将删除的记录保存中所谓的tombstone中,当HBase执行major compact的时候,再根据tombstone去执行物理删除。
如果你指定的timestamp比row中最新的版本大,则相当于删除了整行数据。
2. 使用timestamp
从上面的描述可以看到,时间维度主要是用户多版本的,但你也可以在应用中把它当作column外的另外一个维度来使用,不同的是它的key是长整数型,目前这种方式还存在一些bug,因此还不是很推荐。
在jira中,有两个和时间维度有关的bug值得比较有意思
1. 利用时间维度多版本实现HBase多数据中心复制,参看HBASE-1295 (PDF附件的第四页) 或者这个邮件。
2. HBASE-2406的一个评论提到,如果要在多个表中获得一致性数据(如二级索引表),可以通过向多个表写入相同的timestamp版本来实现。
3. 限制
由于一些bug或者“不可预期的行为”,时间维度目前还有一些问题需要解决。
1. HBASE-1485 和HBASE-2406中 提到的覆写已经存在的timetamp版本的值的问题(在0.90版本中已经解决),换句话说,根据{row key ,column key, timestamp}修改某个cell的值,实际会产生一个新的键值对,对于HBase来说就会存在两个相同timestamp的key,判断最大 timestamp版本就会有问题。当然,通过HBASE-2649中提到的写入到HFile的时间序可以解决这个问题。
2. HBASE-2256中 提到的delete覆盖put(即使put操作发生在delete之后)的问题。由于delete只是记录tombstone,物理删除需要等到 major compact操作,因此有一种情况,你删除了所有<=T的版数据,然后又put了一条<=T的数据,还是会被tombstone标记为删 除。当然,如果你使用的是递增的timestamp不会有这个问题,但如果使用系统时间,而delete和put间隔时间非常短,还是有一定几率两个操作 的timestamp是相同的,这时候杯具依然会存在。
转载自:http://www.cnblogs.com/JemBai/archive/2012/07/21/2602439.html