转载一篇好文章,另外推荐一个技术blog(银河里的星星):http://duanple.blog.163.com/
作者:Dhruba BOrthakur & Joydeep Sen Sarma etc. Facebook Inc 2011-6
原文:http://wenku.baidu.com/view/5b1f48ef0975f46527d3e18b.html
译者:phylips@bmy 2011-9-11
出处:http://duanple.blog.163.com/blog/static/7097176720118121573597/
摘要Facebook最近部署了Facebook Messages,这是它的首个构建于Apache Hadoop平台上的user-facing应用。使用构建于Hadoop之上的类数据库层Apach HBase来对每天数十亿的消息信息进行处理支持。这篇论文描述了Facebook在众多系统中(比如Apache Cassandra,Voldemort)最终选择了Hadoop和HBase的原因,并讨论了应用程序在一致性、可用性、分区容忍性、数据模型及可扩展性上的需求。我们还会探讨一下为了让Hadoop成为一个更有效的实时性系统所做的那些改进,在配置系统过程中所做的那些权衡,以及这种基于Hadoop的解决方案与Facebook和很多其他互联网公司在很多应用程序中采用的那种分片(sharded)MySQL数据库模式相比所具有的优势。我们还会讨论各种设计选择的背后动机,我们在日常工作中面临的挑战,一些未来的还在开发中的功能和改进。我们提供的这些关于部署中的各种观点可以为那些正在考虑使用基于Hadoop的解决方案取代传统分片RDBMS部署的公司,提供一种参考性的模型。
关键词
数据 可扩展性 资源共享 分布式文件系统 Hadoop Hive HBase Facebook Scribe 日志聚合 分布式系统
1.导引
Apache Hadoop是一个顶级的Apache开源项目,它包含一个源于Google GFS和MapReduce的分布式文件系统和MapReduce的开源实现。整个Hadoop生态系统还包括像Apache HBase(源于Google BigTable),Apache Hive(一个构建在Hadoop之上的数据仓库)及Apache Zookeeper(一个用于分布式系统的协商服务)这样的一些项目。
在Facebook,Hadoop早已与Hive结合在一块,进行海量数据集的存储和分析。大部分的分析工作都是离线的批处理式job,侧重点在于最大化吞吐量和效率。典型的工作负载就是从磁盘上顺序读写大量数据。如此一来,对于通过提供对HDFS的低延迟访问而使得Hadoop能够更好的支持随机访问类型的负载缺乏重视。事实上,目前我们是通过将一堆的MySQL数据库集群和使用memcached构建的缓冲层结合起来解决这些问题。很多情况下,来自Hadoop的结果会被上传到MySQL或者是memcached为web层提供服务。
最近,那些需要高度写吞吐率和廉价弹性存储的新一代应用程序在Facebook逐渐兴起,这些应用程序同时还要求低延迟以及高效的硬盘顺序和随机读性能。众所周知MySQL存储引擎具有非常好的随机读性能,但是只能提供非常低的随机写吞吐率。同时很难在保证良好的负载平衡及高的持续运行时间的情况下,对MySQL进行快速的垂直扩展{!scale up和scale out,水平扩展和垂直扩展,Scale Out(水平扩展):根据需求增加服务器,依靠多部服务器协同运算,借负载平衡及容错等功能来提高运算能力及可靠度。Scale Up(垂直扩展):升级服务器以增加处理器等运算资源进行升级以获得对应用性能的要求。}。对于MySQL集群的管理需要相对较高的管理开销,同时它们通常需要使用更昂贵的硬件。基于对HDFS的可靠性和可扩展性的高度自信,我们开始探索让这样的应用程序采用Hadoop和HBase。
基本上我们可以将这些应用程序划分为两类。第一种应用程序集需要实时的并发性,对存储在HDFS上的非常大的实时性数据流进行顺序读访问。比如Scribe(由Facebook创建并广泛使用的一个开源的分布式日志聚合服务)就是一个生成和存储这种数据的实例系统。之前,由Scribe生成的数据会被存储在昂贵而难以管理的NFS服务器上。属于这种类型的应用还有Realtime Analytics和MySQl backups。我们已对HDFS进行了改进,使得它成为一个高性能低延迟的文件系统,通过它已能够减少这种昂贵的文件服务器的使用。
第二种非MapReduce Hadoop应用需要支持快速的随机查找,动态的为一个快速增长数据集建立索引。一个主要的例子就是Facebook Messages。Facebook Messages提供给每个用户一个facebook.com的电子邮件地址,负责所有的电子邮件、SMS以及两个人的或者是一组用户的聊天信息的展示,需要对用户信息的来源进行强力地控制管理,是Social Inbox的基础。此外,这个新的应用会被超过5亿的用户在茶余饭后使用,需要能够扩展到数PB的数据,同时具有严格的正常运行时间需求。我们决定为这个项目采用HBase。HBase实际上依赖于HDFS提供可扩展性、容错性存储,依赖于Zookeeper提供分布式一致性。
在下面的章节中,我们会对这些新型应用中的某些进行更详细的介绍,并说明我们决定采用Hadoop和HBase作为这些项目的通用基础技术的原因。我们会描述为了可以适应Facebook的工作负载、运营考虑以及达到在产品系统中的最佳实践,针对HDFS和HBase进行的具体改进。
2.工作负载类型
在做出是否采用一个特殊的软件系统以及是否从当前的基于MySQL架构上进行迁移的决定之前,我们仔细考察了一些具体应用,对于这应用来说现有解决方案可能会成为一个问题。这些应用可能会由于其工作负载具有非常高的写吞吐量、大规模的数据集、不可预测的增长率、或者某些在一个分片(sharded)RDBMS环境中很难做到或者次优的问题,而导致难以扩展。
2.1Facebook Messaging
最新版的Facebook Messaging整合了现有的e-mail,chat和SMS服务。除了保留了所有这些信息外,新的主题模型还要求为所有的参与者保存信息。作为应用服务器需求的一部分,每个用户将会被绑定到一个数据中心。
2.1.1高的写吞吐量
根据现有的数百万消息产生率及每天数十亿的即时消息量,每天需要导入的数据量是非常巨大的,同时还在持续的增长。各种非规范化的需求还会增加系统的实际写入量,因为每条消息实际中可能会被写入多次。
2.1.2Large Tables
作为产品需求的一部分,消息不能被删除除非用户显示的进行了该操作,这样每个mailbox将会无限增长。与大多数的通讯应用类似,只有那些最近的消息可能会被读取有限的几次,之后就很少会被读取。也就是说,绝大多数的内容都不会再从数据库中读出,但是它们必须是时刻可用的,同时还需要低延迟,因此很难直接进行归档。
存储用户的所有消息意味着我们需要一个包含了日益增长的主题列表和消息内容的按用户进行索引的数据库模式。对于随机写负载类型来说,伴随着表中行数的增加,MySQl这类系统的写性能会显著的降低。数目众多的新消息也意味着一个沉重的写操作负载,在这种类型的系统中这些写操作会被转换为大量的随机IO操作。
2.1.3数据迁移
新的Messaging产品最具挑战性的方面在于新的数据模型上。这意味着所有现有的消息需要被重新摆弄一遍,以适应新的主题模型,然后进行迁移。能够进行大规模scan、随机访问以及快速的大批量导入将会有助于减少将用户迁移到新系统上的时间开销。
2.2Facebook Insights
Facebook Insights提供给开发者和网站站长访问具有社会化插件、Facebook Pages、Facebook Ads的那些网站的Facebook activity相关的实时分析结果。
通过使用不具名数据,Facebook记录那些诸如广告收视次数、点击率、网站访问量这样的一些数据。这些分析可以帮助人们了解其他人是如何与网站内容进行交互,这样他们就可以对他们的服务进行优化。
域名和URL分析以前是通过我们的Hadoop和Hive以一种离线处理的方式周期性的生成。但是,这产生了比较糟糕的用户体验,因为结果可能需要花几个小时等数据处理完成时才可用。
2.2.1实时性Analytics
Insights团队希望他们的用户可以在几秒内而不是之前的几小时就能看到统计结果。这除了需要一个进行处理、聚合、事件保存的系统外,还需要为用户的请求提供一个大规模的,异步队列系统。这些系统都需要是容错的,并能够支持每秒上百万的事件。
2.2.2High Throughput Increments
为了支持现有的Insights功能,时间和基于人口特征的聚合是必要的。然而,这些聚合操作必须要保证是最新状态,因此它们的处理必须是不落地的(on the fly),一次一个事件,通过数字型计数器实现。在具有数百万的单一聚合器及数十亿的事件的情况下,意味着大量的计数器都会具有大量的针对它们的操作。
2.3Facebook Metrics System(ODS)
在Facebook,所有的硬件和软件会把统计信息传给一个称为ODS(Operations Data Store)的度量信息(metrics)收集系统。比如,我们可能会收集在给定的某个服务器或者一系列服务器上的CPU使用量,或者我们可能想追踪针对某个HBase集群的写操作数。对于每个或者一组节点,我们会追踪成百上千个不同的metrics,同时工程师可能希望以不同的粒度在时间轴上将它们绘出。该应用对于写吞吐量有很高的需求,现有的基于MySQL的系统在数据的resharding和进行表的扫描时存在很大的问题,时间会不断攀升。
2.3.1Automatic Sharding
大量的被索引的基于时间序列的写操作,以及不可预测的增长率使得一个分片的MySQL配置很难满足这些情况的处理。比如,一个给定的产品可能在很长的时间内只收集10个metrics,但是伴随着大规模的产品推出和发布,相同的产品可能会产生数千个metrics。对于现有的系统来说,一个MySQL服务器的负载可能会突然超出它所能提供的处理水平,这就迫使该产品的团队手动的把数据从这个服务器进行re-shard,以迁移到多个服务器上。
2.3.2近期数据的快速读取及表的扫描
对于metrics系统的绝大部分的读取都是针对那些最近的原始数据,但是所有的历史性数据也必须是可用的。最近写入的数据必须很快就是可见的,此外为了执行基于时间的汇总统计,整个数据集合也需要被周期性地扫描。
3.Why Hadoop And HBase
上面所描述的工作负载类型对存储系统的需求可以概括如下(排名不分先后):
l 弹性:我们需要能够在最小化开销及不停机的情况下,增加存储系统的容量。某些情况下我们希望可以快速的增加容量,然后系统可以自动的进行负载平衡同时能够利用起新的硬件。
l 高的写吞吐率:大部分应用会存储(可能还会进行索引)大量的数据,同时需要达到很高的写吞吐率。
l 在单个数据中心内的高效的低延迟的强一致性语义:一些重要的应用比如Messages需要在单数据中心内的强一致性。这个需求是直接由用户的期望体验决定的。比如,显示在用户主页上的未读消息个数,以及显示在收信框页面的消息在用户之间应该是一致的。现实来看,实现一个全局的分布式强一致性系统是很难的,但是一个至少能在单个数据中心内部提供这种强一致性的系统已经可以提供一种较好的用户体验。我们也意识到(不像其他的Facebook应用)Messages可以很简单的进行联合这就可以让一个用户限制在单个数据中心的服务的范围,这就使得单数据中心的强一致性成为Messages项目的关键需求。类似地,其他的项目比如实时日志聚合,也可以整个地部署在一个数据中心内,如果系统提供了强一致性保证就更容易进行编程。
l 高效的磁盘随机读:尽管应用级cache被广泛使用(要么通过内嵌的要么通过memcached),在Facebook的应用场景中,会存在大量无法命中cache的操作而需要访问后端的存储系统。MySQL可以高效的执行随机读操作,任何的新系统必须不能比它差。
l 高可用性及灾难恢复:我们的服务需要为用户提供一个高的正常运行时间,即使是面临一些计划或非计划的事件时(有计划的比如软件升级,硬件/容量扩容,非计划的比如硬件错误)。我们还需要能够容忍某个数据中心的失败并最小化数据丢失,同时能够在合理的时间窗口内通过另一个数据中心提供数据服务。
l 故障隔离性:长期运营MySQL数据库的经验表明,故障隔离性是至关重要的。各个数据库肯定会有down掉的情况,但是在这种情况发生时应该只影响到很少的一部分用户。类似地,在我们的Hadoop数据仓库使用中,单个的磁盘故障只会影响到一少部分的数据,同时系统可以很快地从这种故障中恢复。
l 原子性的读-改-写原语:原子性的increments和compare-and-swap API在构建无锁(lockless)并行程序中非常有用,而且也是底层存储系统必须要具备的。
l Range Scans:一些应用需要能够高效地检索在特殊边界中的行集合。比如,针对给定用户的最新的100条消息,或者是给定的某广告客户在最近的24小时的每小时广告投放次数。
指出那些non-requirements也是很有必要的:
n 单数据中心内的网络分区容忍性:不同的系统组件通常具有一些固有的中心化。比如,所有的MySQL服务器会被放置在一些机柜之内,数据中心内的网络分区(network partition)可能会导致其中的大部分都丧失服务能力。因此我们是通过在硬件级别上通过高度冗余的网络设计尽可能地降低这种事件发生的可能性。
n 在单个数据中心故障发生时的零downtime:根据我们的经验,尽管不是不可能的,但是这样的故障很少发生。在一个非理想的现实世界中,系统设计需要做出可以接受的各种折中选择,这个就是我们在这种事件很少发生的给定前提下做出的一个折中。
n 跨数据中心的active-active服务能力{!双工热备,即两个数据中心提供对等的数据服务能力,一个挂了还有另一个可以提供服务}:如前面提到的那样,我们可以方便地对跨越多个数据中心的用户数据进行组合(基于用户的位置)。通过使用一个靠近用户的应用级cache可以掩盖其中的延迟(当用户和data locality不匹配时会产生比较大的延迟)。
某些看起来不是那么明显的因素也会起到作用。我们会更倾向于那些对于Facebook来说已经具有产品经验或者内部开发经验的系统。{!学习一个新的系统是需要成本的,如果这些系统已经在Facebook使用或研究过,那么当它出了问题时就能更好更快的解决它}。在考虑开源项目时,社区力量也是一个重要的考虑因素。在构建和维护这些系统的工程投入给定的情况下,选择一个更通用的解决方案会更有意义(而不是为不同的工作负载重新改变架构和代码实现)。
经过大量的研究和实验之后,我们选择采用Hadoop和HBase作为我们的新一代应用程序的基础存储技术。这个决定是基于当前针对HBase的评估以及我们相信可以通过自己内部的开发解决它当前缺乏的features。HBase已经提供了一个高一致性的,高写吞吐率的key-value存储。HDFS NameNode存在一个突出的单点失败问题,但是我们相信我们的HDFS团队可以在合理的时间窗口内构建一个高可用的NameNode,这对于我们的数据仓库应用也是有益的。好的磁盘读效率看起来也很容易达到(为HBase的LSM Tree实现添加Bloom filter,优化local的DataNode读取,缓存NameNode元数据)。基于我们在Hive/Hadoop数据仓库上的经验,我们认为HDFS是对磁盘子系统上的故障进行容错和隔离的关键。在大规模的HBase/HDFS集群中出现的失败与我们的故障隔离目标背道而驰,但是它可以通过将数据存储在较小规模的集群上得到明显的缓解。各种replication项目,包括我们自己及整个HBase社区内部,看起来这将会为灾难恢复提供可行的方案。
HBase具有高度的扩展性,除了随机和流式读操作还可以支持快速的随机写。它也提供了一个行级别的原子性保证,但是没有原生的跨行事务支持。从数据模型角度上来看,列式存储在数据存储上提供了高度的灵活性,wide row{!当在传统的关系数据库中设计table时,典型用“entities(实体)”处理,或一系列描述性的属性。对于row自身的长度无需考虑过多,因为一旦定义了你的table有哪些列组成,row的长度就是确定的了。而一个wide row意味着一条记录有很多columns(甚至可以是数以百万的)}使得可以在一个table里创建数10亿的indexed value。HBase特别适合于那些写密集型的,需要维护大量数据的,大量索引的工作负载,同时保持了快速进行水平扩展的灵活性。
4.实时性HDFS
HDFS是一个最初设计用于支持离线MapReduce应用的文件系统,作为一种批处理系统,在这种情况下,可扩展性和streaming处理性能才是最重要的。使用HDFS有如下优点:线性的可扩展能力及容错性,可以为公司节省大量花费。那些新式的、更实时性及在线的HDFS应用提出了新的需求,目前我们使用HDFS作为一个通用的低延迟文件系统。在本节内,我们会描述下为支持这些新型的应用我们对HDFS进行的核心改动。
4.1高可用性-AvatarNode
HDFS的设计中有一个中央master--the NameNode。当master down掉的时候,HDFS集群必须等到NameNode恢复后才可用。这是一个明显的单点失败,也是为什么人们很难将它部署在一个需要7×24小时运行的应用中的原因之一。在我们的使用过程里,发现软件的升级是我们的HDFS集群停机的首要原因。因为硬件并非是完全的不可靠,而软件在部署到生产集群上之前都进行了严格地测试,在我们管理HDFS集群的四年时间里,只碰到过一次例外,那次是因为事务日志被存储到一个已损坏的文件系统中导致了NameNode的crash。
4.1.1热备份(Hot Standby)-AvatarNode
启动时,HDFS NameNode会从一个叫做fsimage的文件中读取文件系统元数据。元数据包含了HDFS中的每个文件和目录的名称和元数据。然而,NameNode并没有持久化存储每个block的位置信息。因此,一个NameNode的冷启动由两个主要过程组成:首先,读取文件系统image,applying事务日志,将新的文件系统image存回磁盘;其次,处理来自DataNode的block报告以恢复集群中的block的位置信息。我们最大的HDFS集群大概有150 million个文件,我们观察到这两个阶段大概花了相同的时间。总的算下来,一次冷启动花了大概45分钟。
Apache HDFS中提供的BackupNode可以避免在故障恢复的时候从磁盘中读取fsimage,但是它仍然需要从所有的DataNode那收集block报告。因此BackupNode解决方案的故障恢复时间仍可能会高达20分钟。我们的目标是在数秒内进行故障恢复;因此,BackupNode解决方案无法满足我们对于快速的故障恢复的需求。另一个问题是,NameNode在每个事务发生时都需要同步地更新BackupNode,因此整个系统的可靠性甚至低于单个NameNode时的可靠性。于是,HDFS AvatarNode诞生了。
{!Avatar,这里的Avatar应该取自2009年在美国上映的影片<<Avatar>>(阿凡达)。Avatar本意是化身,影片中的杰克化身为纳美人,穿行于美丽的潘多拉星球,他具有人类与纳美人两种身份,但是同一时刻只有一个是active的。下面的AvatarNodes,其中一个Node便可以看成是另一个的化身,同时同一时刻只有一个是active的,与电影相比的确有些相通的地方。}
一个HDFS集群有两个AvatarNode:Active AvatarNode与Standby AvatarNode。它们形成了一个主从热备组合。一个AvatarNode是对一个普通的NameNode的包装。Facebook所有的HDFS集群都是采用NFS来存储一个文件系统image的拷贝以及一个事务日志的拷贝。那个Active的 AvatarNode会将它的事务写入到保存在NFS文件系统上的事务日志中。与此同时,Standby 的AvatarNode会打开同一个事务日志文件从NFS文件系统上开始读取,同时开始将事务应用到自己的namespace中,来保证它的namespace尽可能地接近于primary。Standby 的AvatarNode也负责primary的check-pointing,以及创建新的文件系统image,这样就不再存在一个独立的SecondaryNameNode。{?SecondaryNameNode又是什么呢?SecondaryNameNode实际上是在hadoop-0.21.0之前才有的,到了0.21.0后SecondaryNameNode已被CheckpointNode和BackupNode取代。首先来看SecondaryNameNode存在的原因,NameNode在启动时会读取fsimage恢复内存状态然后重放修改日志文件中记录的修改,之后再将新的内存状态写入到fsimage中,并产生一个新的空的修改日志。由于NameNode只会在启动时才会对fsimage和修改日志进行merge,这样运行很长时间后,修改日志会变得很大,这样再NameNode下次重启时将会花费很长时间进行merge。SecondaryNameNode的目的就是通过周期性的merge,使得修改日志可以保持在一个较小的规模上。更具体细节可以参见Hadoop的官方文档中的说明。Standby 是如何处理primary的check-pointing的?Standby实际上就可以充当SecondaryNameNode的角色,进行check-pointing}
DataNode与Active AvatarNode和Standby AvatarNode都会进行通信,而不是只跟单个NameNode通信。这意味着Standby AvatarNode也具有最新的关于block的位置信息,这样在一分钟内就可以顺利地成为Active的。Avatar DataNode会向这两个AvatarNodes发送心跳,块报告,以及接收到的block。AvatarDataNode会与Zookeeper进行交互,这样他们就能知道目前哪个AvatarNode是primary的,同时它们只处理那些来自primary的AvatarNode的replication/deletion命令。来自Standby AvatarNode的replication/deletion命令将会被忽略。
4.1.2针对HDFS 事务日志机制的改进
HDFS只有在文件关闭或者调用sync/flush时才会将新分配的block-ids记录到事务日志中。由于我们想让故障恢复尽可能地透明,那Standby AvatarNode就需要在故障发生时能够知道所有的block分配,因此我们在每次块分配时都向事务日志中写入一个新的事务。这就允许一个客户端可以向它在故障恢复之前正在写的那个文件继续进行写入。
在Standby AvatarNode从Active AvatarNode正在写入的那个事务日志中读取事务时,存在只读取到事务的一部分内容的可能性。为了解决这个问题,我们需要修改日志格式使它具有一个事务长度,事务id以及写入到文件中的每个事务的校验和。
4.1.3透明化的故障恢复:DAFS
我们开发了分布式的Avatar文件系统(DAFS),一个提供给客户端使之可以透明地跨越故障恢复事件访问HDFS的上层文件系统。DAFS与Zookeeper进行协作。Zookeeper持有一个包含了给定集群的关于Primary的AvatarNode的物理地址的zNode。当客户端尝试连接HDFS集群(比如 dfs.cluster.com)时,DAFS会查看Zookeeper中持有实际Primary AvatarNode(dfs-0. cluster.com)的物理地址的zNode,并将所有的后续调用指向该Primary AvatarNode。当一个调用碰到一个网络错误时,DAFS会检查Zookeeper看primary是否发生了改变。假设现在发生了一个故障恢复事件,那么zNode现在应该包含新的Primary AvatarNode的物理地址。DAFS会向这个新的Primary节点重试当前调用。我们没有使用Zookeeper订阅模型,因为它会占用Zookeeper服务器上更多的资源。如果一个故障恢复正在进行,那么DAFS会自动阻塞直到故障恢复完成。这样,一个故障恢复事件对于那些访问HDFS数据的应用来说就是完全透明的。
4.2Hadoop RPC兼容性
从一开始,就很清楚我们会为我们的Message应用运行多个Hadoop集群。我们需要那种能够在不同的时间点在不同的集群上部署新软件的能力。这就需要我们改进Hadoop Client,使得它们能够同运行了不同版本的Hadoop软件的Hadoop服务器进行交互。同一个集群内部的不同服务器运行的是相同版本的软件。我们增强了Hadoop RPC软件来自动确定运行在它所通信的服务器上的软件版本,然后在与服务器会话时选择合适的协议。
4.3块可用性:Placement策略
默认的HDFS块放置策略,虽然是机柜感知的,但限制仍然是最小化的。对于一个非local的放置决定是随机的,它可以被放置在任意的机柜上,以及机柜内的任意节点上。为了降低在多个节点同时出错时的数据丢失概率,我们实现了一个可插拔的块放置策略:将块副本的放置限制在比较小的,可配置的节点组内。这就使得我们如果选择合适的组大小,就可以将数据丢失概率降低几个数量级。我们的策略是使用一个逻辑的机柜环,每个机柜又包含一系列的机器,为原始的block确定一个可放置的机柜和机器窗口。更细节地内容比如用于计算这些数字的数学函数和脚本可以参考HDFS-1094。我们发现随着节点组大小的上升,块丢失的概率会也会增加。在我们的集群中,我们使用一个(2,5)的节点组,即机柜的窗口大小是2,机器的窗口大小是5。我们这样选择的原因是此时的数据丢失概率大概比默认的块放置策略小了100倍。
4.4针对实时性负载的性能改进
HDFS最初是为像MapReduce这样的高吞吐率系统设计的。它的很多原始设计都是为了提高吞吐率而不是着重于响应时间。比如,在处理错误时,它喜欢进行重试或者进行等待。为了支持实时性应用,在出错的情况下提供合理的响应时间成为HDFS面临的主要挑战。
4.4.1RPC超时
一个例子是Hadoop如何处理RPC超时。Hadoop使用TCP发送Hadoop-RPCs。当一个RPC客户端检测到一个tcp-socket超时时,不是直接声明一个RPC超时,而是向RPC服务器发送一个ping请求。如果该服务器仍然是活动的,客户端会继续等待响应。这样做的出发点是,一个RPC服务器可能正在经历一个通信风暴、一个临时的高负载、GC产生的停顿,客户端应该等待同时减少到发给服务器的流量。与之相反的,如果是抛出一个超时异常或者是重试该RPC请求可能会导致任务不必要地失败或者给RPC服务器增加额外的负载。
然而,无限等待会给那些具有实时性需求的应用带来负面的影响。一个HDFS客户端间或地向某个Datanode发起一个RPC请求,如果该Datanode不能及时响应就会很糟糕,该客户端会卡在该RPC调用上。一个更好的策略是快速的失败,然后为读或写操作尝试另一个DataNode。因此,我们在启动与服务器的RPC调用时,提供一个可以指定PRC超时时间的设置选项。
4.4.2Recover File Lease
另一个改进是可以快速撤销写者租约。HDFS支持一个文件只有一个写者,NameNode会维护一个租约来保证该语义。存在很多情况,一个应用程序想打开一个之前未被干净地关闭地文件进行读取{?租约会阻塞读者?那么下文中的并发读者又是如何处理的呢,或者说能否用并发读者解决这个问题?是否是笔误呢,感觉读取应该换成写入,根据HDFS最新的论文内容,实际上HDFS是支持对于一个正在写入的文件的读取的。也可能针对的是不同版本。}在此之前,可以通过重复地在该文件上调用HDFS-append直到成功来实现地{!即可以通过append空内容来首先使它的软租约过期,之后就可以读取了。关于这点也有疑问。参见HDFS-1142 ,HADOOP-1700 }。Append操作会触发文件的软租约过期。这样应用程序在HDFS NameNode释放该文件租约前,就必须要等待一个最小的软租约过期周期(默认是一分钟)。其次,HDFS-append操作引入了一些不必要的开销,比如建立write pipeline通常涉及到不止一个地DataNode。当错误发生时,一个pipeline的建立可能会高达10分钟。
为了避免HDFS-append开销,我们增加了一个轻量级的HDFS API调用recoverLease,通过它可以显式地释放一个文件的租约。当NameNode接受到一个recoverLease请求,它会立即将文件的租约持有者改变成它自己。然后开始租约恢复过程。recoverLease rpc返回一个状态值表示租约恢复是否成功。应用程序在尝试读取文件之前需要等待来自recoverLease成功的返回码。
4.4.3本地副本读取
有时应用程序可能会因为扩展性和性能的原因而想把数据存储在HDFS上。然而,一个HDFS文件上的读写会比本地文件的读写具有数量级上的高延迟。为了缓和这个问题,我们对HDFS客户端进行了一个改进,如果检测到本地包含数据的一个副本,就直接对本地副本进行读取而不再通过DataNode进行传输。这个改进为使用HBase的某个工作负载带来了双倍的性能提升。
4.5新Features
4.5.1HDFS sync
Hflush/sync对于HBase和Scribe来说都是一个重要的操作。它将缓存在客户端的写入数据推送到write pipeline中。使得数据对于新的读者都是可见地,同时增强了在客户端或者pipeline中的DataNode出错时的数据持久性。Hflush/sync是一个同步性的操作,这就意味着如果没有收到write pipeline的接收确认它就不会返回。由于该操作被频繁调用,因此提高它的效率就是至关重要的。我们已经进行的一个优化是,在Hflush/sync操作等待响应地同时允许后面的写操作继续进行。这就大大提高了在HBase和Scribe中当某个特定线程周期性调用Hflush/sync时的写吞吐率。
4.5.2并发读者
有些应用需要能够读那些正在被写入的文件的能力。读者首先与NameNode通信得到文件的元数据信息。由于NameNode没有该文件的最后那个block的长度的最新信息,客户端可以从持有其中的一个副本的DataNode处获取该信息。然后它开始读取该文件。并发读者和写者的挑战之处在于如何提供最后一个chunk的数据,因为它的数据内容和校验和是动态变化的。我们通过按需重新计算最后一个chunk数据的校验和来解决该问题。
5.HBase产品化(Production HBase)
在本节中,我们会描述我们在Facebook的对HBase进行的某些重要改进,这些改进主要涉及到正确性、持久性、可用性及性能。
5.1ACID保证
应用开发者会期望它们的数据库系统能提供ACID保证,或者是某些近似于它的保证。事实上,强一致性保证也是我们对HBase的早期评估中,所认为的一个优势之处。现有的类MVCC的读写一致性控制(RWCC)提供了足够的隔离性保证,同时HDFS的HLog(write ahead log)提供了足够的持久性保证。然而,还需要进行一些改动以保证HBase可以实现我们需要的ACID保证中的行级别的原子性及一致性。
5.1.1原子性
第一步就是需要保证行级别的原子性。RWCC提供了大部分的保证,然而在节点失败的情况下有可能丧失这些保证。最初,一个单行事务中的多个entires会被按顺序写入HLog。如果一个RegionServer在写入时挂掉,这个事务就有可能只被部分地写入了。通过使用一种新的日志事务(WALEdit),可以保证每个写入事务要么完整完成要么根本不写入。
5.1.2一致性
HDFS为HBase提供备份机制,因此针对我们的应用的HBase的大多数强一致性需求也是由它处理的。在写的时候,HDFS建立一个连接每个副本的pipeline,所有的副本必须对发送给它的数据进行确认。在得到一个响应或者失败通知之前HBase不会进行下一步的操作。通过使用序列号,NameNode可以识别出任何行为异常的副本同时排除掉它们。在方便的时候,NameNode会花一些时间进行文件的恢复。对于HLog来说,在它不断增长的情况下维护它的一致性和持久性是绝对必需的,一旦检测到即使仅是一个HDFS副本写入失败HBase也必须立即获取新的blocks重新进行写入。
HDFS也针对数据损坏提供一些保护。在读取一个HDFS block时,会执行校验和验证,在校验失败时,整个block会被丢弃。数据丢弃很少会成为问题,因为对于这份数据还有其他两个副本。需要添加一些额外的功能来保证当所有的3份副本都包含损坏数据时,这些blocks依然可以被用于事后的检查分析{!不能被简单的删除丢弃,应该保留它们并记录一些必要的信息,以保证事后能够方便地找到它们进行分析}。
5.2可用性改进
5.2.1重写HBase Master
在通过kill testing进行HBase regions下线测试时,我们发现了很多问题。很快我们意识到问题的根源:整个集群的瞬时状态仅仅保存在了HBase master的内存中。在失去该master的同时,这个状态也丢失了。于是,我们着手去进行HBase master的重写工作。此次重写的关键部分在于将region分配信息从master的内存状态中移到Zookeeper中。因为Zookeeper会至少写入到半数以上的节点中,这样这个瞬时状态在master发生故障恢复时也不会丢失,同时也允许多个服务器的失败。
5.2.2在线升级
导致集群停机的最大原因不是随机的服务器失败,而是系统维护。为最小化停机时间我们碰到了很多问题。
首先,随着时间的推移RegionServers在一个停机请求产生后间歇性地出现需要几分钟才能关闭的情况。这种间歇性的问题是由长的compaction周期造成的。为了解决这个问题,我们让compaction变成可中断的,这样就能及时结束然后做出响应。这使得RegionServers的停机时间降低到秒级别,同时对于整个集群的停机时间给出了一个合理可接受的上界。
另一个可用性改进是滚动式重启。最初,HBase只能停下整个集群然后启动升级。我们添加了滚动式的重启脚本一次对一个服务器执行软件升级。因为master可以在某个RegionServer停机的情况下自动地重新分配它上面的regions,这就可以用来最小化用户能感受到的停机时间。顺便提一下,在滚动式重启过程中,发现很多与region离线及重分配相关的bug,而我们因与Zookeeper集成进行的master重写帮助解决了这里的很多问题。
5.2.3分布式的日志切分
当一个RegionServer挂掉的时候,它上面的regions在可以被重新打开及读写之前,该server上的HLog必须被切分然后进行replay。在日志在剩余的RegionServers上replay之前,会由Master负责对日志的切分。这是整个恢复过程中最慢的一部分,因为每个server通常有很多的HLog文件,但实际上它是可以并行进行的。利用Zookeeper进行管理,可以在多个RegionServers进行切分,master现在只是负责协调一个分布式的日志切分任务。这将恢复时间降低了一个数量级,同时也允许RegionServers可以保留更多的HLogs而不用担心会影响故障恢复的性能。
5.3性能改进
HBase中的数据插入针对写性能进行了专门优化,通过以可能的冗余读为代价将写操作转化为顺序写。一个数据事务会首先被写入一个commit log中,然后应用到一个称为MemStore的内存cache中。当MemStore达到一定阈值后,会以HFile的格式写出去。HFile是不可变的HDFS文件,内部包含一系列有序的key-value对。不是修改现有的HFile,而是在每次flush时写出新的HFile并且将其加入到以region为单位的列表中。读请求需要并行地在多个HFile上进行,通过对它们进行归并聚合得到最终结果。为了提高效率,这些HFile需要进行周期性地compact,或者归并到一块以减少读性能的降低。
5.3.1Compaction
读性能与一个region内的文件数相关,因此关键在于要有一个良好优化的compaction算法。更严重的是,如果compaction算法没有进行合理的调整,网络IO性能也会受到严重的影响。最重要的是我们需要保证为我们的使用场景选择了一个有效的compaction算法。{!HBase最新的compaction算法可以参考HBASE-3209}
最初的compactions根据它们是minor还是major分成了两个独立的代码路径。Minor compactions会基于大小选择所有文件中的一个子集进行,而基于时间的major compactions则无条件地对所有文件进行compactions。在此之前,只有major compactions会处理删除、覆盖,进行过期数据的清洗,这就意味着minor compactions会使得HFile具有不必要地大小,这会降低block cache的效率,同时也会影响未来的compactions效率。通过整合代码路径,实现了代码的简化,同时也让文件尽量地小。
下一个任务就是要提高compactions算法性能了。在发布之后,我们注意到put和sync延迟非常高。我们发现存在一种异常情况,在这种情况下,一个1GB的文件会与其他3个5MB的文件进行合然后生成了一个稍微大些的文件。实际上着浪费了大量的网络IO。该问题的产生是由于现有的compactions算法在3个HFile达到minor compactions的触发条件后,会无条件地对前4个HFile进行minor compactions。解决方案就是对那些达到一定大小的文件停止这种无条件地compacting,同时在候选文件不足时跳过compaction。之后,我们的put延迟从25毫秒降到了3毫秒。
我们也在致力于改进决定是否启动compaction算法ratio参数的大小{!HBase中有个参数hbase.hstore.compaction.ratio 用于决定某文件是否进行compaction}。最初的时候,compaction算法会根据文件年龄从老到新进行排序,然后比较相邻的文件。如果较老的那个文件小于较新的那个文件的2倍大小,那么compaction算法就会将该文件包含在内,然后继续迭代。然而,该算法在HFile文件数目和大小增长很快的情况下表现出次优的行为{!在这种情况下,新产生的文件的大小可能都是差不多的,这样它们可能就没法满足上面的compaction条件,而无法参与compaction}。作为改进,如果某个文件大小,小于比它新的所有文件的总大小的2倍,我们就把它包括进来。这可能产生一种不平稳的状态,因为一个老的文件可能会是下一个比它新的文件的4倍,因此即使是维持在50%的compaction率的情况下,仍可能得到一个比较陡峭的compaction执行曲线。
5.3.2读操作优化
正如讨论的那样,读操作性能需要通过保持region内的文件数在一个较低的水平上来降低随机的IO操作。除了利用compaction来保证磁盘上的文件数,对于某些查询来说跳过某些特定的文件也是可能的,这也能减少IO操作。
Bloom filters提供了一种具有高效地空间利用率及常数级时间开销的方法,来检查某个给定的行或者行列是否存在于某给定的HFile中。因为每个HFile的那个元数据块(可选的)是被顺序地写入到文件尾部,额外的bloom filters可以很容易地添加到里面而无需太大的变更。通过在写回磁盘及缓存在内存的时候使用folding{!folding本意是折叠,实际上是对bloom filter所占用的空间进行的一种优化,比如我们可以把一个具有2N个bit的bloom filter折叠成N个bit的,只需要按位进行或操作,同时在判断时注意做一个转换,就可以了。这样我们就可以比如看看当前的bloom filter的位使用状况是否很稀疏,如果很稀疏我们完全可以进行折叠以降低它占用的空间。具体实现可以参考ByteBloomFilter.java},可以让每个bloom filter的空间开销尽量地小。对于那些特定行或/和列的查询,通过检查每个HFile的bloom filter就可以完全地跳过那些不包含它们的那些文件。
对于存储在HBase的某些基于时间序列或者包含一个特定的已知时间戳的数据来说,可以添加一个特殊的时间戳文件选择算法。因为时间总是在向前流动,因此数据的插入时间通常要比它的时间戳还要晚,每个HFile将会自动生成那些处于某个固定的时间间隔内的值。这些信息可以保存在HFile的元数据中,在进行特定的时间戳或者时间区间内的查询时,就可以检查文件的时间窗口与之是否相交,就可以直接跳过那些与之没有重叠区间的文件。
通过HDFS 本地文件读显著地提高了读操作性能,因此让那些regions驻留在它们的文件本身所在的物理节点上是至关重要的。我们已经进行了一些改进来保证这种集群上的region的分配策略,同时在节点重启时也尽量去维护这种locality。
6.部署及运维经验
在过去的这些年里,我们从最初运行的一个具有10节点的HBase测试集群到很多运行着数千个节点的集群。这些部署已经为数百万的用户提供实时在线的产品服务。在此期间,我们也对核心软件(HBase/HDFS)及运行在HBase之上的应用程序逻辑进行了快速地迭代改进。在这样一个充满流动性的环境中,我们的驾驭高质量软件、正确地部署、运行系统监控以及检测异常和在最小停机时间下进行fix的能力,都是至关重要的。这一节我们会深入到我们在这些演化过程中的实践经验及相关工具。
6.1测试
在我们最初设计HBase解决方案时,就担心过代码的稳定性。我们首先需要测试开源代码的稳定性和耐用性,以保证我们未来进行变更时的稳定性。最终,我们写了一个HBase测试程序。该测试程序可以为HBase写入生成确定性的或者是随机性的数据。该测试程序会将数据写入到HBase集群以及并行地读取及验证它所添加的数据。我们还会继续对该测试程序进行改进以支持在集群中随机选择以及kill掉进程,验证那些成功返回的数据库事务是否已被真地写入。这帮助我们发现了很多问题,也是我们测试变更的首要方法。
尽管我们的集群环境由很多具有分布式行为模式的服务器组成,我们的local开发验证环境通常是由单元测试和单机版环境组成。我们会关注那些在单机版与真实的集群环境中不一致的地方。我们建立了一个称为HBase Verify的工具来在单个服务器上运行简单的CRUD{!即增删改查,create、retrieve、update、delete}工作负载。这使得我们在几分钟内就可以执行一些简单的API调用实验及运行一些负载测试。这个工具甚至对于我们的dark launch集群(算法在这些集群上首次进行大规模的评估)来说都是非常重要的。
6.2监控及工具
当我们具有了很多的HBase产品化使用经验之后,很明显地我们面临的首要问题是在对regions的分配上RegionServers间的产生不一致性。两个RegionServers最终可能会负责同一个region的服务,或者是某个region可能会处于未分配的状态。这些问题是由存储在不同位置上的,关于regions状态的元数据的不一致性导致的:存储在HBase以及ZooKeeper的META region,存储在HDFS上以及RegionServers的内存中的region对应的那些文件。尽管很多的这类问题都可以系统化地解决,同时可以作为HBase Master重写(见5.2.1节)的一部分进行进一步的测试,我们仍然担心某些在产品环境下暴露出的边界问题。最终,我们建立了HBCK作为一种验证这些不同的元数据来源的一致性的数据库级的FSCK{!FileSystemCheck,在linux中fsck是一种检查文件系统一致性的工具}工具。对于普通的不一致性,我们添加一个HBCK ‘fix’配置项来清空内存状态,让HMaster来重新分配不一致的region。目前,我们几乎每天都在我们的生产机器上持续运行HBCK以尽快地发现问题。
对于集群监控来说,一个很重要的组件就是操作指标(operational metrics)。尤其是与HMaster和Zookeeper metrics相比,RegionServer metrics对于评估集群健康状况更有用处。HBase已经通过JMX导出了大量的metrics。然而这些metrics基本上都是面向短期运行的那些操作比如日志写,RPC请求。我们需要添加一些长期运行的一些事件比如compactions,flushes,log splits。另一个比较关键的监控信息就是版本信息。通常我们的多个集群具有不同的版本。在一个集群出现了crash时,我们需要知道它所特有的那些功能。而且滚动式的更新会导致运行中的版本跟已安装的版本并不一定是相同的。因此我们需要记住这两个版本,以及他们出现差异的时间。
6.3手动与自动化Splitting
在学习一个新系统时,我们需要确定哪些features是我们需要立即使用的,哪些features是可以选择不采用的。HBase提供了一个称为自动化splitting的feature,它会在一个region增大到一定程度时将它划分成2个regions。我们认为对于我们的使用场景来说,自动化splitting是可以不要的,同时我们开发一个手动的splitting工具代替它。在table创建时,我们提前将table分割成给定数目的等大小的regions。在平均region大小变得太大时,我们可以启动针对这些regions的滚动式的splits。我们发现这种协议带来了很多好处。
因为我们的regions基本上都是均匀地增长的,因此对于自动化的splitting来说很容易引起split和compaction风暴,因为所有的regions很可能在同一时间达到相同的数据大小。通过手动的splits,我们可以将splits操作交错安排到不同的时间里,这样也可以将由splitting进程产生的网络IO负载平摊开来。这就最小化了对产品工作负载的影响。
因为在任何给定的时间点上,regions的数目都是可知的,这样长期的debugging和profiling就很简单。因为如果regions一直在进行splitting和重命名,就很难通过追踪日志来理解那些region层面的问题。
在我们最初开始使用HBase时,我们曾经碰过过一些日志恢复方面的问题,在region故障恢复时,某些日志文件可能会被落下而处在未处理的状态。如果regions在那时起没有进行过分裂,那么对这种异常的事件事后进行手动的恢复就会很简单。我们可以找到受影响的region,然后replay未被处理的日志。在此,我们假设HDFS的垃圾回收机制会为那些已删除的文件保留一定的时间周期。
有一个比较明显的问题:手动的splitting有没有使得HBase丧失某些重要的优势呢?HBase的其中一个优势就是splitting是逻辑层面地而不是物理地。底层的共享存储(HDFS)允许不进行大量数据的移动和拷贝就可以实现regions的重分配。因此,在HBase里,降低负载的简单方式不是创建更多的regions而是向集群中添加更多的机器。Master会自动地将现有的regions重新分配给那些新的RegionServers而不需要手动地介入。此外,自动化的splitting对于那些不具有均匀分布的应用程序来说很有意义,我们计划在未来为这些应用程序使用该特性。
6.4Dark Launch
从一个现有的消息系统中进行迁移,具有一个很大的优势:在真实环境中测试的能力。在Facebook,广泛采用一种称为”Dark Launch”的测试/部署过程,在这里,在不改变任何UI的情况下,一些关键的后端功能会暴露给某一部分的用户使用。我们通过使用这种机制来为某些用户写两份消息流量,一份给老的架构,一份给HBase。这使得我们可以进行一些有用的性能基准测试,发现HBase的瓶颈,取代那种单纯的仿真测试及估量方式。即使是在产品发布之后,我们仍然可能会使用Dark Launch集群。所有的代码变更在考虑进行上线之前都会首先在Dark Launch集群上运行一周。此外,Dark Launch通常会处理我们的生产集群期望处理的负载规模的2倍。在2倍负载上的长期测试,使得我们可以经受住某些流量突变的情况,同时也可以在进行垂直扩展前帮助验证HBase能处理某些异常的峰值条件。
6.5Dashboards/ODS集成
我们现在已经有了JMX提供的那些metrics,但是我们还需要一种将这些metrics进行可视化的简单方式,以及按时间查看集群的健康状况。我们决定使用ODS,一个类似于Ganglia的内部工具,用于将一些重要的metrics作成线图进行可视化。每个集群都有一个展示面板,内含针对各种平均值及异常值的可视化图形。绘出最大/最小值是非常重要的,因为它们通常标识出了具有异常行为的RegionServers,它们可能导致应用服务器处理队列产生拥塞。最大的好处是可以实时地观察统计信息,这样就可以观察集群对不同的工作负载的响应情况(比如运行一个Hadoop MapReduce job或者是进行regions的splitting)。
此外,我们还提供两个跨集群的展示面板,用于更高级的分析。我们将所有集群的关键统计系统放到一个概览性的展示面板上来提供更宽广的状态视图。在这个展示面板上,我们还会展示四个针对HBase的图:Get/Put延迟,Get/Put次数,每Store的文件数,Compaction队列大小。同时我们也会尽量的展示出集群版本的差异。我们会在四张图上展示每个集群的HMaster版本,HDFS Client版本,NameNode版本,JobTracter版本。这允许我们可以方便的查看版本,找到那些可能存在已知bug的版本。
6.6应用层备份
如何在这种大规模数据集上进行常规备份?一种选择是将数据从一个HDFS集群上拷贝到另一个上去。因为这种策略不是持续进行的,因此在下一次备份事件发生时HDFS上的数据可能已经损坏了。很明显这是不可接受的。我们决定改进应用程序来连续生成一个可选的应用程序日志。该日志会通过Scribe传输并保存到一个用于web分析的独立HDFS集群上去。这是一个可靠的久经考验的数据获取pipeling,特别是我们已经采用这种方式来获取我们的web应用产生的大量click-log数据并传送到Hive存储分析系统。在该应用程序日志里的记录都是幂等的,可以被重复执行多次而不会导致数据丢失。当HBase发生数据丢失事件时,我们就可以重放该日志流,然后在HBase里重新生成数据。
6.7Schema变更
HBase当前并不支持对一个现有的table在线进行schema变更。这意味着,如果我们想向现有的某个table中增加一个新的列族(column family),我们必须停止对table的访问,禁用该table,增加新的列族,将该table恢复上线,然后重启应用负载。这是一个很严重的缺点,因为通常我们都无法去直接停止我们的工作负载。相反的,我们会为我们的某些核心的HBase table预先创建一些额外的列族。当前应用程序可能不会向该列族内存储任何数据,但是可以在以后使用它们。
6.8数据导入
起初,我们采用一个Hadoop job通过常规的数据库put操作将Message的历史数据导入到HBase中。当put请求在服务器间传输时,该Hadoop job会使网络IO达到饱和状态。在进行alpha release时,我们发现这种方式会导致超过30分钟的严重延迟,因为导入数据与我们的实时流量是混杂在一块的。这种影响是不可接受的—我们需要一种既能够为数百万的用户导入数据同时又不能严重影响生产系统正常工作的方法。解决方法是采用一种使用压缩的大批量导入方法。这种大批量导入方法会使用一个map job将数据划分为多个regions,然后reducer直接将数据写成LZO压缩格式的HFiles。这样网络流量主要来源于针对map输出的shuffle过程。这个问题可以通过对map的中间输出进行GZIP压缩来解决。
6.9降低网络IO
当在产品系统中运行了两个月后,从dashboards中我们很快意识到我们的应用是网络IO密集型的。我们需要通过某些方法去分析我们的网络IO流量到底来源自哪些地方。我们将JMX统计功能与日志分析相结合来对一个RegionServer 24小时内的整体的网络IO进行评估。我们发现网络流量主要由三部分组成:MemStore flush(15%),基于文件大小的minor compactions(38%),基于时间的major compactions(47%)。通过观察这个比率,我们发现了很多快速优化方法。比如通过简单地将major compactions的周期从一天增加到一周,就降低了40%的网络IO。通过关闭某些列族的HLog操作,我们也得到了很大的提升。当然前提是存储在这些列族中的数据并不需要太高的持久性保证。
7.工作展望
关于Hadoop和HBase在Facebook的应用才刚刚开始,我们期望对这一套东西不断的进行迭代,并持续的针对我们的应用进行优化。为了让更多的应用来使用HBase,我们也在讨论为它增加二级索引维护及摘要视图这样的一些功能。在很多应用场景中,这样的一些派生数据和视图都可以异步地进行维护。很多应用场景可以通过在HBase的cache中存储大量数据来获益,当然对于HBase的这种改进会占用大量的物理内存。在这一块,目前还受限于Java内部Heap的实现方式,我们也正在评估几种解决方案,比如用Java实现一个slab分配器或者是通过JNI进行内存管理。一个相关的方向是使用flash memory来扩展HBase cache,目前我们也正在探索使用flash memory的各种方式比如FlashCache。最后,因为我们希望那些采用了跨数据中心的双工热备的应用也可以使用Hadoop和HBase,所以我们也在探索一些机制来进行多数据中心的replication和冲突解决。
8.致谢
我们当前的这个Hadoop实时架构是基于过去很多年的工作成果之上的。在此期间,Facebook的很多工作人员为这些系统做出了很多贡献和改进。我们要感谢Scott Chen和Ramkumar Vadalli为Hadoop所做出的很多改进,包括HDFS AvatarNode,HDFS RAID等等。还要感谢Andrew Ryan,Matthew Wetly和Paul Tuckfield在运维,监控,和统计上所做的工作。还要感谢Gautam Roy, Aron Rivin,Prakash Khemani 和 Zheng Shao对存储系统的各个方面的不断支持和改进。还要感谢Patrick Kling实习期间为HDFS HA实现的测试套件。最后一点也是非常重要的一点,我们要感谢我们的用户们,感谢他们对于系统演化中出现的不稳定性的耐心,以及各种有价值的反馈,正是这些使得我们可以不断地改进我们的系统。
9.参考文献
[1] Apache Hadoop. Available at http://hadoop.apache.org
[2] Apache HDFS. Available at http://hadoop.apache.org/hdfs
[3] Apache Hive. Available at http://hive.apache.org
[4] Apache HBase. Available at http://hbase.apache.org
[5] The Google File System.
http://labs.google.com/papers/gfs-sosp2003.pdf
[6] MapReduce: Simplified Data Processing on Large Clusters.
http://labs.google.com/papers/mapreduceosdi04.pdf
[7] BigTable: A Distributed Storage System for Structured Data.
http://labs.google.com/papers/bigtableosdi06.pdf
[8] ZooKeeper: Wait-free coordination for Internet-scale systems.
http://www.usenix.org/events/usenix10/tech/full_papers/Hunt.pdf
[9] Memcached.
http://en.wikipedia.org/wiki/Memcached
[10] Scribe. Available at http://github.com/facebook/scribe/wiki
[11] Building Realtime Insights.
http://www.facebook.com/note.php?note_id=10150103900258920
[12] Seligstein, Joel. 2010. Facebook Messages.
http://www.facebook.com/blog.php?post=452288242130
[13] Patrick O'Neil and Edward Cheng and Dieter Gawlick and
Elizabeth O'Neil. The Log-Structured Merge-Tree (LSMTree)
[14] HDFS-1094.
http://issues.apache.org/jira/browse/HDFS-1094.
[15] Facebook Chat.
https://www.facebook.com/note.php?note_id=14218138919
[16] Facebook has the world's largest Hadoop cluster!
http://hadoopblog.blogspot.com/2010/05/facebook-hasworlds-largest-hadoop.html
[17] Fsck. Available at http://en.wikipedia.org/wiki/Fsck
[18] FlashCache. Available at https://github.com/facebook/flashcache