2011年(13)
分类: 系统运维
2011-01-23 19:32:53
假设每张图片平均的尺寸在200K左右,大概需要占据的存储空间是190G,如果您的图片没有进行压缩,那么平均会在1M左右,整体上也就是1TB左右的空间,综合考虑起来,如果是一个SNS网站,在20万用户的时候会接近这个规模的。
1000万我所指的是图片请求,而不是通常所提到的Page View,不去太追究具体每个页面会有多少的图片需要请求,以平均15个图片来计算,大致是每天70万的PageView,应该来说这是一个中等规模的网站。
很明显,一个20万用户,每天70PV的网站是我们图片服务优化设计的基准点,这时候我们可以考虑在下列的几个环节做优化:
l 缓存:尽可能降低客户端的重复请求,尽可能降低同一图片在服务器的重复处理
l 存储:尽可能服务器代码获取原始图片的时间
l 网络结构:在硬件设计上尽可能让系统架构可扩展和易于管理
下面我们依照上述的基准考虑,来看看可以在那些环节上都手术,从而提高图片服务的整体能力:
1. 为客户端增加缓存的能力
我们知道,许多图片应用都是需要请求一些小图片,如用户头像,那么这种情况下对于图片重复请求的情况则不可避免。同时我们也知道,浏览器都是有缓存的,为此我们应该尽让让浏览器从缓存中取的图片,而不是重复的请求。有两种方式来为实现客户端的缓存:
1. 向客户端增加过期或者缓存控制
对于相对静态的图片,我们可以增加过期策略(Expired),如果我们发送了下列的Http header:
Expires:Thu, 15 Apr 2010 20:00:00 GMT
则表示当前图片到2010年5月15日20点过期,在这之前,如果浏览器已经存在图片,则不会从新请求。
对于动态图片,如在用户上传的图片里写入签名档,我们则可以通过增加Cache-Control的header来帮助浏览器根据条件判断是否重新请求。
关于expires和cache-control的详细说明,请参阅相关的文档,这里不在螯述。
2. 使用etag来强行要求客户端不重新请求
ETag的作用是告诉浏览器缓存,缓存中的内容是否已经变化的一种机制。和Last Modified结合使用,告知浏览器缓存的生成时间。您可以简单地将etag理解为服务器端对于对象的唯一名字(token)。
假设我们发送了这样的header: ETag: "50b1c1d4f775c61:df3"
当浏览器第二次请求该图片时,会用这样的格式查询:
If-None-Match: W/"50b1c1d4f775c61:df3"
如果图片没有作任何改变,服务器将会给浏览器发送响应304和空的响应体。
HTTP/1.0 304 Not Modified
正是如此,也避免了图片的重复请求。
2. 为图片处理的结果增加缓存
从字面上不难理解,比如请求了图片的缩略图,图片处理是相当耗费资源的,那么我们应该避免重复的图片转换,简单一点地说,张上请求了abcdef.jpg的80x100的缩略图,服务器为此已经生产了缩略图,然后发送给张三,那么李四再请求这张图片的时候,就应该将处理结果发送给李四,而不是再进行一遍的图片缩放处理。
而如何缓存这些处理完毕的结果呢?最简单的做法是放在内容,下一次请求的时候直接从内存将处理结果发送给浏览器。但内存资源始终是有限的,而图片本身是相对占据空间的,在访问量比较大的情况下,如果请求的图片比较分散,这时候缓存的命中率是一个很大的问题。
为了解决存储的问题,我们可以提出另外一个方案:将处理结果存储在文件系统,下一次请求直接从文件里读取输出结果。存储空间的问题也就解决了,但是会带来另外一个问题,100万张的图片生成不同尺寸的图片,那就是100万的倍数,此时会让缓存文件变得无比巨大,甚至大过原始图片的存储空间,那应该如何解决呢?
我们刚才讨论的核心是文件缓存和内存缓存这两种不同的缓存方式,两种方式都会存在一些不足之处,为了更好地了解优缺点,我们做一个比较:
内存缓存 |
文件缓存 |
l 优势 n 高性能,无任何额外的处理 l 劣势 n 能够支持的存储有限 n 在文件数比较大的时候缓存命中率比较低 |
l 优势 n 支持比较大的存储(比如100G) n 整体来性能可以接受 l 劣势 n 在图片数量比较大的情况下,会导致缓存占据太多的空间 n 业务出现变更的时候,缓存的管理是一个大问题 |
从列表我们可以看到各自的特点,从倾向来说,文件缓存会有更大的优势,但并不是没有缺点,尤其在文件数量庞大的情况下,我们之前的讨论也提到过,文件系统对于海量的小文件支持是没有优势的,而像80x100的用户头像,大多情况下不超过10K,你要维护一个超过100万小文件的地目录也是有困难的。
这个时候我们需要定义缓存策略,从而在性能和可管理性及其存储之间找到一个平衡点:
ü 数量巨大的小图片,因为不占据太多的存储空间,但是从业务角度来说,却是需要被频繁访问的,从性能的角度考虑,可以把这些数据放在内存中,在频繁访问的情况下也就不会造成太大的性能瓶颈
ü 对于中等尺寸的图片,相对来说访问的频率没有小图片高,但是在存储上也没有原始图片大,可以考虑将这些图片缓存在文件系统中
ü 对于一些比较大的图片,因为访问的频率比较低,可以考虑不缓存,直接推送到客户端。
在这样的原则基础上,我们可以用下面的两个指标去决定到底我们需要采用怎样的缓存策略:
图片访问频率
图片的字节大小
图片访问频率越高,越倾向放在内存,反之越倾向不缓存
图片字节数越大,越倾向文件缓存,反之放在内存
至于实际情况应该如何解决,只能够根据您的具体业务做一些合适的调整,在这里我只是介绍了图片缓存的一些基本原则。
到目前为止,我们已经进一步完善了图片服务,在单机情况下,我们相对详细地考虑了图片服务的各个方面,如果业务压力不是极其大的情况下,做到这点应该完全可以胜任了。下图则描述了目前图片服务的组件结构:
从图上可以看到,针对图片服务我们做了一些抽象,Cache Service负责所有涉及到缓存的管理,而Image Processor负责图片的转换服务,Image Service会利用这三个核心的组件协同工作,把原图片存储的工作交给Storage Service来完成,将图片转换的工作交给Image Processor,将缓存的工作交给Cache Service来处理。
到此,我们能够清晰地描述图片服务的工作流程了:
1. 接受浏览器的请求
2. 提交给缓存服务,如果已经做了缓存,从缓存中输出图片,否则进行步骤3
3. 从Storage Service中提取原始图片
4. 检查客户端的参数请求,如果包括图片转换命令,则提交到Image Processor,否则调到步骤7
5. 调用图片处理的各个子部件(resize,crop等)进行图片转化的工作
6. 将处理结果提交给Cache Service
7. 输出图片
到目前为止,我们还没有对Storage Service动手术,不过对于一般性的应用,到这个阶段已经足够了,而下一步我们将讨论更大数据量的存储,看看应该在那些环节上继续调整。
我们的所有讨论都是限制在一台PC Server上讨论的,后续我们要尝试把原始图片的存储和前端服务分离。