Nginx 利用 Nginx 的 ngx_http_image_filter_module 做实时的图片缩略图

huacnlee · 2016年11月02日 · 最后由 huacnlee 回复于 2016年11月14日 · 18968 次阅读
本帖已被管理员设置为精华贴

你还在用 ImageMagick 生成网站的上传图片缩略图吗?其实有更好的方法一部到位,简单有效。

现而今有非常多的云存储服务支持图片空间,并根据设定的规则来生成空间里面的图片缩略图了,例如 UpYun、Aliyun OSS 都支持。

但有时候我们会因为一些其他的考虑(例如:价格因素),选择本地文件存储上传文件,这个时候,我们如何实现图片缩略图呢?

其实 Nginx 内置了 ngx_http_image_filter_module 可以帮助你处理图片:

  • 缩放
  • 裁剪
  • 调整图片品质
  • 旋转
  • 锐化

我们常用的可能就是缩放和裁剪了,根据业务和设计需要,在合适的位置不同尺寸的缩略图。

安装

可能一些标准的 Nginx 安装包没有带这个 module 的,你需要使用 Nginx 官方的源安装,并额外安装 nginx-module-image-filter 这个包:

curl -O http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key
sudo bash -c 'echo "deb http://nginx.org/packages/ubuntu/ $(lsb_release -cs) nginx
deb-src http://nginx.org/packages/ubuntu/ $(lsb_release -cs) nginx" > /etc/apt/sources.list.d/nginx-stable.list'
sudo apt-get update
sudo apt-get install -y nginx nginx-module-image-filter

也可以直接用做好的 安装脚本

curl -sSL https://git.io/vVHhf | bash

场景

以 Ruby China 的场景为例,我设计了下面几种不同的缩略图版本:

版本名称 限定尺寸 (px) 缩略方式
large 1920 限定宽度,高度自适应
lg 192x192 固定宽度和高度
md 96x96 固定宽度和高度
sm 48x48 固定宽度和高度
xs 32x32 固定宽度和高度

配置 Nginx

假定我们的上传文件存放在 /var/www/homeland/public/uploads 里面。

下面是 Ruby China 这个缩略图规则的完整 Nginx 配置:

/etc/nginx/nginx.conf

user nobody;
worker_processes auto;
pid /var/www/pids/nginx.pid;
daemon on;

# 载入 ngx_http_image_filter_module
load_module modules/ngx_http_image_filter_module.so;

http {
   # ... 省略
}

/etc/nginx/conf.d/ruby-china.conf

proxy_cache_path /var/www/cache/uploads-thumb levels=1:2 keys_zone=uploads_thumb:10m max_size=50G;

server {
  listen 80 default_server;
  listen 443 ssl http2;

  root /var/www/homeland/public;

  location /uploads {
    expires 7d;
    gzip_static on;

    add_header Cache-Control public;
    add_header X-Pownered "nginx_image_filter";
    # HTTP Response Header 增加 proxy_cache 的命中状态,以便于以后调试,检查问题
    add_header X-Cache-Status $upstream_cache_status;

    proxy_pass http://127.0.0.1/_img/uploads;
    # 将缩略图缓存在服务,避免每次请求都重新生成
    proxy_cache uploads_thumb;
    # 当收到 HTTP Header Pragma: no-cache 的时候,忽略 proxy_cache
    # 此配置能让浏览器强制刷新的时候,忽略 proxy_cache 重新生成缩略图
    proxy_cache_bypass $http_pragma;
    # 由于 Upload 文件一般都没参数的,所以至今用 host + document_uri 作为
    proxy_cache_key "$host$document_uri";
    # 有效的文件,在服务器缓存 7 天
    proxy_cache_valid 200 7d;
    proxy_cache_use_stale error timeout invalid_header updating;
    proxy_cache_revalidate on;
    # 处理 proxy 的 error
    proxy_intercept_errors on;
    error_page   415 = /assets/415.png;
    error_page   404 = /assets/404.png;
  }

  # 原始图片
  location /_img/uploads {
    alias /var/www/homeland/public/uploads/$filename;
    expires 7d;
  }

  # 缩略图
  location ~* /_img/uploads/(.+)!(large|lg|md|sm|xs)$ {
    set $filename /uploads/$1;

    if (-f $filename) {
      break;
    }

    # 根据 URL 地址 ! 后面的图片版本来准备好需要的参数(宽度、高度、裁剪或缩放)
    set $img_version $2;
    set $img_type resize;
    set $img_w    -;
    set $img_h    -;
    if ($img_version = 'large') {
      set $img_type resize;
      set $img_w    1920;
    }
    if ($img_version = 'lg') {
      set $img_type crop;
      set $img_w    192;
      set $img_h    192;
    }
    if ($img_version = 'md') {
      set $img_type crop;
      set $img_w    96;
      set $img_h    96;
    }
    if ($img_version = 'sm') {
      set $img_type crop;
      set $img_w    48;
      set $img_h    48;
    }
    if ($img_version = 'xs') {
      set $img_type crop;
      set $img_w    32;
      set $img_h    32;
    }
    rewrite ^ /_$img_type;
  }

  # 缩放图片的处理
  location /_resize {
    alias /var/www/homeland/public$filename;
    image_filter resize $img_w $img_h;
    image_filter_jpeg_quality 95;
    image_filter_buffer         20M;
    image_filter_interlace      on;
  }

  # 裁剪图片的处理
  location /_crop {
    alias /var/www/homeland/public$filename;
    image_filter crop $img_w $img_h;
    image_filter_jpeg_quality 95;
    image_filter_buffer         20M;
    image_filter_interlace      on;
  }
}

