也许生活在这钢筋混泥土之中,早已淡了季节轮回的感触,忽视了做为大自然的有机体,新陈代谢,生生不息的一面。秋天短暂,却是收获喜悦的季节,幸福四溢,风乍起,那麦浪的金黄。
与分布式fs擦肩而过
2013年开始筹划数据库的拆分,2C的分类信息网站时间序特征很明显。每天都有对历史数据进行归档,不断的瘦身,典型按时间 "horizontal sharding"。随着业务增长和变化,热数据已有几百GB,特别是开放免费端口后,短时间数据暴增,例如二手房源表,单表100G,按主键的查询也非常慢,系统极不稳定。
由于系统设计问题,MySQL 直接存储详情页描述部分的 HTML 代码。经过分析,描述 TEXT字段平均 6KB 左右,100G的单表,贴子属性数据只有20G,其它均为描述 TEXT 字段,决定拆字段,也就是所谓的 "vertical sharding"。拆出的 TEXT 字段存储有两个选择,MySQL 或是分布式文件系统。对市面上一些系统做调研后,没有运维经验,出于稳定性和非侵入考虑,选择扔然存储在 MySQL 中。后来做 Automan SQL 自动上线,支持批量文件上传,保存在本机文件系统中,一直没有改成存储分布式文件系统中,很可惜,擦肩而过。
上周看毛剑在写基于 haystack 的文件系统,正好重温一遍。
脸书老的图片架构
上图是标准的图片访问流程,用户上传图片,Web Server 直接存储到后端存储中,并将图片 Url 保存到数据库中,例如 MySQL等。Url 类似如下格式:
http://<Machine ID>/FILE/PATH
当请求发出后,Web Server 从 MySQL 中取出图片 Url 信息,包裹一层 CDN 信息,扔到用户的浏览器页面。由用户浏览器按照包裹后的 Url 渲染页面,包裹的格式可能如下:
http://<CDN>/<TAG>/<Machine ID>/FILE/PATH
CDN 处理请求,如果图片不存在,那么根据协议剥离出真实图片 Url,去源站请求冲 Cache,这就是人们常说的 CDN 回源,监控回源率来不断优化业务。
自然地,脸书老架构也是将图片保存到类 POSIX 文件系统中,商业共享存储 + NFS 挂载模式。这个模式支持脸书的飞速发展,比较像05年阿里的IOE。后来这个架构遇到了瓶颈:
1. 脸书图片四个尺寸,随着量越来越大,长尾理论突显,CDN 缓存这些长尾成本太高。
2. 类 POSIX 存储方式,为获取文件,访问 metadata 产生很多 IO 。比如说权限这些属性信息就是不必要的,如果 metadata 全部缓存在内存中,成本太高。
脸书haystack架构
Haystack 是脸书在2012年发布的论文《Finding a needle in Haystack: Facebook’s photo storage》,详细描述了他们的图片存储架构更迭。那么 haystack 就是要解决这些问题,有四个设计目标:
1. High throughtput and low latency. 和数据库 OLTP 业务类似,要求高吞吐和低延迟,一方面热门图片缓存到 CDN 中,另外一方面减少存储磁盘 IO 次数,将 metadata 存放到内存中,获取图片只发生一次 IO 操作。
2. Fault-tolerant. 高可用性是不可避免的话题,haystack 设计时考虑了地域 IDC 容灾和机柜容灾。服务端上传图片时指定冗余策略。
3. Cost-effective. 基于商业共享存储的都比较贵,普通 PC 服务器挂载大硬盘便宜很多,但是故障率也比较高,这一点比较考验 Fault-tolerant 高可用。
4. Simple. 架构简单,部署和运维更方便。最近在研究的 vitess 架构和部署非常复杂,这也是他流行不起来的一个原因。
基于这个思想,haystack 设计者绕过了 POSIX 文件系统这块,把 haystack 变成了一个 KV FS,即 NOFS。每个图片对应一个 FID,不再单独存放文件系统中,而是同一个物理卷 Volume 图片全部写入一个文件中,由 Volume Server 内存维护 FID : <Volume Machine, Offset, Size> 映射关系,Volume Server 内存中维护打开的文件句柄,读取图片时只需一次 IO 顺序读操作。
架构比较简单,分为三部份:Haystack Directory, Haystack Cache, Haystack Store
Directory: 即所谓的 Meta Server
1. 生成 FID,维护 logical volume 与 physical volume 映射关系,解决上传时的负载均衡问题。
2. 新加入的 Store Server 要在这里注册。
3. 维护 logical volume 的 read-only 属性,只读的 logical volume 不再接受 upload 请求。
4. 决定请求走 CDN 还是内部 Haystack Cache Server.
Cache: 所谓的内部 CDN
1. 对图片 FID 采用一致性 hash 算法保存。
2. 只缓存用户请求,而不是来自 CDN 的请求。
3. 只缓存 write-enabled store 图片,由于上传的时间序,相当于只缓存最新生成的图片。比如说用户刚上传的图片,可能就会存到 Cache 中预热。
Store: 最终落地存储服务
1. 图片顺序追加到一个大文件中,内存中维护图片在文件中的 Offset 和 Size 的索引信息。
2. 为了解决重启快速加载问题,索引信息会单独保存到一个 Index File 中。
Store 存储格式
涉及两类文件, Store File 和 Index File。
Store File 是一个大文件,文件头为 Superblock 保存全局的版本号等信息。每个图片为一个 Needle 结构,顺序追加到文件尾。每个 Needle 保存图片的 Cookie, Key, Flags, Size, Data, CheckSum等信息,由于脸书上传图片一式四份,四个尺寸共用同一个 Key, 那么就由 Alternate Key 做区分。
当机器重启后,需要利用 Index File 快速重建内存中图片索引信息。如果没有索引文件,那么顺序扫描 Store File 也可以重建,但耗时。 假设 Needle Index 占用24byte,那么128G内存机器可以存储68亿图片的元数据信息。论文中表示,Index File 异步写,重启后可能会有 Ophen Photo 需要从 Store File 中重建。
图片上传,更新与删除
图片时间序特征很明显,所有的上传,更新与删除均为 Append 追加操作。 Web Server 请求 Directory Server, 获取 Volume Id, Key, Alternate Key, Cookie。Web Server 将这些与图片数据上传到指定 Store 机器上,数据的冗余由 Store 同步完成,强一致。
更新操作与上传一致,更新内存索引信息,数据追加到 Store File 即可。删除操作将内存和 Index File 中的 Flags 标记为删除即可。对于大量删除操作,会产生文件空洞,需要根据一定策略回收。
图片读取
在 Url 中可以解出图片的 Volume Id, Key, Alternate Key, Cookie 信息,Web Server 访问 Directory Server 得到该 Volume Id 所在的 Store Server。再由 Store Server 查询内存中索引信息,根据 Flags 标记判断是否已删除。如果未删除,根据 Offset Size 去 Store File 获取详细数据,解出存储的数据和 Cookie,判断与请求的 Cookie 是否一致,不一致则报错。
Weadfs
Weadfs 是开源版本的GO实现,代码比较简单易懂。仔细阅读源码,可以加深对 haystack 的理解和认识。看知乎上说好多公司在使用,比较稳定。
1. 脸书的 Paper 里还是有好多细节没有披露,比如 Directory Server 的高可用,如果多个那么数据一致性的保证。从源码上看 Weadfs 通过 Raft 来达到多个 Master Server 的高可用。
2. Weedfs 的 Padding 为8,Size使用4字节,那么单 Volume 文件最大32GB.
3. 相比 haystack, 多了很多实用功能:Gzip压缩,索引信息可以存储在 LevelDB 中,多 Master 高可用,filer server 等等。
BeansDB&FastDFS&HDFS
赶集使用 FastDFS 做图片存储,Tracker Server 对应 haystack 的 Directory Server. 但是 FastDFS是使用 POSIX 文件系统的,IO 压力有些大。
BeansDB 是 nice 在使用的存储,Memcache协议,基于 Bitcask 模型,由 R+W>N 来保证一致性。豆瓣一直在用,存储 mp3 文本 图片信息。
这两个开源产品也是针对小文件存储做的优化,有时间还得好好读读源码加深理解。另外一个 HDFS 用来存储大文件,按块打散存储,适合批量作业,吞吐量大,但时延比较高。
结语
本来要写 Redis Proxy Step By Step 系列的,先耽搁一周,下周继续好了。推荐一首李荣浩的歌 《自拍》。