快生活 - 生活常识大全

中等规模网站的图片存放规划


  好像现在是个网站就允许用户上传头像,其中一部分还允许上传相册、个性背景图之类的东西。对图片的规划各村都有各村的高招,这里只是抛砖引玉、提个醒:当文件膨胀到一定规模的时候再去改就来不及了,在一个项目的草创时期,让一个人多花两个星期的时间来琢磨这个"小"问题也绝对称不上是过度设计。
  我对中等的定义:图片所占空间在 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,你不知道头像的主人是谁:有可能其他人也用相同的图片做头像,或者在某人的相册里。
  问题
网站目录投稿:从筠