本文翻译自Jim R. Wilson的Understanding HBase and BigTable

学习HBase(Google BigTable的开源实现)最难的部分,就是从概念上理解它到底是什么。

我觉得挺不幸的是,这两个很棒的系统的名字用了表(table) 和库(base)这样的字眼,这很容易把熟悉关系型数据库的那些人(像我自己)搞晕。

这篇文章主要想从概念上描述清楚这两个分布式数据存储系统,读完它以后,当你面对“该用HBase还是该用传统数据库”这样的问题的时候,应该能够做出更加明智的决定。

都在术语里边

幸运的是,Google的BigTable论文清楚地解释了BigTable实际上是什么。这是论文“Data Model”部分的第一句话:

BigTable是一个稀疏的,分布式的,持久化的,多维的,有序的映射表(map)。

注意:读完上面这句话以后,有些读者可能有点晕,现在可以定一下神。

这篇论文接着解释道:

这个map是用row key,column key, 和timestamp来做索引的,每个在map里的值都是未经处理的字节数组。

同样地,HBase架构文档指出:

HBase使用的数据模型与BigTable非常相似。用户将数据行存在有标签的表中。一个数据行有一个可排序的key 和任意多个列。表的存储是稀疏的,所以只要用户喜欢,同一张表的数据行能够有疯狂变化着的不同数量的列。

虽然这一切看起来相当神秘,但是当你把它分成一个个单词的话,就可以理解了。我将按照以下这个顺序来讨论它们:映射表,持久化,分布式,有序,多维度,和稀疏。

与其尝试整个儿来理解一个系统,我发现化整为零更容易理解。

映射表

本质上来说,HBase和BigTable就是一个map。取决于编程语言背景,你可能更熟悉关联数组(PHP),字典(Python),哈希(Ruby),或者对象(JavaScript)。

根据维基百科,map是一个抽象的数据类型,由一个键的集合和一个值的集合组成,每一个键对应一个值。

下边的是个简单的用JSON语法描述的map的例子,它的所有值都是字符串类型:

{
  "zzzzz" : "woot",
  "xyz" : "hello",
  "aaaab" : "world",
  "1" : "x",
  "aaaaa" : "y"
}

持久化

持久化就是说你存进这个特殊的map中的数据“一直存在”,即使创建了或访问了它的程序都已经者结束了。这个概念与其他的持久化存储没什么区别,比如文件系统。接着讲。。。

分布式

HBase和BigTable建立于分布式文件系统之上,以致于底层的文件存储能够分布在一系列的独立的机器上。

HBase是基于Hadoop的分布式文件系统(HDFS)或者亚马逊的简单存储服务(S3)的上层应用,而BigTable则是基于Google文件系统(GFS)

数据的副本被复制到多个机器节点上的方式和数据在磁盘冗余阵列(RAID)里面分布的方式有点相似。

对于本文的目的来说,我们并不关心具体使用的是哪种分布式文件系统。重要的是知道它是分布的,这样数据在比如集群内的某个节点挂掉时,有一定保障。

有序的

不同于大部分的map的实现,在HBase/BigTable里,键值对严格按字母表顺序排序,就是说键为”aaaaa”的数据行的下一个行键应该是”aaaab”,并且列键为”zzzzz”的数据行比较远。

继续我们的JSON例子,排序后是这样:

{
  "1" : "x",
  "aaaaa" : "y",
  "aaaab" : "world",
  "xyz" : "hello",
  "zzzzz" : "woot"
}

由于这些系统通常很大很分布,这个有序的特点实际上非常重要。相近的键对应的数据行存储在一起,这样当你需要扫描表的时候,你最感兴趣的记录是彼此临近的。

这一点对于选择键的规范是重要的。举个例子,如果一个表的键是域名,将域名反过来写是非常有必要的(用“com.jinbojw.www”而不是“www.jimbojw.com”),这样子域名的数据行会比较临近父域名的数据行。

继续考虑域名这个例子,键为“mail.jimbojw.com”的数据行将会靠近“com.jimbojw.www”的数据行,而不是临近比如“mail.xyz.com”这种数据行。如果不将域名反过来,就会出现后者这种情况。