你可能会觉得上面为何写得这么绕啊!

没办法,Nginx 不支持在 if {} 这个 block 里面用 image_filter 函数,image_filter 的第一个参数 resize/crop 也不能用变量的方式传输,所以...

然后,重启 Nginx,就可以尝试了。

注意点

  • 由于开启了 proxy_cache 缩略图将会在服务器上以文件的形式存在,你需要确保每次上传新文件名尽可能的是唯一的(例如用时间,或文件内容 MD5 作为文件名,参考 CarrieWave 文件名设计
  • 浏览器强制刷新,会发起 Pragma: no-cacheRequest Header,Nginx 会忽略 proxy_cache 重新生成图片。

效果演示

扩展阅读

lgn21st 将本帖设为了精华贴。 11月02日 16:37

👍

我记得 nginx 有个 upload module 可以配合来做上传,这样就把上传从 rails 的 app server 里独立出来,类似自己做了一个 内网的小型 s3 一样的 upload server,rails 里配合 carrierwave 直接向 upload server 传,也没有 app server 部署多台时文件上传都基于本 app server 的问题。如果要使用云服务可配合其他 upyun qiniu 或 cdn 等的回源抓取功能,也保证了文件一直自己有独立的一份存储 ,不像直接使用了云存储后,想迁移到其他云存储时的困难,如 ruby-china 目前使用 upyun 仅使用 upyun 的 ftp 功能把源文件拉回来好麻烦,而 qiniu/s3 似乎更难拿到整个备份(一旦上云,欲罢不能)

同样 https://github.com/ruby-china/homeland/issues/512 也可以用 nginx 的 proxy 来做,如果存则直接 response,不存在则从远端 down 下来再 response

http://distinctplace.com/infrastructure/2013/09/18/use-nginx-to-proxy-files-from-remote-location-using-x-accel-redirect/

https://gist.github.com/bennylope/1297267

访问 Original 是 404?

好文

不过我觉得类似七牛那种方式更炫酷,不用写死版本号,想怎么裁剪就怎么裁剪,换设计的时候不用列一大堆图片的尺寸定义

#5 楼 @bluecoda qiniu 也可定义版本号 参数在后台设置,如果不设置,允许参数自己定,恶意爬虫会给你生成一大堆文件,存储空间是要钱的。

#5 楼 @bluecoda 这个可以结合 lua 自定义一下裁剪部分 是可以的

不错,,我们还在用 ImageMagick 生成图片的

这种方式在实际应用时,最大的风险是遇到不断请求各种大小,短时间内塞爆 cache 的恶意请求。不知道这个 module 对此有没有防御手段。

#10 楼 @kgen 固定尺寸了的,每个尺寸都有 Cache

#11 楼 @huacnlee 哦,我再看了下你的配置,是我误解了。你并不是打算让 ngx_http_image_filter_module 根据请求参数,生成任意大小的缩略图。

这种办法有个坏处就是 image 保存在自己的服务器上,会占用服务器的带宽,如果请求图片的流量大容易影响网站的服务。

#7 楼 @kewin 没想到这儿看到你了。。

#13 楼 @yzhrain 自己存一份 搭在单独的 server 上,访问走 cdn 就可以了。

@huacnlee 建议做上 quality 设置 xs sm 这几个 version 图片质量不需要那么高

另外 每个图片 都可应用上头像的 version 似乎比较浪费,不知道这个可以根据目录不同分别编程不。这样类似云存储的不同 bucket 可设置不同的 style 来设置缩略 如: avatar 可以有 xs sm md lg 而 photo 可以有 large 其他模块的图片 也许需要不同的 size 和质量的图片版本。

如果任一个图片都可以应用上所有 version 不排除恶意爬虫会生好多无用图片。

#14 楼 @firebroo 帅哥这论坛我 5 年前就在哦 呵呵 你这啥都涉猎 不错 加油 看好你

#17 楼 @kewin 兴趣来了瞎玩玩~。~

#15 楼 @huobazi 这样做疑似性价比不及放在类似 S3 之类的服务上啊

#19 楼 @yzhrain 不考虑迁移的话 直接放入云存储就好。

如果本地存储图片,而又想实时获取缩略图的话,可以使用 dragonfly 这个 gem,也是使用的 ImageMagick,名字就说明了一切“drag on fly”。不像 paperclip 那样需要事先在 model 中定义几种缩略图尺寸;同时可以搭配各种 cache 方案。

用这个 nginx module 的话一旦有下面这样的需求就麻烦了:

  • 图片只有特殊用户可见,比如上传者;
  • 要求更丰富的图片处理功能,比如加上上传者的水印;

其实还有一个适用场景:

当你购买的云存储没有提供图片缩略图功能的时候,可以用这种方式搭建。

关于 Cache-ControlPragma 在这种场景的意义:

当客户端 Request Headers 带有以上两个参数 Cache-Control: no-cachePragma: no-cache 的时候,Nginx 会直接忽略 Proxy Cache 请求原始的服务器,重新下载新的图片。

Cache-ControlPragma 的含义是相同的,区别是 Cache-Control 不支持 HTTP 1.0

这种 Request Headers 在浏览器 强制 刷新的时候会发出。

Request Headers:
  :authority:ruby-china-files.b0.upaiyun.com
  :method:GET
  :path:/user/avatar/2.jpg!md
  :scheme:https
  accept-encoding:gzip, deflate, sdch, br
  cache-control:no-cache
  pragma:no-cache
  ...
需要 登录 后方可回复, 如果你还没有账号请 注册新账号