V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
n0vad3v
V2EX  ›  分享创造

让站点图片加载速度更快——使用 WebP Server 在不改变 URL 的情况下无缝转换图片为 WebP

  •  1
     
  •   n0vad3v ·
    n0vad3v · 2020-03-01 20:18:18 +08:00 · 4084 次点击
    这是一个创建于 1727 天前的主题,其中的信息可能已经有所发展或是发生改变。

    为了知道我们的 Web 站点性能如何,我们一般会使用 Google 的 PageSpeed Insights ,其中,我们可能经常看到如下提示:

    同时,你的打分也会被下降了,如何解决这种问题呢?

    Image formats

    我们知道,图片一般有不同的格式,比如我们常见的 JPG,PNG 就分别属于两种不同的图片格式,通过是否对图片进行压缩,我们可以分为:

    • 无压缩。不对图片数据进行压缩处理,能准确地呈现原图片。 BMP 格式就是其中之一。
    • 无损压缩。压缩算法对图片的所有的数据进行编码压缩,能在保证图片的质量的同时降低图片的尺寸。 png 是其中的代表。
    • 有损压缩。压缩算法不会对图片所有的数据进行编码压缩,而是在压缩的时候,去除了人眼无法识别的图片细节。因此有损压缩可以在同等图片质量的情况下大幅降低图片的尺寸。 其中的代表是 jpg。

    除了是否压缩以外,还有一个大家可能经常会遇到的问题——图片是否有透明图层,例如在之前的「搭建 Cloudflare 背后的 IPv6 AnyCast 网络」中的图片,在嵌入我的文章时并不会因为背景颜色的变化而显示出一个白色的背景,这是因为图片存在「透明通道(阿尔法通道)」,常见的支持透明通道的图片格式有:PNG,PSD,JPEG XR 和 JPEG 2000,其中后两者也是 Google 推荐的图片的 next-gen formats 之二,而常见的无透明通道的则是:JPEG 啦。

    除了这个之外,还有一个由 Google 牵头研发,Telegram Stickers 主力使用的文件格式——WebP。

    WebP

    WebP 的有损压缩算法是基于 VP8 视频格式的帧内编码[17],并以 RIFF 作为容器格式。[2] 因此,它是一个具有八位色彩深度和以 1:2 的比例进行色度子采样的亮度-色度模型( YCbCr 4:2:0 )的基于块的转换方案。[18] 不含内容的情况下,RIFF 容器要求只需 20 字节的开销,依然能保存额外的 元数据(metadata)。[2] WebP 图像的边长限制为 16383 像素。

    在 WebP 的官网中,我们可以发现 Google 是这样宣传 WebP 的:

    WebP lossless images are 26% smaller in size compared to PNGs. WebP lossy images are 25-34% smaller than comparable JPEG images at equivalent SSIM quality index.

    简单来说,WebP 图片格式的存在,让我们在 WebP 上展示的图片体积可以有较大幅度的缩小,也就带来了加载性能的提升。

    要生成一个 WebP 图片非常简单,只需要下载 Google 提供的 cwebp 工具,并且使用:

    cwebp -q 70 picture_with_alpha.png -o picture_with_alpha.webp
    

    就可以进行转换了,转换出来的 webp 图片比原图会小不少,但是这个是单张图片,我们的目的是让站点的图片可以无痛地以 WebP 格式输出,如果我们的博客上有 100+ 张图片转换该如何操作呢?如果是更多呢?

    聪明的人可以想到——我们可以写一个脚本来自动转换,或者使用一些服务器插件,比如 mod_pagespeed (可以参考之前的文章:使用 Nginx 和 mod_pagespeed 自动将图片转换为 WebP 并输出),但是这些操作都有其特定的局限性,以 mod_pagespeed 为例,假设你能流畅完成编译 /安装 /配置,它的转换需要以图片和站点内容在一个目录下为前提,并通过修改 URL 的方式进行转换,举例来说,你的图片地址为:

    <img src="picture_with_alpha.png">
    

    那么经过转换后的图片可能为:

    <img src="picture_with_alpha.png.pagespeed.ic.uilK6vtMij.webp">
    

    这样可以做到图片和转换后的图片分离的效果,但是这种转换我个人博客上便无法完成,为了方便配置和防止被绑死在一个博客平台上,我的博客图片被统一地放在了 https://blog-assets.nova.moe/ 地址上,这样 mod_pagespeed 便无法发挥作用了。

    Polish

    在 Cloudflare 中,Pro 用户可以使用到一个功能——Polish,功能描述如下:

    Improve image load time by optimizing images hosted on your domain. Optionally, the WebP image codec can be used with supported clients for additional performance benefits.

    如果选择了 Serve WebP Image 的话,通过 Cloudflare 的图片请求会被无缝地转换为 WebP 格式输出,同时请求头部中,会多一个名为 cf-polished 的 Header,用来 debug 转换情况。有兴趣的读者可以看一下 Cloudflare 的博文「Using Cloudflare Polish to compress images」来了解更多相关信息。

    Cloudflare 的这个功能很赞,由于这个转换需要算力,所以 Polish 只提供给 Pro 用户使用,为了同样使用到类似的功能,我用 NodeJS 写了一个服务器,命名为 WebP Server,之后和 Benny 用 Golang 重写了一遍,命名为 WebP Server Go。

    WebP Server Go

    由于 WebP Server 和 WebP Server Go 功能类似,且由于主要在发展后者,这里直接介绍 Go 版 WebP Server 啦。

    WebP Server Go 的使用方式非常简单,由于使用 Go 编写,使用者只需要下载单一文件——webp_server,创建一个 config.json 文件,内容大致如下:

    {
    	"HOST": "127.0.0.1",
    	"PORT": "3333",
    	"QUALITY": "80",
    	"IMG_PATH": "/path/to/pics",
    	"ALLOWED_TYPES": ["jpg","png","jpeg"]
    }
    

    然后 webp_server --config /path/to/config.json 即可运行 WebP Server,最后加上 Nginx 的反向代理就可以用了。

    举个例子,有一张图片是 https://image.nova.moe/tsuki/tsuki.jpg,对应的图片在服务器上的存放目录为 /var/www/nova-image/tsuki/tsuki.jpg 的话,那么,配置文件中的 IMG_PATH 就是 /var/www/nova-image,同时,每次转换导出的 webp 图片会被缓存到 webp_server 同目录的 exhaust/tsuki/tsuki.webp 下,供后续访问的时候直接输出使用。

    最重要的一点是——我们访问的 URL 可以完全不用改变,访客访问的依然是 https://image.nova.moe/tsuki/tsuki.jpg ,但是得到的图片格式为:image/webp,而且体积减少了不少。

    而且,对于 Safari 用户来说,WebP Server 会选择直接输出原图,防止出现输出的 webp 图片不能显示的情况。

    Effect

    那么这个 WebP Server 效果如何呢?以一篇包含了不少图片的文章「那些年我开过的车(们)」为例:

    Before WebP Server

    在默认原图输出的时候,PageSpeed 得分为

    对应的图片请求为:

    After WebP Server

    使用了 WebP Server 之后:

    看上去很赞是不是~

    Afterword

    我们很多人都想回馈社会,在这股洪流中再添上一笔。这是用我们的专长来表达的唯一方式——因为我们不会写鲍勃·迪伦的歌或汤姆·斯托帕德的戏剧。 我们试图用我们仅有的天分去表达我们深层的感受,去表达我们对前人所有贡献的感激,去为这股洪流加上一点儿什么。那就是推动我的力量。

    ——《史蒂夫·乔布斯传》

    这世界上轮子太多,在各种开发工具的加持下造出一个没有解决任何痛点的粗糙轮子也越发简单,而专注于解决特定需求的工具只有少量沉淀,在这个 PNG/JPG 和 WebP 共存的历史背景下,希望 WebP Server Go 成为起到一个平稳过渡的工具,目前代码已经开源在 GitHub:wep-sh/webp_server_go,由于初学 Go 没多久,代码上可能还有不少欠缺考虑的地方,还望未来的使用者海涵。

    Happy Hacking!

    15 条回复    2020-03-19 09:08:50 +08:00
    Tink
        1
    Tink  
       2020-03-01 22:55:05 +08:00 via iPhone
    不用切换 url 这一点很赞,解决思路新颖
    s609926202
        2
    s609926202  
       2020-03-01 22:58:45 +08:00 via iPhone
    赞👍
    cydian
        3
    cydian  
       2020-03-01 23:07:57 +08:00 via Android
    腾讯云 CDN 也可以实现。
    并且 cloudflare 在国内相当慢。
    also24
        4
    also24  
       2020-03-01 23:17:02 +08:00
    赞自己动手。

    此类功能,如果能作为 nginx / httpd / caddy 之类的 WebServer 插件使用就更好了。
    nicoljiang
        5
    nicoljiang  
       2020-03-02 16:16:16 +08:00   ❤️ 1
    赞一个想法和动手能力。
    我最近也做了一套方案来做图片实时处理,不过除了 webp 的自动转化之外,还提供一些简单的实时剪裁,包括智能裁剪:

    https://rmt.dogedoge.com/fetch/girl.jpg?h=500&w=1200&pos=auto&fmt=webp

    近期可能也会发布一下。
    n0vad3v
        6
    n0vad3v  
    OP
       2020-03-02 22:50:21 +08:00
    @nicoljiang 多谢夸奖~也感谢大家的回复~

    关于转换图片的话,其实之前调研了一下发现有蛮多类似功能的工具,不过有一个问题就是需要后面加上一些后缀,这样就没法做到在不改变 URL 的情况下给站点提速了,也就是 WebP Server 主要尝试解决的一个痛点,不过这种加后缀的方式感觉也有一些潜在的优化角度,比如用 JS 获取页面信息(展示宽度等)后直接把转换参数 append 到图片的 src 中,做到只渲染出和屏幕一样宽的图片的效果,这样应该可以有更大的优化空间。
    n0vad3v
        7
    n0vad3v  
    OP
       2020-03-02 23:08:55 +08:00
    @also24 好想法,那样的话会更加易用一些,不过现阶段我们精力还跟不上,短期内估计做不出来 :(
    winterpotato
        8
    winterpotato  
       2020-03-04 19:48:51 +08:00
    哦嚯,这个思路很赞的呀!感觉还有好些可以改善的地方呢🤔
    nicoljiang
        9
    nicoljiang  
       2020-03-04 22:54:30 +08:00
    @n0vad3v 对的。在牺牲一些简洁程度的同时也会有一些其他收益。例如在做毛玻璃 placeholder 的时候,可以轻易按比例裁剪成 50 的宽度,大小不到 1k。并且遇到 gif 动图时,能通过 frame 参数快速展示第一针做预览,然后再加载动画主体。
    xiaoz
        10
    xiaoz  
       2020-03-17 10:32:00 +08:00
    阁下就是 webp_server_go 的作者吗?
    n0vad3v
        11
    n0vad3v  
    OP
       2020-03-17 11:40:36 +08:00   ❤️ 1
    @xiaoz 这个不好回答呀,本来是自学 Go 之后第一个练手的项目,然后被 @bennythink 大佬看好,之后 muzi 也加入进来了,所以...
    是,但不全是.
    n0vad3v
        12
    n0vad3v  
    OP
       2020-03-17 11:43:12 +08:00
    @nicoljiang 这个切入点很赞!
    xiaoz
        13
    xiaoz  
       2020-03-17 12:22:48 +08:00
    @n0vad3v 这里我有一个疑问,假如配合 CDN 使用,如果 CDN 缓存了 webp 的图像,同一个 URL,CDN 不会再去请求源站,直接吐 webp 的缓存,那么 IOS 是不是无法打开?
    n0vad3v
        14
    n0vad3v  
    OP
       2020-03-17 13:13:01 +08:00   ❤️ 1
    @xiaoz 嗯,对,如果被 CDN 缓存了并输出了 webp 格式的图像的话,iOS 用户就没法打开了。
    这一点我们的做法是让 CDN 上对应的 URL 关闭缓存(在 Cloudflare 上可以通过创建一个 Page Rules,并设置 Cache Level: Bypass 解決。)

    后期我们也会更新一下 README,感谢指出。
    4ark
        15
    4ark  
       2020-03-19 09:08:50 +08:00 via iPhone
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2757 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 13:32 · PVG 21:32 · LAX 05:32 · JFK 08:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.