HBase和RDBMS的比较
HBase和其他面向列的数据库常常被拿来和更流行的传统关系数据库(或简写为 RDBMS)进行比较。虽然它们在实现和设计上的出发点有着较大的区别,但它们都 力图解决相同的问题。所以,虽然它们有很多不同点,但我们仍然能够对它们进行 公正的比较。
如前所述,HBaSe是一个分布式的、面向列的数据存储系统。它通过在HDFS上提 供随机读写来解决Hadoop不能处理的问题。HBase自底层设计开始即聚焦于各种 可伸缩性问题:表可以很“高”(数十亿个数据行);表可以很“宽”(数百万个 列);水平分区并在上千个普通商用机节点(commodity node)上自动复制。表的模式 是物理存储的直接反映,使系统有可能提供高效的数据结构的序列化、存储和检 索。但是,应用程序的开发者必须承担重任,选择以正确的方式使用这种存储和检 索方式。
严格来说,RDBMS是一个遵循“Codd的12条规则”(Codd's 12 Rules)的数据库。 标准的RDBMS是模式固定、面向行的数据库且具有ACID性质和复杂的SQL查 询处理引擎。RDBMS强调事务的“强一致性”(strong consistency),参照完整性 (referential integrity),数据抽象与物理存储层相对独立,以及基于SQL语言的复 杂査询支持。在RDBMS中,可以非常容易地建立“二级索引” (secondary index),执行复杂的内连接和外连接,执行计数、求和、排序、分组等操作,或对 表、行和列中的数据进行分页存放。
对于大多数中小规模的应用,MySQL和PostgreSQL之类现有开源RDBMS解决方 案所提供的易用性、灵活性、产品成熟度以及强大、完整的功能特性几乎是无可替 代的。但是,如果要在数据规模和并发读写这两方面中的任何一个(或全部)上进行 大规模扩展(scale up),就会很快发现RDBMS的易用性会让你损失不少性能,而如 果要进行分布式处理,更是非常困难。RDBMS的扩展通常要求打破Codd的规 则,如放松ACID的限制,使DBA的管理变得复杂,并同时放弃大多数关系型数 据库引以为荣的易用性。
成功的服务
迄今为止,如何解决以上扩展问题并没有一个清晰的解决办法。无论怎样,都需要 开始横向进行扩展。可以尝试在大表上进行某种分区或查看一些能提供多主控机的 商业解决方案。
无数应用、行业以及网站都成功实现了RDBMS的可伸缩性、容错和分布式数据系 统。它们都使用了前面提到的很多策略。但最终,你所拥有的已经不再是一个真正 的RDBMS。由于妥协和复杂性问题,系统放弃了很多易用性特性。任何种类从属 复本或外部缓存都会对反规范化的数据引入弱一致性化(weak consistency)。连接和二 级索引的低效意味着绝大多数查询成为主键查找。而对于多写入机制(0^出writer) 的设置很可能意味着根本没有实际的连接,而分布式事务会成为一个噩梦。这时, 要管理一个单独用于缓存的集群,网络拓扑会变得异常复杂。即使有一个做了那么 多妥协的系统,你仍然忍不住会担心主控机崩溃,或在几个月后,数据或负载可能 会增长到当前的10倍。
HBase
让我们考虑HBase,它具有以下特性。
没有真正的索引:行是顺序存储的,每行中的列也是,所以不存在索引膨胀的问题,而且插人性 能和表的大小无关。
自动分区:在表增长的时候,表会自动分裂成区域,并分布到可用的节点上。
线性扩展和对于新节点的自动处理: 增加一个节点,把它指向现有集群,并运行Regionserver。区域自动重新进行 平衡,负载会均匀分布。
普通商用硬件支持:集群可以用1000到5000美金的单个节点搭建,而不需要使用单个得花5万美 金的节点。RDBMS需要大量I/O,因此要求更昂贵的硬件。
容错:大量节点意味着每个节点的重要性并不突出。不用担心单个节点失效。
批处理:MapReduce集成功能使我们可以用全并行的分布式作业根据“数据的位置” (location awareness)来处理它们。
如果你没日没夜地担心数据库(正常运行时间、扩展性问题、速度),应该好好考虑 从RDBMS转向使用HBase。你应该使用一个针对扩展性问题的解决方案,而不是 性能越来越差却需要大量投人的曾经可用的方案。有了 HBase,软件是免费的,硬 件是廉价的,而分布式处理则是与生俱来的。
实例:HBase在Streamy.com的使用
Streamy.com是一个实时新闻聚合器和社会化分享平台。它有很多功能特性。我们 最早是在postgreSQL上开始的,实现很复杂。postgreSQL是一个很棒的产品,社 区支持很好,代码很漂亮。我们尝试了教材中所有可以在扩展时提速的技巧,甚至 为了适应我们的应用需求而修改了代码。一开始,我们利用了 RDBMS的所有优 点。但是,我们逐渐一一放弃了这些特性。最后,所有的团队都成为了DBA(需要 在开发的同时考虑管理任务)。
我们的确解决了碰到的很多问题,但是有两个问题最终迫使我们必须寻求RDBMS 以外的解决办法。
Streamy从几千个RSS源爬取数据,并对其中的上亿个信息项进行聚合。除了必须 存储这些信息项以外,我们应用中有一个复杂的查询需要从一个源的集合中读取按 时间排序的列表。极端情况下,在单个查询中会牵涉数千个源及其的所有信息项。
超大规模的信息项表
一开始,只有一个信息项表。但是,大量的二级索引导致插入和更新非常慢。我们 开始把数据项分成几个一对一链接的表来存储其他信息,通过这种方式把静态字段 和动态字段区分开,根据字段查询方式来对字段分组,并据此进行反规范化。即使 做了这些修改,单独的各个更新操作也需要重写整个记录,所以要跟踪信息项的统 计信息就很难做到可扩展。重写记录、更新索引都是RDBMS的固有特性,它们是 不能去除的。我们对表进行了分区,由于有时间属性可以作为自然分区的属性,所 以这本身并不困难。但应用的复杂性还是很快超出我们能够控制的范围。我们需要 另一种解决办法!
超大规模的排序合并
对按时间进行排序的列表进行“排序合并”(sort merge)是Web 2.0应用中常见的操作。相应的SQL查询示例可能如下所示:
MERGE( SELECT id, stamp, type FROM streams WHERE type IN ('type1','type2','type3','type4',...,'typeN'))ORDER BY stamp DESC LIMIT 10 OFFSET 0;
假设id是streams上的主键,在stamp和type上有二级索引,RDBMS的查询 规划器会像下面这样处理查询:
MERGE (SELECT idj stamp, type FROM streamsWHERE type = 'typel' ORDER BY stamp DESC,...,SELECT id, stamp, type FROM streamsWHERE type = 'typeN' ORDER BY stamp DESC )ORDER BY stamp DESC LIMIT 10 OFFSET 0;
这里的问题是我们只要最前面的.10个ID。但査询规划器实际会物化整个合并操 作,然后在再最后限定返回结果。简单地对每个type使用堆排序(heap sort)让你可 以在有10个结果以后就进行“提早过滤”(early out)。在我们的应用中,每个 七乂口6可以有数万个ID,因此物化整个列表再进行排序极其缓慢,而且没有必要。 事实上,我们进一步写了一个定制的PL/Python脚本,用一系列的查询来进行堆排 序。查询如下:
SELECT id, stamp, type FROM streams WHERE type = 'typeN'ORDER BY stamp DESC LIMIT 1 OFFSET 0;
如果从typeN中获得了结果(在堆中第二个最近的项),我们继续执行下面的查询:
SELECT id, stamp, type FROM streams WHERE type = 'typeN'ORDER BY stamp DESC LIMIT 1 OFFSET 1;
在几乎所有情况下,这都胜于直接用SQL实现和采用查询规划器的策略。在SQL 最差的执行情况下,我们采用Python过程的方法比它们要快一个数量级。我们发 现,要想满足需求,需要不停尝试优于査询规划器的方法。在这里,我们再一次觉得需要有其他解决办法。
有了HBase以后的日子
我们的基于RDBMS的系统始终能够正确地满足我们的需求:问题出在可扩展性 上。如果重点关注扩展和性能问题而非正确性,最终少不了千方百计地找出捷径, 并针对问题进行定制优化。一旦开始为数据的问题开始实现自己的解决方案, RDBMS的开销和复杂性就成为障碍。逻辑抽象和存储层的独立性与ACID要求成 为巨大的屏障。它们成为在开发可扩展系统时无法承受的负担。HBase只是一个分 布式、面向列、支持排序映射的存储系统。它唯一进行抽象的部分就是分布式处 理,而这正是我们不想自己面对的。另一方面,我们的业务逻辑斤此丨加^ logic)* 高度定制化和优化的。HBase并不试图处理我们的所有问题。这些部分我们自己能 够处理得更好。我们只依靠HBase来处理存储的扩展,而不是业务逻辑。能够把 精力集中在我们的应用和业务逻辑,而不需要关心数据的扩展问题,使我们完全得 到了解放。
我们目前已经有包含数亿数据行和数万列的表。能够存储这样的数据让人感到兴 奋,而不是恐惧。