分享照片是Facebook上最流行的的功能之一。截至目前,用户已经上传超过15亿张照片,这使得Facebook成为最大的照片共享网站。对于每一个上传的照片,Facebook都生成并存储四个大小不同的图像,从而转化为共60亿张照片,总容量超过1.5PB。目前以每周220万新照片的速度增长,相当于每周要额外增加25TB存储。在高峰期每秒需要传输55万照片。这些数字对Facebook的照片存储基础设施的一个重大的挑战。
旧的 NFS 照片架构
老的照片系统架构分以下几个层:
- 上传层接收用户上传的照片并保存在 NFS 存储层。
- 照片服务层接收 HTTP 请求并从 NFS 存储层输出照片。
- NFS存储层建立在商业存储系统之上。
- Cachr: 一个缓存服务器,缓存 Facebook 的小尺寸用户资料照片。
- NFS文件句柄缓存:部署在照片输出层,以降低 NFS 存储层的元数据开销。
新的照片架构将输出层和存储层合并为一个物理层,建立在一个基于HTTP 的照片服务器上,照片存储在一个叫做haystack 的对象库,以消除照片读取操作中不必要的元数据开销。新架构中,I/O 操作只针对真正的照片数据(而不是文件系统元数据)。haystack 可以细分为以下几个功能层:
- HTTP 服务器
- 照片存储
- Haystack 对象存储
- 文件系统
- 存储空间
在下面的介绍中,我们会对于上述的每个功能层做详细的讲述。
存储空间Haystack 部署在商业存储刀片服务器上,典型配置为一个2U的服务器,包含:
- 两个4核CPU
- 16GB – 32GB 内存
- 硬件 RAID,含256-512M NVRAM 高速缓存
- 超过12个1TB SATA 硬盘
每个刀片服务器提供大约10TB的存储能力,使用了硬件 RAID-6, RAID 6在保持低成本的基础上实现了很好的性能和冗余。不佳的写性能可以通过RAID控制器和NVRAM缓存回写解决,写由于读取大多是随机的,NVRAM缓存是完全用于写入的。
Haystack 对象库是建立在10TB容量的单一文件系统之上。
图片读取请求需要在读取系统调用这些文件的位置偏移,但是为了执行读取操作,文件系统必须先找到实际物理卷上的数据。文件系统中的每个文件都被一个叫做inode结构标识。inode包含了一个磁盘上逻辑文件偏移和物理区块偏移的映射。在使用的特殊类型文件系统时大文件块映射可能相当大。
基于文件系统的区块为给个逻辑区块和大文件保存映射。这些信息通常不适合保存在inode的缓存中,而是存储在在间接地址块。所以在读取文件的时候必须按照特定的流程。这里可以多个是间接地址块,所以一个读取会产生多个I/O取决于是否间接地址块被缓存。
该系统只为连续范围的区块保持映射。一个连续的大文件的块映射可以只由一个范围的标识,这样是适应inode的系统需求的。但是,如果该文件是一个被切割的不连续的块的话,他的块地图可能非常的大。以上可以通过文件系统主动为大的物理文件分配大块的空间来减少碎片。
目前使用的文件系统为XFS,一个很大程度提供高效的文件预分配系统。
Haystack 对象存储
Haystack 是一个简单的日志结构(只能追加),存储着其内部数据对象的指针。一个 Haystack 包括两个文件,包括指针和索引。下面的图片将描述haystack存储文件的布局:
haystack最前面的8K存储是被超级块占用。紧随超级块是针,每针组成的一个头部,数据和尾部:
一个针被他的<offset key=”" alternate=”" cookie=”">元组标识,其中的偏移量为其在haystack存储的偏移。Haystack不在任何健值上做限制,即允许可以有重复键针。下图显示了索引文件的布局:
在haystack存储文件中有每针相应的的索引记录,并且包含针索引记录的顺序必须和haystack存储文件相关的针的顺序相匹配。按照规定索引文件的最低需求是找到一个特定的针在haystack存储文件的元数据。载入和组织索引记录到一个有效的查找数据结构是Haystack程序的责任。索引文件是不是很关键,因为如果需要它可以从haystack存储文件重建。索引的主要职责是让针元数据无需通过较大的Haystack存储文件,快速加载到内存中。原因是其可以让索引编程原来存储的1%。
Haystack 写操作
Haystack 写操作同步将指针追加到 haystack 存储文件,当指针积累到一定程度,就会生成索引写到索引文件。由于索引文件是不是很关键,为了能有更快的性能所以采用异步的方式进行写入。
为了降低硬件故障带来的损失,索引文件还会定期写到存储空间中。在崩溃或突然断电的情况下,将haystack恢复处理器存储中任何残缺的针和截断haystack存储中最后一个有效的针。接下来,它会把丢失的针的索引记录 写到haystack文件的最后。
Haystack不允许重写现有的针偏移,如果一个针数据需要被重写,那么新版本必须使用相同的<key alternate=”" key=”" cookie=”">元组。应用程序会自动分辨出这两个相同的键,有最大偏移的便是最新的那一个。
Haystack 读操作
传到 haystack 读操作的参数包括指针的偏移量,健,备用键,Cookie 以及数据大小。Haystack为数据大小添加头部和尾部的长度,然后根据数据尺寸从文件中读取整个指针。读取操作成功的关键就是作为参数传递的健,备用键,Cookie是否匹配,数据是否通过了校验,并且针没有被删除掉。(见下文)
Haystack 删除操作
删除操作比较简单 – 只需要在 Haystack 存储的指针字段中的“删除”位标记一下即可。并且,相关的索引记录不会做任何的修改。是最终的应用程序引用到的是一个删除的针。像这样一个读取删除针的操作将会返回一个相应的错误给应用程序。空间对已删除的针不做任何的回收,只有这样,才能使 haystack 的空间非常的紧凑。(见下文)
照片存储服务器
照片存储服务器负责接受 HTTP 请求,并转换成相应的 Haystack 操作。为了尽量减少服务器检索照片时的I/O操作,该服务器维护着全部 Haystack 中文件索引的缓存。服务器启动时,系统就会将这些索引读到缓存中。由于每个节点都有数百万张照片,必须保证索引的容量不会超过服务器的物理内存。在内存中仅需要保存查找照片所需的少量元数据即可。
对于用户上传的图片,系统分配一个64位的独立ID,照片接着被缩放成4种不同尺寸,每种尺寸的图像拥有相同的随机 Cookie 和64位的密钥,图片尺寸描述(大,中,小,缩略图)被存在代用key 中。接着上传服务器通知照片存储服务器将这些资料连同图片存储到 haystack 中。
每张图片的索引缓存包含以下数据:
![](https://img-my.csdn.net/uploads/201212/19/1355912673_4060.jpg)
由于Google的开源 sparse hash data 结构对于每个条目只有2bit的开销,所以Haystack使用它来保证内存中的索引缓存尽可能小。
照片存 储的写/修改操作
写操作将照片数据写到 Haystack 存储并更新内存中的索引。如果该索引记录中包含了相同的键,那么这是一次对现有的照片进行修改的操作。并且只要修改索引记录中的偏移来反应新图像在haystack存储文件的位置。照片存储始终假定,如果有重复的图像(图像具有相同的键),有较大的偏移量的那个存储是有效的。
照片存储的读操作
传递给一个读操作的参包括Haystack ID,照片的 Key, 尺寸以及 Cookie。服务器事先在缓存中按照照片的Key和所需文件的偏移进行查找。如果找到了它,并向haystack发出读取词图像的请求。按照上面说的,haystack的删除操作并不更新它的索引记录,因此添加到内存中的索引可以包含以前删除的照片的内容。当阅读以前的删除的照片失败后,系统将在内存的索引中色绘制词图片的偏移量为0.
照片存储的删除操作
通知 Haystack 执行删除操作之后,内存中的索引缓存会被更新,将偏移量设置为0,表示照片已被删除。
重新整理(压缩)
重新整理(压缩)是一种回收删除和重复的针(针使用相同的Key)的在线操作。它会通过复制针跳过任何重复或删除的条目创建一个新的 haystack。一旦此操作完成它就回去替换掉内存中的文件和结构。
HTTP 服务器
Http 框架使用的是简单的基于开源的libevent库的 evhttp 服务器。使用多线程,每个线程都可以单独处理一个 HTTP 请求。因为我们的系统消耗大多是I/O操作,HTTP服务器的性能并不很重要。
结束语
Haystack 是一个基于 HTTP 的对象存储,包含指向实体数据的指针,该架构消除了文件系统元数据的开销,并实现将全部索引直接存储到缓存,以最小的 I/O 操作实现对照片的存储和读取。
本文作者为Facebook的工程师Peter Vajgel, Doug Beaver 和 Jason Sobel, 由标点符进行翻译。
原文链接(E文):http://www.facebook.com/note.php?note_id=76191543919&ref=mf
转发原文:http://www.biaodianfu.com/facebook-efficient-storage-of-billions-of-photos.html