zimg是一套国人针对图片处理服务器而设计开发的开源程序,目的是解决图片服务中如下三个问题:
总体思路
想要在展现图片这件事情上有最好的表现,首先需要从整体业务中将图片服务部分分离出来。使用单独的域名和建立独立的图片服务器有很多好处,比如:
zimg采用的是与Facebook相似的策略,将图片处理的大权收归自己所有,绝大部分事情都由自己处理,除非特别必要,最小程度地引入第三方模块。
架构设计
为了极致的性能表现,zimg全部采用C语言开发,总体上分为三个层次,前端http处理层,中间图片处理层和后端的存储层。
代码层面
虽然zimg在二进制实体上没有分模块,上面已经提到了原因,现阶段面向中小型的服务,单机部署即可,但是代码上是分离的。
main.c是程序的入口,主要功能是处理启动参数,部分参数功能如下:
在zimg中图片的唯一Key值就是该图片的MD5,这样既可以隐藏路径,又能减少前端(指zimg前面的部分,可能是你的应用服务器)和zimg本身的存储压力,是避免引入结构化存储部分的关键,所以所有GET请求都是基于MD5拼接而成的。假如你的网站某个地方需要展示一张图片,这个图片原图的大小是1000*1000,但是你想要展示的地方只有300*300,你会怎么做呢?一般还是依靠CSS来进行控制,但是这样的话就会造成很多流量的浪费。为此,zimg提供了图片裁剪功能,你所需要做的就是在图片URL后面加上w=300&h=300(width和height)即可。
在图片上传部分,如果我们的图片服务器前端采用Nginx,上传功能用PHP实现,需要写的代码很少,但是性能很差。首先PHP接收到Nginx传过来的请求后,会根据http协议(RFC1867)分离出其中的二进制文件,存储在一个临时目录里,等我们在PHP代码里使用$_FILES[“upfile”][tmp_name]获取到文件后计算MD5再存储到指定目录,在这个过程中有一次读文件一次写文件是多余的,其实最好的情况是我们拿到http请求中的二进制文件(最好在内存里),直接计算MD5然后存储。于是自己去阅读了PHP的源代码,自己实现了POST文件的解析,让http层直接和存储层连在了一起,提高了上传图片的性能。除了POST请求这个例子,zimg代码中有多处都体现了这种“减少磁盘I/O,尽量在内存中读写”和“避免内存复制”的思想,一点点的积累,最终将会带来优秀的表现。
zimg.c是调用imagemagick处理图片的部分,现阶段zimg服务于存储量在TB级别的单机图片服务器,所以存储路径采用2级子目录的方案。由于Linux同目录下的子目录数最好不要超过2000个,再加上MD5的值本身就是32位十六进制数,zimg就采取了一种非常取巧的方式:根据MD5的前六位进行哈希,1-3位转换为十六进制数后除以4,范围正好落在1024以内,以这个数作为第一级子目录;4-6位同样处理,作为第二级子目录;二级子目录下是以MD5命名的文件夹,每个MD5文件夹内存储图片的原图和其他根据需要存储的版本,假设一个图片平均占用空间200KB,一台zimg服务器支持的总容量就可以计算出来了:
1024 * 1024 * 1024 * 200KB = 200TB
除了路径规划,zimg另一大功能就是压缩图片。从用户角度来说,zimg返回来的图片只要看起来跟原图差不多就行了,如果确实需要原图,也可以通过将所有参数置空的方式来获得。基于这样的条件,zimg.c对于所有转换的图片都进行了压缩,压缩之后肉眼几乎无法分辨,但是体积将减少67.05%。具体的处理方式为:
zcache.c是引入memcached缓存的部分,引入缓存是很重要的,尤其是图片量级上升之后。在zimg中缓存被作为一个很重要的功能,几乎所有zimg.c中的查找部分都会先去检查缓存是否存在。比如:我想要a(代表某MD5)图片裁剪为100*100之后再灰白化的版本,那么过程是先去找a&w=100&h=100&g=1的缓存是否存在,不存在的话去找这个文件是否存在(这个请求所对应的文件名为 a/100*100pg),还不存在就去找这个分辨率的彩色图缓存是否存在,若依然不存在就去找彩色图文件是否存在(对应的文件名为 a/100*100p),若还是没有,那就去查询原图的缓,原图缓存依然未命中的话,只能打开原图文件了,然后开始裁剪,灰白化,然后返回给用户并存入缓存中。
可以看出,上面过程中如果某个环节命中缓存,就会相应地减少I/O或图片处理的运算次数。众所周知内存和硬盘的读写速度差距是巨大的,那么这样的设计对于热点图片抗压将会十分重要。
除了上述核心代码以外就是一些支持性的代码了,比如log部分,md5计算部分,util部分等。
参考链接:
http://zimg.buaa.us/arch_design.html
http://www.laruence.com/2009/09/26/1103.html
- 大流量:对于一些中小型网站来说,流量问题就是成本问题,图片相对于文本来说流量增加了一个数量级,省下的每一个字节都是白花花的银子。所以凡是涉及到图片的互联网应用,都应该统筹规划,降低流量节约开支。
- 高并发:高并发的问题在用户量较低时几乎不会出现,但是一旦用户攀升,或者遇到热点事件,比如网站被人上传了一张爆炸性的新闻图片,短时间内将会涌入大量的浏览请求,如果架构设计得不好,又没有紧急应对方案,很可能导致大量的等待、更多的页面刷新和更多请求的死循环。总的来说,就是要把图片服务的性能做得足够好。
- 海量存储:在介绍Facebook图片存储的文章里提到,当时Facebook用户上传图片15亿张,总容量超过了1.5PB,这样的数量级是一般企业无法承受的。虽然很难做出一个可以跟Facebook比肩的应用,但是从架构设计的角度来说,良好的拓展方案还是要有的。需要提前设计出最合适的海量图片数据存储方案和操作方便的拓容方案,以应对将来不断增长的业务需求。
总体思路
想要在展现图片这件事情上有最好的表现,首先需要从整体业务中将图片服务部分分离出来。使用单独的域名和建立独立的图片服务器有很多好处,比如:
- CDN分流。如果你有注意的话,热门网站的图片地址都有特殊的域名,比如微博的是ww1.sinaimg.cn,人人的是fmn.xnpic.com等等,域名不同可以在CDN解析的层面就做到非常明显的优化效果。
- 浏览器并发连接数限制。一般来说,浏览器加载HTML资源时会建立很多的连接,并行地下载资源。不同的浏览器对同一主机的并发连接数限制是不同的,比如IE8是10个,Firefox是30个。如果把图片服务器独立出来,就不会占用掉对主站连接数的名额,一定程度上提升了网站的性能。
- 浏览器缓存。现在的浏览器都具有缓存功能,但是由于cookie的存在,大部分浏览器不会缓存带有cookie的请求,导致的结果是大量的图片请求无法命中,只能重新下载。独立域名的图片服务器,可以很大程度上缓解此问题。
zimg采用的是与Facebook相似的策略,将图片处理的大权收归自己所有,绝大部分事情都由自己处理,除非特别必要,最小程度地引入第三方模块。
架构设计
为了极致的性能表现,zimg全部采用C语言开发,总体上分为三个层次,前端http处理层,中间图片处理层和后端的存储层。
- http处理层引入基于libevent的libevhtp库,专门处理基本http请求 。
- 图片处理层采用imagemagick库。
- 存储层采用memcached缓存加直接读写硬盘的方案,后期可能会引入TFS4等。
代码层面
虽然zimg在二进制实体上没有分模块,上面已经提到了原因,现阶段面向中小型的服务,单机部署即可,但是代码上是分离的。
main.c是程序的入口,主要功能是处理启动参数,部分参数功能如下:
- -p [port] 监听端口号,默认4869
- -t [thread_num] 线程数,默认4,请调整为具体服务器的CPU核心数
- -k [max_keepalive_num] 最高保持连接数,默认1,不启用长连接,0为启用
- -l 启用log,会带来很大的性能损失,自行斟酌是否开启
- -M [memcached_ip] 启用缓存的连接IP
- -m [memcached_port] 启用缓存的连接端口
- -b [backlog_num] 每个线程的最大连接数,默认1024,酌情设置
在zimg中图片的唯一Key值就是该图片的MD5,这样既可以隐藏路径,又能减少前端(指zimg前面的部分,可能是你的应用服务器)和zimg本身的存储压力,是避免引入结构化存储部分的关键,所以所有GET请求都是基于MD5拼接而成的。假如你的网站某个地方需要展示一张图片,这个图片原图的大小是1000*1000,但是你想要展示的地方只有300*300,你会怎么做呢?一般还是依靠CSS来进行控制,但是这样的话就会造成很多流量的浪费。为此,zimg提供了图片裁剪功能,你所需要做的就是在图片URL后面加上w=300&h=300(width和height)即可。
在图片上传部分,如果我们的图片服务器前端采用Nginx,上传功能用PHP实现,需要写的代码很少,但是性能很差。首先PHP接收到Nginx传过来的请求后,会根据http协议(RFC1867)分离出其中的二进制文件,存储在一个临时目录里,等我们在PHP代码里使用$_FILES[“upfile”][tmp_name]获取到文件后计算MD5再存储到指定目录,在这个过程中有一次读文件一次写文件是多余的,其实最好的情况是我们拿到http请求中的二进制文件(最好在内存里),直接计算MD5然后存储。于是自己去阅读了PHP的源代码,自己实现了POST文件的解析,让http层直接和存储层连在了一起,提高了上传图片的性能。除了POST请求这个例子,zimg代码中有多处都体现了这种“减少磁盘I/O,尽量在内存中读写”和“避免内存复制”的思想,一点点的积累,最终将会带来优秀的表现。
zimg.c是调用imagemagick处理图片的部分,现阶段zimg服务于存储量在TB级别的单机图片服务器,所以存储路径采用2级子目录的方案。由于Linux同目录下的子目录数最好不要超过2000个,再加上MD5的值本身就是32位十六进制数,zimg就采取了一种非常取巧的方式:根据MD5的前六位进行哈希,1-3位转换为十六进制数后除以4,范围正好落在1024以内,以这个数作为第一级子目录;4-6位同样处理,作为第二级子目录;二级子目录下是以MD5命名的文件夹,每个MD5文件夹内存储图片的原图和其他根据需要存储的版本,假设一个图片平均占用空间200KB,一台zimg服务器支持的总容量就可以计算出来了:
1024 * 1024 * 1024 * 200KB = 200TB
除了路径规划,zimg另一大功能就是压缩图片。从用户角度来说,zimg返回来的图片只要看起来跟原图差不多就行了,如果确实需要原图,也可以通过将所有参数置空的方式来获得。基于这样的条件,zimg.c对于所有转换的图片都进行了压缩,压缩之后肉眼几乎无法分辨,但是体积将减少67.05%。具体的处理方式为:
- 图片裁剪时使用LanczosFilter滤镜;
- 以75%的压缩率进行压缩;
- 去除图片的Exif信息;
- 转换为JPEG格式。
zcache.c是引入memcached缓存的部分,引入缓存是很重要的,尤其是图片量级上升之后。在zimg中缓存被作为一个很重要的功能,几乎所有zimg.c中的查找部分都会先去检查缓存是否存在。比如:我想要a(代表某MD5)图片裁剪为100*100之后再灰白化的版本,那么过程是先去找a&w=100&h=100&g=1的缓存是否存在,不存在的话去找这个文件是否存在(这个请求所对应的文件名为 a/100*100pg),还不存在就去找这个分辨率的彩色图缓存是否存在,若依然不存在就去找彩色图文件是否存在(对应的文件名为 a/100*100p),若还是没有,那就去查询原图的缓,原图缓存依然未命中的话,只能打开原图文件了,然后开始裁剪,灰白化,然后返回给用户并存入缓存中。
可以看出,上面过程中如果某个环节命中缓存,就会相应地减少I/O或图片处理的运算次数。众所周知内存和硬盘的读写速度差距是巨大的,那么这样的设计对于热点图片抗压将会十分重要。
除了上述核心代码以外就是一些支持性的代码了,比如log部分,md5计算部分,util部分等。
参考链接:
http://zimg.buaa.us/arch_design.html
http://www.laruence.com/2009/09/26/1103.html
No comments:
Post a Comment