注意HBase/BigTable中的有序不是值有序而是键有序。除了键,没有自动索引任何其它东西,这一点就好象普通的map实现一样。

多维度

直到目前,我们还没有提到任何“列”的概念,在概念上我们把“table”当成普通的hash/map,这完全是有意的。“列”这个词像“表”和“库”一样是另一个沉重的单词,承担了多年的关系型数据库经验的感情包袱。

相反,我觉得将它看做一个多维度的map(或者嵌套的map,如果愿意的话)更容易。我们的JSON例子添加一个维度的话,就成了下边这个样子:

{
  "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"
  }
}

在上边的例子中,你会注意到,现在每个键都指向一个正好有两个键“A”“B”的map。从现在开始,我们把最顶层的键值对看做是“行”。而且,按照BigTable和HBase的命名规则,“A”和“B”映射表则被称为“列簇”。

一个表的列簇在表被创建的时候就被指定了。并且以后难以或者说不可能被修改。添加一个列簇也有可能非常昂贵,所以最好一开始就一次性指定好你需要的列簇。

幸运的是,一个列簇可能有任意个数量的列,用“column qualifier”或者“列标签”来标识。下边我们的JSON例子的一部分,这次加上了列标识:

{
  // ...
  "aaaaa" : {
    "A" : {
      "foo" : "y",
      "bar" : "d"
    },
    "B" : {
      "" : "w"
    }
  },
  "aaaab" : {
    "A" : {
      "foo" : "world",
      "bar" : "domination"
    },
    "B" : {
      "" : "ocean"
    }
  },
  // ...
}

注意这两行数据里,“A”列簇有两个列:“foo”和“bar”,“B”列簇仅有一列,并且其列标识为空字符串。

当查询HBase/BigTable的数据时,你必须提供全列名,格式为“:”,举个例子,上边的例子中共有三列:“A:foo”,“A:bar”,和“B:”。

注意,虽然列簇是不变的,但是列是可变的。考虑下面这行扩展了的数据:

{
  // ...
  "zzzzz" : {
    "A" : {
      "catch_phrase" : "woot",
    }
  }
}

这里,数据行“zzzzz”有且仅有一列,“A:catch_phrase”。因为每行可有任意多的不同的列,没有内置的方法来列出所有行中的所有列。要得到这个信息,你得做一个全表扫描。但是你可以查询所有的列簇,因为他们(基本上)是不可变的。

HBase/BigTable里最后的维度是时间。所有的数据都是有版本的,要么是整数的时间戳(Unix时间戳),或者用户选择的其他整数。客户端会在插入数据的时候指定时间戳。

考虑加上了用户指定的整数时间戳的例子:

{
  // ...
  "aaaaa" : {
    "A" : {
      "foo" : {
        15 : "y",
        4 : "m"
      },
      "bar" : {
        15 : "d",
      }
    },
    "B" : {
      "" : {
        6 : "w"
        3 : "o"
        1 : "w"
      }
    }
  },
  // ...
}

每个列簇可以自己决定一个数据单元保留多少个版本(一个rowkey/column唯一指定一个数据单元)。大多数的情况下,应用只会简单的请求给定的数据单元的数据,而不指定时间戳。那样,HBase/BigTable将会返回最新的版本(时间戳最大的版本),因为它是按时间倒序排序的。

如果应用程序要获取一个特定的行并指定时间戳,HBase将会返回时间戳小于或等于给定的时间戳的那个数据单元。

用我们假想的HBase表,查询行/列为“aaaaa”/“A:foo”的数据单元将会返回”y“,而查询行/列/时间戳 为“aaaaa”/“A:foo”/10的数据单元将会返回“m”,当查询行/列/时间戳 为“aaaaa”/“A:foo”/2 将会返回一个空的结果。

稀疏

最后的关键词是稀疏。像上边提到的,一个给定的数据行的每个列簇能有任意多的列,或者根本没有列。另一种类型的稀疏是基于数据行的空隙,仅指键值之间的空隙。

这只有用本文基于map的术语,而不是用关系型数据库的相关概念来理解HBase/BigTable,才会觉得非常合理。

结束语

希望对你理解HBase的数据模型有所帮助。