背景介绍
本论文发表自2010年,是当时facebook所应用的存储系统。在2010年前后facebook上存储的照片数量开始迅猛的增长,当时有差不多2600亿张图片、20PB的数据量,每周有60TB新的照片上传至facebook系统,服务器每秒需要处理百万张。此外,这些照片有一个特点就是照片的都是小文件照片。基于以上特点,传统的存储架构已经无法满足当前的存储需求。
因此,facebook团队提出了一个新的存储架构Haystack来满足日益增长的存储需求。Haystack架构有以下四点需求:
- 高吞吐、低延迟:降低磁盘读取次数、保持元数据存储在主存中
- 容错性:分布式存储,数据备份
- 低成本:更高的存储利用率、执行效率更高
- 设计简单:生产环境中更便捷的开发和部署新系统
前序设计
NFS-based Design
如上图所示,是基于NFS的传统存储架构(NFS是网络文件系统能使使用者访问网络上别处的文件就像在使用自己的计算机一样),图中的NAS(网络附属存储)是连接着网络上的存储设备,如下图所示:
传统的设计用户若要访问facebook的网站时将会经过以下步骤:
- 浏览器向web Server发送页面请求
- web Server给浏览器返回一系列的URL链接,每个URL链接对应着照片文件的存储信息。
- 浏览器得到URL后向CDN(内容分发网络,可以理解成缓存)发送资源申请请求。此时,若CDN中存储着申请的资源文件则直接返回资源至浏览器,否则CDN向photo store server发送申请请求。
- photo store server先在自己的缓存中查找照片文件,若没有再去NAS上查找对应的照片资源,找到再返回给CDN
- CDN收到照片资源后在自己这里缓存一份然后再将照片反回给浏览器。
以上,便是传统的照片存储系统的运行机理。在基于NFS的存储系统中facebook自己总结了以下几点不足之处,
- Cache/CDN is not practical
Too many disk operations per action
- inefficient as the directory's blockmap was too large to be cached effectively by appliance
- common to incur 10 disk operations to retrieve a single image
Caching in filesystem level wouldn't work either
- long tail
第一点:Cache/CDN(可以理解成CDN为第一级缓存、Cache为第二级缓存)在实践中的效果很差的原因在于,如果CDN不命中那么在Cache上命中的可能性也不大(Cache是指photo store server上的第二级缓存)。至于为什么CDN不命中Cache命中的概率也不大可以这样理解:用户访问的基本上都是新上传的照片,新上传的照片本身大部分就在CDN中有缓存,如果CDN中都没有代表着用户访问的极有可能是老照片,所以老照片在Cache中有缓存的可能性也不大。
第二点:每找一张照片操作系统所进行的文件IO操作次数太多,导致效率低下(原因下一节解释)。在本系统中,facebook将打开的文件描述符缓存起来了photo server里面(文件描述符表现出来就是一个数字),下次读取无需再次查找和打开文件(文件名—->handle这一层映射存在主存中),但是存在一个问题:CDN不命中,说明请求的照片是不常用的照片,所以后面的系统中大概率也不会名字,再cache一层实际意义不大(CND若命中则可直接取走数据,不命中的话后续命中的概率也不大 所以再cache一层的意义不大)
第三点:与第一点的解释相似,由于long tail效应(如果CDN不命中则Cache也大概率不会命中,所以Cache的意义不是很大)
此外,facebook团队在前期犯了一个错误导致存储效率低下:
在一个文件夹下放置上千张图片。这样导致每次查询一张照片所付出的查询代价都是不小的。在后续的版本中facebook改进了它在一个文件夹下存数百张照片。
文件系统的工作机制
需要注意的是本文写于2010年,当时的文件系统和现在的有一定的差异,当时所采用的是ex2(现在ex4)。在这个背景下理解上一节所留下的问题:读取一张照片所需要的IO次数过多(经常读一张需要10来次IO操作)。
读取一个文件的步骤(表层)
fopen一个文件、read数据。在我们使用的层面读取一个文件很简单只需调研编程语言所提供的接口例如fopen函数打开一个文件,然后系统会给你返回一个文件描述符(一个数字,代表着你需要读的文件)然后再对该描述符进行读写即可。
读取一个文件的步骤(底层)
- 进程调用库函数向内核发起读文件请求;
- 内核通过检查进程的文件描述符定位到虚拟文件系统的已打开文件列表表项;
- 调用该文件可用的系统调用函数read()
- read()函数通过文件表项链接到目录项模块,根据传入的文件路径,在目录项模块中检索,找到该文件的inode;
- 在inode中,通过文件内容偏移量计算出要读取的页,读取文件数据。
若一个文件的存储路径是:/etc/passwd。则read找到这个文件在磁盘上所存储的位置的步骤是:
- 先查看根目录下的文件夹在磁盘上存储的位置,找到etc这一行发现其在7(i-node)这个位置。
- 然后跳转到7这里查找passwd这个文件的i-node信息(如同所示存储在6422这个位置)
- 然后磁盘指针再指向6422这个位置passwd这个文件具体的data block指针指向的位置上读取相应的文件信息。(一个文件会存储在不同的位置,即data block指针指向的位置很可能不是连续的)
所以,如上这个例子所示,read("/etc/passwd")这个操作便有3次IO操作。此外由于在ex2文件系统下,在当前文件夹下查找指定文件的i-node信息是线性查询(现在ex4是一个树的结构),所以若一个文件夹下有上千个文件,则平均查询到一个文件的查询次数是500次。
在linux系统下可以用ls -li来查看文件的i-node信息,如下所示。
至此,讲清楚了传统的架构设计,接下来讲解Haystack的工作。
Haystack的设计与实现
Haystack存在两种metadata:
- Application metadata:描述浏览器能检索到图片存储信息的元数据
- Filesystem metadata:描述图片存储的具体位置的元数据(相当于二级缓存)
根据这两种元数据便可以找到找的存储的位置,例如:利用Application metadata找到记录照片元数据的位置,再去该服务器上找到该元数据,而找到的这个元数据是记录着照片的具体的存储信息。(可能有点绕)举个形象的例子,比如说你干了什么坏事情,警察叔叔要来找你,警察叔叔先去警察局查小册子(Application metadata)找到你在哪个小区,然后再去该小区找到你的住宿信息(filesystem metadata)再根据该住宿信息找到你这个人。
若每一张照片有一个metadata则会造成metadata的数量非常多,无法存储在主存中,所以Haystack将多个文件数据存储在一个文件中,以此来减少元数据(简单有效,即减少了元数据也减少了IO操作)。
URL设计
每张照片由这样一个URL来定位,首先根据CDN的信息去对应的CDN找,若没找到则去掉CDN的部分用剩下的链接去Cache上面找,若还是没找到则去掉Cache的部分,用Machine id开头的部分去找数据。
https://scontent-xsp1-2.xx.fbcdn.net/v/t1.0-9/s960x960/117626438\_694963981232505\_2939587164346987203\_o.jpg?\_nc\_cat=102&\_nc\_sid=e007fa&\_nc\_ohc=J92KKoh-gT4AX8XBxXJ&\_nc\_ht=scontent-xsp1-2.xx&\_nc\_tp=7&oh=8b2848b261b59aad56c2684c994e65fb&oe=5F6219F9
如上所示是当前的facebook的照片存储信息,有部分还是可以参考的。但是在2020年的现在架构估计已经发生了变化。
Haystack系统的核心组件
Haystack主要有三个核心组件组成:
- Haystack Store:本质上还是之前的那些NAS,定义一个logical volume,每个logical volume对应管理着若干个phisical volume。
- Haystack Directory:负责metadata的mapping,本质上是做如何从logical vlolume映射到physical volume上。决定生成的是Cache的URL还是CDN的URL(实现Cache和CDN的负载均衡),并且将写满的磁盘标记为只读磁盘。
- Haystack Cache:Cache本质上还是一个CDN,由于CDN太贵了,可以理解成CDN为外层缓存,Cache是内层的缓存,Cache可以减少系统对于外层CDN的依赖。Cache和CDN相当于一个两层的缓存。CDN找不到则去Cache里面找,Cache找不到再去系统里面找
如上图所示,新的Haystack的工作流程如下:
- 浏览器向web server发送网址请求。
- web server进一步向Directory发送请求。
- Directory生成所需要的照片的URL等信息后返回数据给server再返回给浏览器(Directory同时负责负载均衡的作用,即决定浏览器是去CDN还是Cache找资源)。
- 若浏览器去CDN找资源(如果是读则走Cache),CDN中若缓存着所需照片的资源,则直接返回数据,若没找到则进一步去Cache中找。
- 若Cache中也没有则向Store系统发送请求。
- Cache拿到数据后缓存一份然后一步一步返回给浏览器。
Haystack Directory
- 提供逻辑卷到物理卷的映射,可以构造URL
- 负责逻辑卷的写和物理卷的读之间的均衡(the Directory load balances writes across logical volumes and reads across physical volumes.)
- 判断需要的照片需要缓存在CND还是Cache中(减少对于CDN的依赖)
- 将存储满的物理卷标记为只读卷
Haystack Cache
- 分布式哈希的形式,将照片的ID作为哈希表的key来定位缓存的数据,若哈希表不命中则去存储系统中查找。这里的value表示着是照片的存储路径,即logical volume id/offset/size等信息。(Haystack将一个数百万个照片存储在一个大的100G文件中)
缓存照片只会在以下两种情况下发生
请求直接来自用户而非CDN
- 经验结论:CDN未命中的数据在Cache中大概率也不会命中(CDN本身就是一个大Cache)
存储照片的机器是可写的(存储空间未满)
- read only代表该卷已经存在较长时间,很可能已经在CDN上已经有缓存,其次,根据经验照片的数据越新,越容易被再次访问,老磁盘上的数据代表着数据年代比较久了
- 类似CDN的功能,存储着较为热点的数据(根据facebook直自己的调查数据,照片上传相差一周 访问频率差了10倍 6个月后只剩下百分之一)
Haystack Store
- 每个存储机器对应着数个物理卷,每个物理卷存储这数百万张图片
- 每个物理卷可以看作一个巨大的文件(100GB),利用照片ID和偏移量可以定位存储具体照片的位置。(这样设计将成千上万个文件合在一个文件里面(100GB),管理一个文件描述符,这样的量级便可以理想的存储这一层映射关系。可以将其存储在主存当中)
读:根据文件描述符、文件名字、文件大小、文件在volume上的偏移量,这样便可直接读出文件。
写:直接生成相应照片的needle,append到后面就可以,再更新主存中的相应索引信息
- Haystack系统的关键技术:做到不用IO操作便可读取到文件的名字、偏移量、文件大小等信息(信息存储在内存中)
结构化存储形式
- 只需知道文件名、偏移量、文件大小便可读出相应的文件
- 为了能快速定位照片,每个存储机器在主存中维持每个物理卷的相应数据(照片的块位置、照片大小、卷内偏移量)
- 文件存储结构
上传照片
当用户上传照片时将经过以下几个步骤:
- 将照片数据发送到web server服务器上
- web server根据照片的数据信息向Directory发送分配请求
- Directory根据照片信息分配一个可写的逻辑卷地址,并返回该地址
- web server得到逻辑卷地址后将照片数据上传至相应的逻辑卷上
- 逻辑卷得到数据后,将照片写入到其管理的每个物理卷中(貌似当时一个逻辑卷负责三个物理卷,每个物理卷的位置不一定在一起,以这样的方式做到了容错)
如果是更新照片则有两种策略:
- 新照片和老照片不在一个物理卷上,则直接在当前物理卷上新增,并更新缓存信息
- 若新照和老照片在同一物理卷,则在物理卷的后面增加该信息,并更新缓存(根据偏移量判断新旧,越大的代表最新上传的)
照片读取
- 向存储机器发送逻辑卷ID、key等信息请求照片
- 存储机器在主存中查找相应的元数据,若存在该照片则根据该元数据去读取具体的照片
删除照片
- 在相应的缓存和物理卷的位置上将其delete flag至1 标记为已删除
- 删除的空间等以后一起回收(回收是将当前物理卷上的所有文件拷贝到另外一个物理卷上,并略过那些flags上删除位为1的needle)
索引文件
- 传统的文件系统的索引信息在系统开机时遍历整个物理卷,然后生产的索引信息
- 为了防止机器宕机导致索引消失(映射关系存储在主存中),Haystack需要周期性的将索引信息备份到磁盘中(1.提高了容错性 2.加快了重新构建映射关系的速度(若不存储则需要遍历所有文件重新构造映射关系))
- Haystack索引文件的信息(根据这个信息,定位照片、判断照片是否已经被删除)
存储引擎-XFS
XFS的底层文件块是以extents的形式存储的(连续)而block块大部分是不连续存储的所以会有很多的索引信息导致无法存储到主存中。
extents:每块1GB大小
RAID:每块block大小为256kb
这样可以做到一次读取便可把文件读到缓存中(block太小需要多次读才能读完一张照片的数据)
总结
这样本论文的核心部分都已经介绍了,以上是基于自己的理解和网上的一些资料信息写出的。肯定存在一些问题,如果有问题直接提出来修正。