0%

译-理解HBase和BigTable

前言

最近在了解HBase,作为一个HBase小白,迫切的想对HBase有一个较为直观的认识,但大部分文章切入的角度喜欢和RDMS做比较,看下来千篇一律,没有清晰形象的解释清楚HBase的基本数据模型。HBase官方手册中推荐了两篇博客,个人认为写的醍醐灌顶,比较适合我这种愚钝的人,决定用蹩脚英文稍加翻译记录,方便下次快速回忆。


开篇

学习Hbase(谷歌BigTable的开源实现)最困难的部分是HBase它实际是个什么。

非常不幸的是,Hbase和BigTable这两个伟大的系统在名称中都包含table和base这两个词,这往往导致和RDBMS(例如我自己)产生混淆。

这篇文章旨在从概念角度描述这些分布式数据存储系统。相信在你读完后会对何时使用HBase和传统数据库有更明智的决定。

数据解释

幸运的是Google’s BigTable Paper清晰的解释了BigTable是什么。这是“数据模型”一节的第一段话:BigTable是一个稀疏,分布,持久化,多维度有序map。

注意: 请牢记这个定义。

BigTable白皮书对这句话这么解释:这个map使用row key,column key和一个时间戳来做索引,map中每个值都是一个无意义的字节数组。

按照这些原则,Hadoop百科的HBase Architecture白皮书中这么描述:HBase使用的数据模型和BigTable非常相似。用户将数据行存储在带标签的表中。一个数据行有一个有序的key和一个任意数量的column。这个表存储是稀疏的,如果用户愿意,同一个表中行可以有疯狂变化的column。

尽管这些看起来很神秘,但当你一次理解一个关键词就会很有感觉了。我将以以下顺序来讨论他们:map,持久化(persistent),分布式(distributed),有序的(sorted),多维的(multidimensional),和稀疏的(sparse)。

map

HBase/BigTable的核心是map。基于不同的编程语言,您可能更熟悉相关术语PHP的array,Python的dictionary,Ruby的Hash或者JavaScript的Object。

从维基百科的文章中我们知道,一个map就是“一个抽象的数据类型,由一组key和一组value组成,其中每个key关联一个value。”。使用JavaScript Object Notetion来表示一个简单的map结构如下:

1
2
3
4
5
6
7
{
"zzzzz" : "woot",
"xyz" : "hello",
"aaaab" : "world",
"1" : "x",
"aaaaa" : "y"
}

persistent

持久化意味着在你把数据放到这个特殊的map后,他们将持久存在。这个和其他任何持久化存储没有区别,比如:文件系统上的文件。

distributed

HBase和BigTable都建立在分布式的文件系统之上,,因此基础文件存储可以分布在一系列独立的计算机中。

Hbase底层存储可以是Hadoop的分布式文件系统(HDFS)Amazon的简单存储服务(S3),而BigTable底层是Google文件系统(GFS)

数据在多个参与节点上的复制方式,和RAID系统中对数据跨磁盘的传输类似。

这里我们不关心底层使用的是那种分布式系统的实现。重要的是理解他是分布式的,它提供了一种在集群失败时对数据的保护。

sorted

与大多数map的实现不同,在HBase/BigTable中,key/value对保持的严格的字母顺序。也就是说,row key “aaaa“的下一个row key 应该是“aaaab”,他们和“zzzzz”里的很远。

继续使用JSON的方式来表示,有序的map版本看起来像这个样子:

1
2
3
4
5
6
7
{
"1" : "x",
"aaaaa" : "y",
"aaaab" : "world",
"xyz" : "hello",
"zzzzz" : "woot"
}

因为这些系统十分巨大,并且是分布式的,有序的特性就显得十分重要。key相似的行的空间临近性可以确保当你必须scan表时,你感兴趣的那些元素彼此之间间隔相近。

这是选择row key时很重要的一点。比如,决定一个表的key为域名。将域名反转(比如是“com.jinbojw.www”而不是“www.jinbojw.com”)是很有意义的,因为子域名和父域名的行会离的比较近。

继续域名的例子,域名行“mail.jinbojw.com”的下一行应该是“www.jinbojw.com”而不是“mail.xyz.com”,如果按照常规的域名顺序就会出现这种情况。

请注意,“sorted”(有序的)术语在HBase/BigTable中不是值“values”是有序的。除了键之外,没有其他任何自动索引,和普通的map实现一样。

multidimensional

以上讨论中我们没提到任何关于“column”(列)的概念,而使用常规的hash/map概念替代了“table”。这是故意的。“column”同“table”和“base”一样是另一个装载的词,它是我们多年RDBMS经验的感性认知。

