器→工具, 开源项目, 术→技巧, 研发, 运维

使用zimg搭建图片服务器

钱魏Way · · 932 次浏览

一般的大型网站都会将图片存放在专门的服务器,这样可以很好的提升网站的性能。比较简单的方式是采用云厂商提供的服务,比如七牛云、又拍云等。今天要介绍的是一款开源的实现方案zing。

zimg简介

zimg是一套国人针对图片处理服务器而设计开发的开源程序,目的是解决图片服务中如下三个问题:

  • 大流量:对于一些中小型网站来说,流量问题就是成本问题,图片相对于文本来说流量增加了一个数量级,省下的每一个字节都是白花花的银子。所以凡是涉及到图片的互联网应用,都应该统筹规划,降低流量节约开支。
  • 高并发:高并发的问题在用户量较低时几乎不会出现,但是一旦用户攀升,或者遇到热点事件,比如网站被人上传了一张爆炸性的新闻图片,短时间内将会涌入大量的浏览请求,如果架构设计得不好,又没有紧急应对方案,很可能导致大量的等待、更多的页面刷新和更多请求的死循环。总的来说,就是要把图片服务的性能做得足够好。
  • 海量存储:Facebook用户上传图片上亿张,总容量超过了nPB,这样的数量级是一般企业无法承受的。虽然很难做出一个可以跟Facebook比肩的应用,但是从架构设计的角度来说,良好的拓展方案还是要有的。需要提前设计出最合适的海量图片数据存储方案和操作方便的拓容方案,以应对将来不断增长的业务需求。

以上三个问题,其实也是相互制约和钳制的,比如要想降低流量,就需要大量的计算,导致请求处理时间延长,系统单位时间内的处理能力下降;再比如为了存储更多的图片,必然要在查找上消耗资源,同样也会降低处理能力。所以,图片服务虽然看起来业务简单,实际做起来也不是一件小事。

zimg的定位:

  • zimg是图像存储和处理服务器。您可以使用URL参数从zimg获得压缩和缩放的图像。
  • zimg的并发I/O,分布式存储和及时处理能力非常出色。您不再需要在图像服务器中使用nginx。在基准测试中,zimg可以在高并发级别上每秒处理3000个以上的图像下载任务和每秒90000个以上的HTTP回显请求。性能高于PHP或其他图像处理服务器。
  • 用于中小型的图床服务

以下是zimg支持的功能:

  • 所有图片默认返回质量为75%,JPEG格式的压缩图片,这样肉眼无法识辨,但是体积减小
  • 获取宽度为x,被等比例缩放的图片
  • 获取旋转后的图片
  • 获取指定区域固定大小的图片
  • 获取特定尺寸的图片,由于与原图比例不同,尽可能展示最多的图片内容,缩放之后多余的部分需要裁掉
  • 获取特定尺寸的图片,要展示图片所有内容,因此图片会被拉伸到新的比例而变形
  • 获取特定尺寸的图片,但是不需要缩放,只用展示图片核心内容即可
  • 获取按指定百分比缩放的图片
  • 获取指定压缩比的图片
  • 获取去除颜色的图片
  • 获取指定格式的图片
  • 获取图片信息
  • 删除指定图片
  • 以上这些功能的提供,仅需要一个url+特定的参数,通过get方式就可以完成。

zimg的设计思路

想要在展现图片这件事情上有最好的表现,首先需要从整体业务中将图片服务部分分离出来。使用单独的域名和建立独立的图片服务器有很多好处,比如:

  • CDN分流。如果你有注意的话,热门网站的图片地址都有特殊的域名,比如微博的是sinaimg.cn,人人的是fmn.xnpic.com等等,域名不同可以在CDN解析的层面就做到非常明显的优化效果。
  • 浏览器并发连接数限制。一般来说,浏览器加载HTML资源时会建立很多的连接,并行地下载资源。不同的浏览器对同一主机的并发连接数限制是不同的。如果把图片服务器独立出来,就不会占用掉对主站连接数的名额,一定程度上提升了网站的性能。
  • 浏览器缓存。现在的浏览器都具有缓存功能,但是由于cookie的存在,大部分浏览器不会缓存带有cookie的请求,导致的结果是大量的图片请求无法命中,只能重新下载。独立域名的图片服务器,可以很大程度上缓解此问题。

图片服务器被独立出来之后,会面临两个选择,主流的方案是前端采用Nginx,中间是PHP或者自己开发的模块,后端是物理存储;比较特别一些的,比如Facebook,他们把图片的请求处理和存储合并成一体,叫做haystack,这样做的好处是,haystack只会处理与图片相关的请求,剥离了普通http服务器繁杂的功能,更加轻量高效,同时也使部署和运维难度降低。zimg采用的是与Facebook相似的策略,将图片处理的大权收归自己所有,绝大部分事情都由自己处理,除非特别必要,最小程度地引入第三方模块。

zimg的架构设计

为了极致的性能表现,zimg全部采用C语言开发,总体上分为三个层次,前端http处理层,中间图片处理层和后端的存储层。下图为zimg架构设计图:

  • http处理层引入基于libevent的libevhtp库,专门处理基本http请求 。
  • 图片处理层采用imagemagick库。
  • 存储层采用memcached缓存加直接读写硬盘的方案,后期可能会引入TFS4等。

