现实世界的业务会比我们之前描述的复杂许多,图片也不仅仅是上传就完了,还需要作很多的处理,那我们就继续发挥想象,剖析一下图片应用中还会有哪些“必需”的功能:
n 用户头像应该是要有多个尺寸的,以方面在不同的页面显示不同大小的图片
n 我需要给图片打上水印
n 我需要为特定设备(如手机)提供图片显示的支持
n 图片需要做一些特效处理
n ……
尽管并不是所有的需求我们都需要,但是总会有一些你需要对图片做后续处理的,怎样更加平滑地处理这些问题,在不同的业务场景,不同的架构师会提出不同的解决方案,但是核心的考虑只有两个:
1. 如何能够灵活地满足目前的业务需求
2. 如何尽可能地优化资源,包括文件存储和网络带宽
说到具体的技术实现,也有两种思路,那就是“预先生成”和“按需生成”,很难一下子去评判具体哪个方案更好,哪个方案更加节省成本,那么我们就来看看这两种解决方案的实现考虑:
预先生成:从字面上很容易理解,那就是根据业务的需要提前生成不同规格的图片,比如用户头像,网站会有20x20,80x100,200x250和原图这样的四种规格,同时原图需要打上WebSecretApp.COM的水印,与此同时,我的网站支持wap访问,因此我还需要生成针对手机优化的头像。我们的处理程序也就需要生成下列的图片地址:
针对这样的情况,我们需要预先生成5个图片,然后根据一定的业务规则存储到不同的文件夹,之后将图片列表的信息存储在文件或者数据库中,前端界面根据不同的业务需要限显示不同的图片地址。
按需生成:简单地说我们不会实现做图片生成的工作,服务器只存储一份原始的图片,当然这个的“原始”是相对于后来业务的,在用户上传的图片(如数码相机里头的)存储到服务器之前,我们可能作了一轮的压缩工作,如从2040x 1536压缩成1024x768。为了能够动态生成图片,我们还需要一个图片服务处理,如getimage.php,这个时候所有的图片处理地址变为:
http://img.websecretapp.com/getimage.php?file=/user/2008/04/30/abcdef.jpg&size=80x100
其中红色部分为实际的图片地址,而绿色部分参数size则是根据自己的业务而定,从某些角度来看,这样的生成方式对于业务来说能够带来相对大的灵活度,在也许增加或者改变时,我们只需要修改getimage.php就可以了,但不是没有缺点,下面我们就来看看不同的图片生成方式的优缺点:
预先生成:
优点
|
缺点
|
ü url地址确定,能够支持静态文件缓存
ü 一旦生成完毕,不需要额外的处理
ü 性能比较高
|
ü 如果业务相对复杂,预处理需要比较多的时间
ü 业务逻辑预先定义,一旦出现变动,可能出现不协调的情况,比如需要打水印,已经处理过的图片不能够支持
|
按需生成:
优点
|
缺点
|
ü 能够灵活支持不同的图片生成
ü 一旦业务出现变动,维护和改动的成本比较低
|
ü 在客户请求时才生成,不可避免地影响性能
ü 不利于缓存的支持
|
从比较情况来看,各有优劣,就如天平的两端,总有轻重之分,那么从架构设计的考虑呢,我们需要作下面的考虑:
s 高性能:系统要能够支撑尽可能高的访问量,让用户得到良好的用户体验
s 可靠性:架构的设计要尽可能确保服务的持续性,也就是不宕机
s 安全性:我们需要让系统能够支持防盗链,授权访问的功能
s 可伸缩性:让基础服务支持向外扩展(scale-out),也就是在单一主机性能达到瓶颈的时候能够通过增加机器来解决
s 可扩展性:在业务出现变动或者增加的情况下,我们的基础服务能够相对容易地通过扩展来支持
s 可管理性:这是一个大家容易忽略的主题,对于一个业务支撑的基础服务,我们要同时考虑它的管理成本,如监控、维护和备份的代价有多大
从性能和可靠性来说,静态文件无疑是最优的,而市场上也有非常多的成熟解决方案来支持静态文件服务,而动态生成的方式,则让我们在安全、可扩展和可伸缩上具有更大的灵活度,而我们的架构设计正是要在这些设计原则中找到平衡。
既然纯粹的预先生成和纯粹的按需生成都存在一些不足之处,那么是否能够有更好的解决方案呢,这也就是我所要探讨的主要内容,回顾一下刚才的讨论,我们需要为图片服务增加这么一些功能:
1. 能够根据用户上传的图片需要生成应对不同业务的图片,主要包括不同尺寸的图片、水印增加和图片效果处理。
2. 保持高性能,不会因为业务的变动而导致性能称为瓶颈
3. 业务变动时,能够快速通过调整来满足需求
4. 允许通过增加机器来解决业务压力增长的情况
5. 能够对用户上传的内容进行有效地管理
从我们刚才的分析可以看到,使用动态生成最大的优势在于灵活性,而预先生成的最大优势在于性能,结合起来说,我们需要能够动态地生成业务需要的图片,同时又能够获得静态文件的性能优势。回答这个问题并不难:缓存。于是“动态生成+缓存”也就成了我们后续讨论的重点。
如何动态生成,又如何缓存呢?
首先,我们需要解决动态生成的问题,也就是说能够按照“按需生成”用户所期望的图片,还是以前面的图片来举例:
http://img.websecretapp.com/getimage.php?file=/user/2008/04/30/abcdef.jpg&size=80x100
用户请求的具体文件名为:/user/2008/04/30/abcdef.jpg,希望的尺寸为80x100。如果加上要打上水印呢?我们则可以将URL改写为:
http://img.websecretapp.com/getimage.php?file=/user/2008/04/30/abcdef.jpg&size=80x100&iswatermark=true
从请求的语义来理解,我们可以表述成下面的行为:
1. 请给我一个名为/user/2008/04/30/abcdef.jpg的图片
2. 将图片缩放成80x100
3. 在这个图片上加上水印
其中第二步和第三步我们可以理解为两个图片的处理操作,第一步则是代表请求的图片源,如果还需要图像截取,去除红眼等等操作呢?道理很简单,加上不同参数不就可以了吗?只要我的服务器处理程序支持这样的参数解析,为了让URL更加具有可读性,我们可以做一个urlrewrite,让图片服务更加具有现实意义。
http://img.websecretapp.com/user/2008/04/30/abcdef.jpg?size=80x100&iswatermark=true
如此一来,是不是让您的图片服务看起来更加“聪明”了呢?
http://img.websecretapp.com/user/2008/04/30/abcdef.jpg 代表请求原图
如果附加了参数,只要这个命令被服务端的代码所支持,就能够根据用户需要生成不同的最终图片,至于如何进行urlrewrite,不同的编程语言在不同的平台下会有不同的实现,也不是我这里讨论的重点。到此我们也完成了对于图片服务的升级,它将具备这样的能力:
1. 图片服务的地址虽然没有改变,但是开始支持参数请求
2. 图片的处理依赖于客户端请求的参数,具体命令(如size,iswatermark)则具体依赖于服务器代码的实现
3. 对于多个处理参数,目前采用串行执行的方式,也就是说排在前面的首先执行,如我们上面的例子,首先执行图片缩小,然后才执行水印的操作
下图描述了我们目前图片服务的处理流程

到
目前为止,我们在技术功能上已经实现了一个更加完整的图片服务,但是还有一些问题没有解决,比如性能问题,比如可扩展问题等等,如果在数据量和业务量都不
是很大的情况下,这样的图片服务已经足够灵活,大多程度也能够满足我们的需求了,但如果您面对的是大规模网站的系统呢,到目前为止的处理是远远不够的。
接下来的篇幅,我们会对业务做一个基本的假设:我们的网站有100万张以上的图片,我们每天至少需要考虑1000万用户请求。在这样的基础之上,我们需要对缓存,对文件存储和网络结构作一些框架性的调整。 |