【51CTO独家特稿】照片应用程序是Facebook最流行的功能。直至目前为止,Facebook的用户已经上传了超过150 万幅照片,这使得Facebook成为最大的照片共享网站。对于每一个上传的照片, Facebook生成并保存成4种不同大小的图像,即总共有60亿的图片占1.5PB的存储容量。目前的增长速度是每星期220万个新照片,即每周消耗 25TB的额外存储空间。在高峰期,平均每秒会上传550,000幅图像。这些数字给Facebook的照片存储基础架构带来了严重的挑战。
NFS照片基础架构
旧的照片基础架构包含几个层次:
◆上传层接收用户上传的照片,测量原始图像的大小并将其保存到NFS存储层。
◆照片服务层接收HTTP照片请求,并向用户提供保存于NFS存储层的照片。
◆NFS存储层建立于商业存储设备之上。
由于每个图像存储在自己的文件内,所以根据命名空间目录和文件inode(内节点),在存储层产生了大量的元数据。这些元数据量远远超过了NFS存 储层的缓存能力,导致了上传和读取每张照片时成倍的I/O操作。整个照片服务的基础架构由于NFS存储层的大量元数据负荷而成为了一个瓶颈,这就是 Facebook严重依赖CDNs来提供照片服务的原因之一。以下两个附加的优化部署,用来在一定程度上减轻这个问题:
Cachr :一个缓存服务层,用来缓存Facebook中较小的“个人资料”图像。
NFS文件句柄缓存——部署在照片服务层,消除了一些NFS存储级元数据负荷
Haystack照片基础架构
新的照片基础架构将照片服务层和存储层合并为一个物理层。它实现了一个基于HTTP的照片服务器,把照片存储在名为Haystack的通用对象中。 对于新层次的主要要求是消除任何照片读取操作的不必要的元数据开销,使每个读取I/O操作只是读取实际照片数据(而不是文件系统元数据)。 Haystack可划分为以下一些功能层-
◆HTTP服务器
◆照片存储
◆Haystack对象存储
◆文件系统
◆存储设备
以下各节中,我们会自底向上密切关注每一个功能层。
存储设备
Haystack部署于日常存储片之上。一个2U存储片的典型硬件配置的是-
◆2 x 4核CPUs
◆16GB – 32GB内存
◆具有256MB – 512MB NVRAM缓存的硬件RAID控制器
◆12+ 1TB SATA驱动器
每个存储片提供大约10TB的可用空间,配置为一个RAID-6分区,由硬件RAID控制器进行管理。RAID 6提供了足够的冗余性和出色的读取性能,可以降低存储成本。RAID控制器NVRAM回写高速缓存可以部分缓解低劣的写性能。由于读取大多是随机的,所以 NVRAM缓存完全保留给写操作。磁盘高速缓存被禁用,以保证在系统崩溃或电源断电时数据的一致性。
文件系统
Haystack对象存储实现于一个文件之上,该文件存储在一个单一文件系统上,该文件系统建立于10TB volume(卷)大小的空间之上。
照片读取请求导致read()系统调用请求读取文件中不同偏移量的信息,但为了执行读取操作,文件系统必须首先在实际物理卷上找到数据。在文件系统 中,每个文件的是由一个名为inode的结构所描述,该结构包含一个块映射,可以把逻辑文件偏移量映射到物理卷中的物理块偏离量。对于大文件,根据所使用 的文件系统类型的不同,块映射可能会相当庞大。
基于块的文件系统为每个逻辑块维护其映射信息,对于大文件,这些映射信息将不会像通常那样存入缓存的inode,而是储存在间接地址块,读取文件数据时需要进行转换。间接转化可能存在好几个层次,因此,根据间接地址块是否被缓存,单一的读取可能会导致若干个I/O操作。
基于范围的文件系统只为连续的块(区域)维护映射信息。对于一个连续大文件的块映射只由一个区域组成,此区域的大小正好可以装入inode之中。但 是,如果该文件是严重地分散和不连续的,其区块在卷中不连续,那么其块映射可以随之增长。有了基于范围的文件系统,就可以通过积极分配一大块空间来减少碎 片。
目前,所选择的文件系统是的XFS,基于范围的文件系统提供有效文件预分配。
Haystack对象存储
Haystack是一个简单日志结构(只追加)的对象存储,包含描述存储对象的指针。一个Haystack包括两个文件——实际的包含指针的Haystack存储文件,以及一个索引文件。下图显示了Haystack存储文件的结构布局:
第一个8KB的Haystack存储由超级块所占用。紧接着超级块的是指针,每个指针由页眉、数据、和页脚组成。
一个指针是由其﹤Offset(偏移量), Key, Alternate Key(替换键),Cookie﹥元组唯一确定,其中偏移量是指在Haystack存储中的指针偏移量。Haystack对于关键字的值没有任何限制,有 的指针可以有多个关键字。下图显示的是索引文件的结构布局—
在Haystack存储文件中,每个指针有一个相应的索引纪录,而且指针索引纪录的顺序必须与Haystack存储文件中相关的指针顺序相匹配。索 引文件提供查找Haystack存储文件中某一特定指针所需的最小元数据。为了快速查找,把索引记录载入并组织到一个数据结构中,这是Haystack应 用程序(在我们的情况下是照片存储)的职责。索引文件不是至关重要的,因为它可以根据所需从Haystack存储文件中重建。索引的主要目的是可以快速加 载指针元数据到内存中,而无须遍历庞大的Haystack存储文件,这是因为索引的大小通常还不到存储文件的1%。
Haystack写操作
Haystack写操作同步添加新的指针到Haystack存储文件中。当指针成功添加到庞大的Haystack存储文件中之后,相应的索引记录也被写入索引文件。由于索引文件不是至关重要的,为了达到更快的性能,该索引记录是异步写。
索引文件还会定期被刷新到下面的存储设备,以便限制由硬件故障所引起的恢复操作的程度。在系统崩溃或突然断电的情况下,Haystack恢复程序丢 弃所有存储中的不完整的指针,同时截断Haystack存储文件直到最后一个有效的指针,然后,在Haystack存储文件最后为所有跟踪的孤立指针写入 丢失的索引记录。
Haystack不允许覆盖已存在的指针偏移量,因此,如果某个指针的数据需要修改,其修改后的新版本必须使用相同的﹤Key, Alternate Key, Cookie﹥元组。然后应用程序就可以认为,在那些有着多个关键字的指针中,具有最大偏移量的指针就是最新添加的指针。
Haystack读操作
传递给Haystack读操作的参数包括指针偏移量、关键字、替换键、Cookie和数据大小。然后Haystack添加页眉和页脚的大小到数据大 小中,并从文件中读取整个指针。只有当关键字、替换键和Cookie符合参数类型,所传递的数据通过校验,并且指针没有被之前的操作删除时,读操作才能成 功(见下文)。
Haystack删除操作
删除操作很简单——通过设置指针的标记域中的一个“deleted(已删除)”标记位,标记Haystack存储中的指针为已删除。然而,相关的索 引记录并不进行任何方式的修改,因此一个应用程序可能会结束于引用某个已删除的指针。对于这样的指针的读操作会注意到“deleted”标记,然后终止操 作,提示操作错误,给出错误信息。已删除的指针的空间不会以任何方式回收。回收已删除指针的空间的唯一方法是压缩c(见下文) 。
照片存储服务器
照片存储服务器负责接收HTTP请求,并转化成相应的Haystack存储操作。为了尽量减少读取照片所需的I/ O操作次数,服务器在内存中保存一个Haystack存储文件中所有照片偏移量的索引。启动时,服务器读取Haystack索引文件并生成一个内存中的索 引。由于每个节点数以亿计的照片(并且该数字只会随着更大容量的驱动器而增加),我们必须确保该索引能够装入可用的内存中。这是通过在内存中保留最少数量 的元数据来实现,只保留查找照片所需的信息。
当用户上传一个照片,该照片就被分配一个唯一的64位编号。然后将照片转化为4个不同大小的图片。每个图片具有相同的随机Cookie和64位关键 字,合理的图像大小(大,中,小,缩略图)是储存在替换键中。然后上传服务器调用照片存储服务器,把所有4个图像存储在Haystack中。
内存中的索引为每张照片保存以下信息:
Haystack使用开源Google稀疏散列数据结构来减小内存中的索引,因为使用它,每条记录只占2位。
照片存储写/修改操作
写操作写入照片到Haystack,并更新内存索引。如果该索引中已经包含了具有相同关键字的记录,那么这就是一个修改现有照片的操作,那么只修改 索引记录偏移量,以反映新图像在Haystack存储文件中的位置。照片存储总是假设存在重复的照片(具有相同关键字的照片),只有存储在最大偏移量位置 的照片是有效的。
照片存储读操作
传递到读操作的参数包括Haystack id 和照片关键字、大小和COOKIE 。服务器根据照片关键字,执行一个在内存索引上的查找操作,然后得到含有所需照片的指针偏移量。如果发现调用的是Haystack读操作来读取照片,那么 如上所述,Haystack删除操作并不更新Haystack索引文件记录。因此,一个新的内存索引可能会包含之前删除的照片的旧记录。读取之前删除的照 片将会导致操作失败,并且内存中的索引会自动更新,设置已经删除图像的偏移量为0。
照片存储删除操作
在调用Haystack删除操作之后,内存中的索引被更新,设置特定图像的偏移量为0来表示该图像已经被删除。
压缩
压缩是一个联机操作,可以回收已被删除的指针和重复指针(具有相同关键字的指针)所占用的空间。它通过复制指针创建一个新的Haystack,跳过所有重复和已删除的指针。每次这样做,就会交换文件和内存中文件的结构。
HTTP服务器
我们使用的HTTP框架是由开源lib event图书馆所提供的简单的evhttp服务器。我们使用多线程,同一时间内,每个线程能够处理一个HTTP请求。因为我们的工作量最主要是由I/O操作产生,因此HTTP服务器的性能并不是至关重要的。
【编辑推荐】