Press "Enter" to skip to content

即时调整图片大小

作为一个网络架构师,资产管理是其中的许多问题之一,而在资产中最重要的问题就是图片。一种简单的方法是设置一张图片,然后通过 CSS 让浏览器调整图片的大小:

然而,这意味着您要下载原始图片。它会带来两个问题:原始图片的大小以及浏览器基于 CSS 进行的子优化大小调整。

本文将介绍两种替代方案:传统和全新的解决方案。

提前调整大小

针对单一图片源的传统解决方案是提前调整大小。在发布之前,设计师会花时间提供不同分辨率的多个图片版本。在这个博客中,我正在使用这种技术。我提供了三种不同上下文环境下显示文章主图的分辨率作为背景图片:

  • 在文章页面上显示的大尺寸
  • 在首页上显示的 VoAGI
  • 在文章页面上与相关文章一起显示的小尺寸

我还删除了 JPEG 元数据以进行更高的大小减小。

然而,传统方法是利用 HTML 的 picture 标签:

<picture> HTML 元素包含零个或多个<source>元素和一个<img>元素,以提供不同显示/设备场景下图片的替代版本。

浏览器将考虑每个子<source>元素并从中选择最佳匹配。如果找不到匹配项,或者浏览器不支持<picture>元素,则选择<img>元素的 src 属性的 URL。然后在占用 <img> 元素的空间中呈现所选的图片。

MDN web docs 上的 Picture 元素

转而,您可以如下使用它:

这种方法已经使用了很长时间,但它有两个问题。首先,为每个图片提供多种分辨率需要很长时间。可以自动化该过程并通过 AI 获得良好的结果。

然而,所需存储的容量可能是原始图片大小的两倍或三倍,这取决于创建额外分辨率的数量。在资源丰富的环境中,如电子商务,这将大大增加成本。

即时调整大小

我最近偶然发现了一个名为 imgproxy 的组件,它可以实时调整图片的大小:

imgproxy 可以在保留存储空间和 SaaS 成本的同时使网站和应用程序速度飞快

imgproxy 网站

它提供了一个终点,您可以向其中发送一个编码的 URL,其中定义了:

  • 要调整大小的图片及其位置,例如本地、HTTP URL、S3 存储桶等。
  • 不同的尺寸参数,例如尺寸、适应与填充等。
  • 格式。imgproxy 支持标准格式,如 JPEG 和 PNG,还支持更现代的格式,如 WebP 和 AVIF。它还可以根据“Accept”标头选择最佳格式。
  • 许多(很多!)其他选项,如水印、过滤、旋转等。

imgproxy 提供了一个开源免费版本和一个付费版本;本文中的所有内容都包含在前者中。

一种解决方案是由 web 开发人员在 HTML 中编写每个 imgproxy 的 URL:

这泄露了与网页相关的拓扑细节。这不是一个可维护的解决方案。我们可以通过反向代理或 API 网关来解决这个问题。由于明显的原因,我将使用 Apache APISIX。

有了这种方法,上述 HTML 变得更简单:

Apache APISIX 拦截以 /resize 开头的请求,重写 imgproxy 的 URL,并将重写后的URL转发给 imgproxy。以下是整体流程:

相应的 Apache APISIX 配置如下:

  1. 匹配以 /resize 为前缀的请求
  2. 重写 URL
  3. 捕获正则表达式中的宽度和图片
  4. 格式化 imgproxy 的 URL。 http://server:3000 是托管原始图片的服务器;@webp 表示对 WebP 格式的偏好(如果浏览器支持)

使用上述方法,将/resize/200/ai-generated.jpg重写为/rs:fill/w:200/plain/http://server:3000/ai-generated.jpg@webp传递给imgproxy

测试

我们可以使用Docker Compose设置一个小的测试示例:

  1. 简单的Web服务器托管HTML和主要图像

现在,我们可以使用浏览器的开发者工具进行上述设置的测试,模拟小屏幕设备,例如iPhone SE。结果如下:

  • 由于屏幕分辨率,请求的图像是400像素宽的,而不是原始图像。可以在请求的URL中看到
  • 返回的图像是WebP格式,大小为14.4kb
  • 原始的JPEG图像大小为154kb,是返回图像的十倍以上。这节省了大量的网络带宽!

讨论

将存储成本减少十倍自然是一个巨大的好处。然而,这并非都是美好如愿。计算调整大小的图像是一个计算密集的操作,每个请求都会消耗CPU时间。此外,无论imgproxy多么高效,在创建图像时也是需要时间的。我们以存储成本换取了CPU成本,并且现在出现了轻微的性能问题。

为了解决这个问题,我们需要在前面添加一个缓存层,可以是自定义的缓存层,也可以是更常见的CDN。你可能会反对说我们又要存储资产,因此存储成本又会上升。然而,一个重要的区别是缓存仅适用于使用的图像,而在之前的解决方案中,我们为所有图像付费进行存储。同时,你还可以应用已知的缓存策略,例如预热,在你知道一组图像将会高需求的情况下,例如在活动之前。

结论

在本文中,我们描述了如何使用Apache APISIX和imgproxy来减少多种分辨率图像的存储成本。通过在顶部添加缓存,整体架构中添加了更多组件,但存储成本却减少了。

本文的灵感来源于Andreas Lehr在StackConf的演讲。

本文的完整源代码可以在GitHub上找到。

进一步了解

Leave a Reply

Your email address will not be published. Required fields are marked *