我们可以通过一个简单的方式来看多维map—嵌套map。给我们的JSON例子增加一个维度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"1" : {
"A" : "x",
"B" : "z"
},
"aaaaa" : {
"A" : "y",
"B" : "w"
},
"aaaab" : {
"A" : "world",
"B" : "ocean"
},
"xyz" : {
"A" : "hello",
"B" : "there"
},
"zzzzz" : {
"A" : "woot",
"B" : "1337"
}
}

如上面的例子,我们注意到现在我们map中的每一个key的value都是一个由“A”和“B”两个key组成的map。进一步,我们可以把顶级key/map对看作一个“row”。类比到BigTable/Hbase的命名中,“A”和“B”就被叫做“Column Families”(列族)。

一个table的column families在创建table时就被指定,一旦创建完成后面修改起来就十分困难或者不可能被修改。同样的,增加一个新的column families的代价也是很高的,所以在创建之前一定要考虑好。

幸运的是,一个column family可以有任意数量的column,一个column被叫做“qualifier”或者“label”。下面我们给JSON例子增加一个column qualifier维度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...
"aaaaa" : {
"A" : {
"foo" : "y",
"bar" : "d"
},
"B" : {
"" : "w"
}
},
"aaaab" : {
"A" : {
"foo" : "world",
"bar" : "domination"
},
"B" : {
"" : "ocean"
}
},
// ...

我们注意到,“A”列族有两列:“foo”和“bar”,“B”列族只有一个限定符(qualifier)为空串(“”)的列。

当像HBase/BigTable请求数据时,我们必须提供全列名称“:”。以上面的JSON为例,一共有三个列:“A:foo”,“A:bar”和“B:”。

尽管列族是静态的,但是他们的列不是。考虑这个扩展行:

1
2
3
4
5
6
// ...
"zzzzz" : {
"A" : {
"catch_phrase" : "woot",
}
}

在这个场景中,“zzzzz”行只有一列,“A:catch_phrase”。因为每行可以有任意数量不同的列,所以没有内置的方法来查询所有行中的所有列。想要得到这个信息,你必须进行全表的扫描。但是,你可以查询所有列族,因为他们基本是不可变的。

HBase/BigTable中最后一个维度是时间。所有数据都使用整数时间戳(自纪元以来的秒数)或您选择的另一个整数来进行版本控制。在写入数据时,可以通过客户端指定时间戳。

使用任意整数时间戳的来更新下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
// ...
"aaaaa" : {
"A" : {
"foo" : {
15 : "y",
4 : "m"
},
"bar" : {
15 : "d",
}
},
"B" : {
"" : {
6 : "w"
3 : "o"
1 : "w"
}
}
},
// ...
}

每个列族都可以有自己的规则来决定一个cell(cell通过其row key/column进行标识)中保存多少个数据版本。大多数情况下,应用请求一个cell的数据并不会指定时间戳,通常,HBase/BigTable会返回最近的一个版本(时间戳最大的那个),因为他是按照降序的方式存储的。

如果应用请求时指定了一个时间戳,那么HBase将会返回时间戳相等的或者小于给定时间戳最大的cell。

使用我们假想的HBase表,通过row/column方式查询“aaaaa”/“A:foo”将会返回“y”,而通过row/column/timestamp方式查询“aaaaa”/“A:foo”/10将会返回“m”。查询“aaaaa”/“A:foo”/2将会返回null。

阅读注:关于时间戳的理解,可以看看另一篇文章来加深理解-->Bending time in HBase<--

sparse

最后一个关键词是sparce(稀疏的)。正如上面提到的,一个给定的行在每个列族中都可以有任意数量的列,或者压根没有。稀疏的另一种类型是行间隙,这意味着key之间可能存在间隙。

如果您思考HBase/BigTable同本文一样是基于map而不是类比RDBMS中的概念,那对稀疏将会有比较好的理解。

就这样吧

我希望这可以帮助你从概念上HBase的数据模型。

一如既往,我期待您的想法,评论和建议。


写在最后

以上是我对整个博文蹩脚的理解,其实对于稀疏的理解,个人认为还不是很直观,最好是结合HBase的物理视图来加以辅助,这样会更直观。

十分感谢Jim Wilson的这篇文章,让我比较清楚的了解HBase的基础概念。不恰当的类比害死人,希望初学者不要再拿HBase和关系型数据库做对比来入门了,那些文章简直看的是云里雾里,不知所云。

参考

翻译整理自:https://dzone.com/articles/understanding-hbase-and-bigtab