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

使用zimg搭建图片服务器

钱魏Way · · 1,192 次浏览
!文章内容如有错误或排版问题,请提交反馈,非常感谢!

一般的大型网站都会将图片存放在专门的服务器,这样可以很好的提升网站的性能。比较简单的方式是采用云厂商提供的服务,比如七牛云、又拍云等。今天要介绍的是一款开源的实现方案 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 的部署安装(centos7)

安装依赖库:

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再封装图片服务。

参考链接:

发表回复

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