中等规模网站的UGC图片存放规划
先声明,本文借鉴了很多刘涛(Tarkus)和 Druggo Yang 的实战经验,特此感谢
好像现在是个网站就允许用户上传头像,其中一部分还允许上传相册、个性背景图之类的东西。对图片的规划各村都有各村的高招,这里只是抛砖引玉、提个醒:当文件膨胀到一定规模的时候再去改就来不及了,在一个项目的草创时期,让一个人多花两个星期的时间来琢磨这个“小”问题也绝对称不上是过度设计。
我对中等的定义:图片所占空间在 1T - 数十 T 之间。
功能需求
基本的就两点:排除重复,和可扩展性。
排重并不为很多人所重视,因为对很多人来说短期可以承受,实际经验重复的占了 50%(一些流行的图片会被重复上传很多次),但问题是这里还关系到另外一件事:审核。例如一些很流行的黄图会被频繁的被不同人重复上传,这也是不小的审核工作量。
具体设计
简单的说就是 MySQL 单点来保证唯一,将文件 MD5 转换为递增ID,再将固定数量的文件分成组,例如最简单的 MySQL 表可以这样。
CREATE TABLE IF NOT EXISTS `upload_pic` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`hash` binary(16) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `hash` (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=ucs2 AUTO_INCREMENT=1;
每次有上传,先 MD5、INSERT,成功就返回 ID,失败就重新 SELECT 找以前的 ID
文件就是整个 ID 的补零切三段,如 12345 保存为实际的文件 /upload/000/012/345,不保留扩展名,统一发送 Header Content-Type application/octet-stream
首要原则是只新增,不改写(除了审核后删除的)
调优
上面所述只为了方便理解的最精简的方案,还有很多事情可以做。
用于发号的 MySQL 仅仅是写入单点,对于已经生成的 hash,可以定期生成 Archive 引擎 的只读表。然后新上传时以 SELECT/INSERT/SELECT 的顺序获取 ID(这时候,第三步的 SELECT 是极其罕见的)。
如果是 nfs 挂载,初期可以挂整个 /upload,后期可以以一级子目录为单位,分散在不同机器上。如 /upload/001 和 /upload/002 挂在机器 A 上,诸如此类。这样我们可以每 M 个图片为一个挂载单位(下面简称 MU――Mounted Unit)。如果图片的平均大小是 200K 左右,则每个 MU 的平均大小应该在 1M * 200K = 200G 左右。由于内容不再改写,老的 MU 可以到处搬动、甚至分散到不同机房,其实是类似 flickr 的 farm*.static 子域名。
实际磁盘问题是个大问题,这方面网友 Druggo Yang 给了我很多经验,他最早面临的问题是大量随机读写、几乎没热点,大概是文件实在太碎,硬盘反倒在网卡之前成为瓶颈,银弹是有:SSD,不过太tmd贵了。最后他们是用的 LVS,那已经是我知识体系以外的东西了。
我想到的解决方法被 Druggo Yang 评价为自己做软 RAID:不同 MU 挂到不同盘,用 nginx 访问,或者系统本身瓶颈,那就若干个 Server 有相同的 MU,做轮询。但总之我不推荐做磁盘 RAID,这可能是我还没搞明白,也可能真的如此,就拿最简单的两块硬盘做 RAID 0 来说,按我的理解,每读一个文件的时候,两块磁盘都需要定位,而两块独立的磁盘却可以分别各定位一个文件,据说也会受磁盘控制器的限制,但大体上不影响我的我的结论:RAID 的优势应该在连续读写上,而随机读写反而会因为短板效应而略微降低 IOPS。总之这问题我只能纸上谈兵,有条件应该拿几块硬盘测一下的。
此外,诸如目录分级是 1000 还是 100 个文件一组,还是用其他进制,都可以细测。记得以前看过一个单目录文件数的 Benchmark,可惜后来再也找不到了(有谁能提示一下?),隐约记得 500 比较合适,因为这里需要频繁手工搬动,索性就弄个人脑容易识别的数了
另外为了防止遍历需要加个扰码,这也很容易了。
至于缩略图接口,另算
应用特例:头像
其实每个网站都有的图片机制是头像,现在普遍的规则是用户 ID + 补码,补码可以是更新次数(如 douban)、时间戳(如 t.sina)、文件名(如 twitter)之类的。补码的主要目的是为了靠改变 URL 而绕开 Header Expires
但直到上家公司的产品部门在全面模仿 Facebook 运动中,想要实现 Facebook 里的一个功能:所有图片的大一统,如新上传的图片进相册、可以将任意一张图片设为头像,而收藏功能的流程跟自己上传几乎是一样的,等等,虽然这个改动过于底层以至于辞职一年多后公司彻底黄掉也没能看到它实现,但我还是很喜欢这个大一统的:不去考虑一张图片被哪些地方引用,只管存就是了,而所有涉及到图片的地方,都保存的是图片 ID,因此只看某一张图片的 URL,你不知道头像的主人是谁:有可能其他人也用相同的图片做头像,或者在某人的相册里。
问题
再次强调,这仅仅是个例子,针对每个网站、每个应用的需求,方案也是千变万化的。大家都有各自关注的重点。
所有图片都是不删的,删的只是针对图片的引用页而已,也就是说,如果一个人传了几张私人图片、由自己删除后,如果别人知道图片 URL,则还是可以访问的(或者更简单的情况,某个被设置为限少数人访问的相册的图片 URL 被公开),我不清楚实际运营时这个问题是否会很严重,总之不好回避。
对 MU 的调配是基于人工的,而不是任何高科技的自维护系统。老实说我认为就目前(2010 年)来说,除了 SSD 在某些特殊情况下能值回票价,其他的一些高科技玩意真的看不懂,我目前还很排斥云××类东西,不仅仅是贵,最主要的原因是,即使钱花到了,它们做的还没说的那么好。一个外行可以把技术视为魔法和银弹,但作为从业者,基本的问题是应该知道的,云存储再牛逼,他也是跑在机械硬盘上的,而我们现有的工作,有很重要的一部分是在约定的系数下,加减成本和可靠性两个指标,使其乘积最大。如果磁盘数量在可控范围内,偶尔找人花几个工时维护一下就够,我相信普通的热备就已经是好的选择。除非容量已经上 PB 了,或者到了无缘无故的程度,可反过来说,那些魔法软件又有多少 PB 级案例?总之云××可以等等,第一批冲上去的大都是炮灰。现阶段我还是觉得每GB平均半毛钱不到的硬盘真他妈便宜好用
建议继续学习:
- HFile存储格式 (阅读:14521)
- 我对技术方向的一些反思 (阅读:9851)
- 淘宝图片存储架构 (阅读:9810)
- 海量小文件存储 (阅读:7541)
- HBase技术介绍 (阅读:6737)
- 存储基础知识之——硬盘接口简述 (阅读:6123)
- 在perl中连接和使用sqlite做数据存储 (阅读:5040)
- 如果用户在5分钟内重复上线,就给他发警告,问如何设计? (阅读:4863)
- HTML5本地存储初探(二) (阅读:4385)
- Redis新的存储模式diskstore (阅读:4399)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:Platinum 来源: Soulogic
- 标签: UGC 存储
- 发布时间:2010-08-04 00:00:51
- [66] Go Reflect 性能
- [65] Oracle MTS模式下 进程地址与会话信
- [64] 如何拿下简短的域名
- [59] android 开发入门
- [59] IOS安全–浅谈关于IOS加固的几种方法
- [58] 图书馆的世界纪录
- [58] 【社会化设计】自我(self)部分――欢迎区
- [53] 视觉调整-设计师 vs. 逻辑
- [47] 界面设计速成
- [46] 读书笔记-壹百度:百度十年千倍的29条法则