为了避免数据库带来的性能瓶颈,zimg不引入结构化数据库,图片的查找全部采用哈希来解决。事实上图片服务器的设计,是一个在I/O与CPU运算之间的博弈过程,最好的策略当然是继续拆:CPU敏感的http和图片处理层部署于运算能力更强的机器上,内存敏感的cache层部署于内存更大的机器上,I/O敏感的物理存储层则放在配备SSD的机器上,但并不是所有人都能负担得起这么奢侈的配置。zimg折中成本和业务需求,目前只需要部署在一台服务器上。由于不同服务器硬件不同,I/O和CPU运算速度差异很大,很难一棒子定死。zimg所选择的思路是,尽量减少I/O,将压力放在CPU上,事实证明这样的思路基本没错,在硬盘性能很差的机器上效果更加明显;即使以后SSD全面普及,CPU的运算能力也会相应提升,总体来说zimg的方案也不会太失衡。

zimg的代码实现

虽然zimg在二进制实体上没有分模块,上面已经提到了原因,现阶段面向中小型的服务,单机部署即可,但是代码上是分离的。

main.c

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,酌情设置

zhttpd.c

zhttpd.c是解析http请求的部分,分为GET和POST两大部分,GET请求会根据请求的URL参数去寻找图片并转给图片处理层处理,最后将结果返回给用户;POST接收上传请求然后将图片存入计算好的路径中。为了实现zimg的总体设计愿景,zhttpd承担了很大部分的工作,也有一些关键点,下面捡重点的说一下:

  • 在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

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

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部分等。

zimg的部署安装(centos 7)

安装依赖库:

sudo yum install -y  wget openssl-devel cmake libevent-devel libjpeg-devel giflib-devel libpng-devel libwebp-devel ImageMagick-devel libmemcached-devel 
sudo yum install -y glibc-headers gcc-c++
sudo yum install -y build-essential nasm

安装依赖:

# openssl
mkdir /usr/local/zimg/openssl
cd /usr/local/zimg/openssl
wget http://www.openssl.org/source/openssl-1.0.1i.tar.gz
tar zxvf openssl-1.0.1i.tar.gz
cd openssl-1.0.1i
./config shared --prefix=/usr/local --openssldir=/usr/ssl
make && make install

# cmake
mkdir /usr/local/zimg/cmake
cd /usr/local/zimg/cmake
wget http://www.cmake.org/files/v3.0/cmake-3.0.1.tar.gz
tar xzvf cmake-3.0.1.tar.gz 
cd cmake-3.0.1
./bootstrap --prefix=/usr/local 
make && make install

# libevent
mkdir /usr/local/zimg/libevent
cd /usr/local/zimg/libevent
wget http://cloud.github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
tar zxvf libevent-2.0.21-stable.tar.gz
cd libevent-2.0.21-stable
./configure --prefix=/usr/local 
make && make install

# libjpeg-turbo
mkdir /usr/local/zimg/libjpeg-turbo
cd /usr/local/zimg/libjpeg-turbo
wget https://downloads.sourceforge.net/project/libjpeg-turbo/1.3.1/libjpeg-turbo-1.3.1.tar.gz
tar zxvf libjpeg-turbo-1.3.1.tar.gz
cd libjpeg-turbo-1.3.1
./configure --prefix=/usr/local --with-jpeg8
make && make install

# webp
mkdir /usr/local/zimg/webp
cd /usr/local/zimg/
wget http://downloads.webmproject.org/releases/webp/libwebp-0.4.1.tar.gz
tar zxvf libwebp-0.4.1.tar.gz
cd libwebp-0.4.1
./configure
make
sudo make install

# jpegsrc
mkdir /usr/local/zimg/jpegsrc
cd /usr/local/zimg/
wget http://www.ijg.org/files/jpegsrc.v8b.tar.gz
tar -xf  jpegsrc.v8b.tar.gz
cd jpeg-8b
./configure --prefix=/usr/local --enable-shared --enable-static
make && make install

# imageMagic
mkdir /usr/local/zimg/imageMagick
cd /usr/local/zimg/
wget http://www.imagemagick.org/download/ImageMagick.tar.gz
tar zxvf ImageMagick.tar.gz
cd ImageMagick-6.9.1-10
./configure  --prefix=/usr/local 
make && make install

# libmemcached
wget https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz
tar zxvf libmemcached-1.0.18.tar.gz
cd libmemcached-1.0.18
./configure -prefix=/usr/local 
make && make install

可选的插件:

# memcached
wget http://www.memcached.org/files/memcached-1.4.19.tar.gz
tar zxvf memcached-1.4.19.tar.gz
cd memcached-1.4.19
./configure --prefix=/usr/local
make
make install

# beansdb
git clone https://github.com/douban/beansdb
cd beansdb
./configure --prefix=/usr/local
make

# benseye
git clone git@github.com:douban/beanseye.git
cd beanseye
make

# SSDB
wget --no-check-certificate https://github.com/ideawu/ssdb/archive/master.zip
unzip master
cd ssdb-master
make

# twemproxy
git clone git@github.com:twitter/twemproxy.git
cd twemproxy
autoreconf -fvi
./configure --enable-debug=log
make
src/nutcracker -h

构建zimg

cd /usr/local
#git clone https://github.com/buaazp/zimg -b master --depth=1
cd zimg   
make

安装成功后:

cd /usr/local/zimg/bin
./zimg conf/zimg.lua

打开http://localhost:4869看是否安装成功。

如果嫌手动安装太麻烦,就直接使用docker镜像

# 拉取zimg镜像
$ docker pull iknow0612/zimg
# 启动zimg容器
$ docker run -it -d -p 4869:4869 -v /data/zimg/:/zimg/bin/img --name my_zimg iknow0612/zimg sh app.sh

可以自己基于zimg再封装图片服务。

参考链接:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注