当你浏览一个网站页面的时候,浏览器开始了加载页面的过程,你当然希望立即看到这个网页的内容,如果网站加载过慢你可能会产生焦虑并直接关闭它,这对网站的影响是致命的,因为搜索引擎会统计网站的跳出率,进而影响网站的域名权重和排名,如果放任网页加载很慢会逐渐导致网站的自然搜索流量降低很多,所以一个好的网站应该加载时间越短越好,最长也不应该超过3秒。现代网站都会使用一些技术去提高网站加载速度,比如我们可以:
以上影响网站加载速度的因素中,图片资源过大是一个很重要的因素。在Web流量中,图片一直是大头,如何将图片压缩的更小却不损失一些细节是大家一直在研究的话题。为了更好的演示这个问题,我使用Google提供的Pagespeed Insights评估了下我博客的这篇文章页面《我的财务管理方案》:

如上图所示,全部图片加载耗时39秒之多,这纵然和我上传了过大的PNG图片有关系,实际我应该在本地上传图片前做一番压缩的,不过由于我的工作流中写文章是用Markdown格式的,图片一般是截图或者复制直接粘贴进Markdown文件中(通过VSCode插件自动帮我从内存中上传到AWS S3中然后插入CDN链接至VSCode),由于图片在内存中上传S3是PNG无损图片格式的,PNG图片一般都很大的,如果图片很多的话会导致用户流量网页需要加载很长时间:

这篇文章如果首次加载需要下载近10MB的图片资源,如果网速慢的话根本加载不完。要解决这个问题,让我们先研究下不同图片格式的差异:

如上图,考虑到压缩比与浏览器兼容性,一般我们在网页用的图片主要是JPEG(JPG)/GIF/PNG这三种,JPEG和JPG是同一种格式,只因为早期Windows后缀不允许四个的后缀名。这篇Comparison of different image compression formats论文对比了BMP、TIFF、PNG、JPEG、JPEG 2000图片的压缩比较:

从上可看,一般网站对图片没啥格式要求的话,尽量使用JPG格式图片,压缩比高的有损图片,如果要支持无损图片,可以使用PNG。
不过Google在2010年发布了一种黑科技的图片格式:WebP。
与PNG相比,WebP无损图像的尺寸要小26%。在同等的SSIM质量指数下,WebP有损图像比同类JPEG图像小25-34%。无损WebP支持透明性(也称为Alpha通道),而仅增加22%的字节数。对于可以接受有损RGB压缩的情况,有损WebP还支持透明性,与PNG相比,文件大小通常小3倍。从我个人的测试中,同一张图片,如:https://img.bmpi.dev/3461797f-6a16-e9a5-a01c-f583d7086b49.png,PNG大小为2.1MB,WebP为198KB,而图片细节并没什么丢失(从网页浏览看)。
这么看WebP很完美,唯一的问题在于兼容性,Safari到2020年了都不支持Webp,十年过去了!!!不仅Safari不支持,所有以Safari内核WebKit开发的浏览器都不支持,包括iOS上的Chrome。那我们能不能不支持Safari或者说iOS平台呢?

如果你网站不在乎iOS的流量,那当然可以不支持,不过当我看到我的一个网站的统计如下时:


如上图所示,近一半的流量都来自iOS平台,所以还是得支持iOS。
简单介绍下我博客(https://bmpi.dev)的技术栈:
我们可以通过判断浏览器User Agent的类型给iOS用户返回jpg/png图片,给支持webp的浏览器返回webp格式图片,如下:
user->browser->js check ua->imgix(image convert api)->s3
不过这个方案存在两个问题:
我们换一种可行的方案:
user->browser->aws cloudfront->aws lambda->s3

如上图,当用户通过CDN访问S3中的图片时会抛出四种事件:Viewer Request、Origin Request、Viewer Response、Origin Response。AWS允许我们使用Lambda@Edge(Lambda的扩展,只能部署在us-east-1区域中,但是可以同步到各区域的cloudfront中)监听这四种事件并做一些处理,比如对浏览器请求的header与cookie做一些修改,对源响应做一些额外处理,比如转换图片之类。具体流程如下分解1:
通过AWS这篇文章3分析指出,处理了100万张图像,15GB存储和50GB数据传输也才不到90块钱人民币,的确很便宜。
在上述方案中,我们每次将处理好的webp图片存放至S3中,供下次用户查询时不需要再次转换立即就可以从S3中取出给用户,响应速度提升了,同时减少了Lambda@Edge函数的运行,可以帮我们省钱,相比新增的S3存储,因为webp格式本身是很小的,这个存储成本也是可以接收的。
在编写代码之前,确保你先看了这篇AWS官方介绍文章:《Resizing Images with Amazon CloudFront & Lambda@Edge | AWS CDN Blog》4。
最终代码放至aws-lambda-edge-img2webp5。
代码组织结构如下:
.
├── Dockerfile
├── Makefile
├── aws-sam-lambda-edge-webp.yaml
├── dist
│ ├── origin-response-function.zip
│ └── viewer-request-function.zip
├── lambda
│ ├── origin-response-function
│ │ ├── index.js
│ │ ├── package-lock.json
│ │ └── package.json
│ └── viewer-request-function
│ ├── index.js
│ ├── package-lock.json
│ └── package.json




在本地项目根目录执行make all后脚本自动使用Docker打包Lambda@Edge压缩包至dist目录,然后我们上传至AWS的us-east-1中的S3桶中,如果没有可以随便建一个,我建立的是bmpidev-aws-lambda-edge,这个必须和下面的CloudFormation配置文件一致才行,方便我们接下来使用,然后将dist中两个压缩包上传至这个S3桶中。

将以上文件上传至us-east-1区域的CloudFormation新建一个堆栈,此配置文件会自动生成Lambda函数所需的角色和AWS IAM策略,这个文件执行完毕后,你需要给IAM/角色/权限中赋予S3写入读取权限,这样Origin-Response Function才有权限将webp上传到你的图片资源桶中。
在Lambda>函数>bmpidevImgTransformResponse/bmpidevImgTransformRequest中,点击操作>功能>部署到Lambda@Edge。

如上图,确认后,开始将Lambda函数部署到Lambda@Edge并关联到你的CloudFront中,你在CloudFront监控中应该可以看到部署的Lambda函数的统计信息。
这个在调试的时候很有用,Lambda的Nodejs支持打印日志的,这样我们就可以看到函数执行的具体情况了。

同一个页面,在支持webp格式的浏览器中总加载大小从10MB降低至不到1MB,加载时间从12秒降至3秒。
https://medium.com/nona-web/converting-images-to-webp-from-cdn-9433b56a3d52 ↩︎
https://docs.aws.amazon.com/solutions/latest/serverless-image-handler/overview.html ↩︎
https://aws.amazon.com/cn/blogs/networking-and-content-delivery/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/ ↩︎
https://aws.amazon.com/cn/premiumsupport/knowledge-center/lambda-execution-role-s3-bucket/ ↩︎
https://stackoverflow.com/questions/50008445/aws-cloudfront-returns-access-denied-from-s3-origin-with-query-string ↩︎
当你浏览一个网站页面的时候,浏览器开始了加载页面的过程,你当然希望立即看到这个网页的内容,如果网站加载过慢你可能会产生焦虑并直接关闭它,这对网站的影响是致命的,因为搜索引擎会统计网站的跳出率,进而影响网站的域名权重和排名,如果放任网页加载很慢会逐渐导致网站的自然搜索流量降低很多,所以一个好的网站应该加载时间越短越好,最长也不应该超过3秒。现代网站都会使用一些技术去提高网站加载速度,比如我们可以:
以上影响网站加载速度的因素中,图片资源过大是一个很重要的因素。在Web流量中,图片一直是大头,如何将图片压缩的更小却不损失一些细节是大家一直在研究的话题。为了更好的演示这个问题,我使用Google提供的Pagespeed Insights评估了下我博客的这篇文章页面《我的财务管理方案》:

如上图所示,全部图片加载耗时39秒之多,这纵然和我上传了过大的PNG图片有关系,实际我应该在本地上传图片前做一番压缩的,不过由于我的工作流中写文章是用Markdown格式的,图片一般是截图或者复制直接粘贴进Markdown文件中(通过VSCode插件自动帮我从内存中上传到AWS S3中然后插入CDN链接至VSCode),由于图片在内存中上传S3是PNG无损图片格式的,PNG图片一般都很大的,如果图片很多的话会导致用户流量网页需要加载很长时间:

这篇文章如果首次加载需要下载近10MB的图片资源,如果网速慢的话根本加载不完。要解决这个问题,让我们先研究下不同图片格式的差异:

如上图,考虑到压缩比与浏览器兼容性,一般我们在网页用的图片主要是JPEG(JPG)/GIF/PNG这三种,JPEG和JPG是同一种格式,只因为早期Windows后缀不允许四个的后缀名。这篇Comparison of different image compression formats论文对比了BMP、TIFF、PNG、JPEG、JPEG 2000图片的压缩比较:

从上可看,一般网站对图片没啥格式要求的话,尽量使用JPG格式图片,压缩比高的有损图片,如果要支持无损图片,可以使用PNG。
不过Google在2010年发布了一种黑科技的图片格式:WebP。
与PNG相比,WebP无损图像的尺寸要小26%。在同等的SSIM质量指数下,WebP有损图像比同类JPEG图像小25-34%。无损WebP支持透明性(也称为Alpha通道),而仅增加22%的字节数。对于可以接受有损RGB压缩的情况,有损WebP还支持透明性,与PNG相比,文件大小通常小3倍。从我个人的测试中,同一张图片,如:https://img.bmpi.dev/3461797f-6a16-e9a5-a01c-f583d7086b49.png,PNG大小为2.1MB,WebP为198KB,而图片细节并没什么丢失(从网页浏览看)。
这么看WebP很完美,唯一的问题在于兼容性,Safari到2020年了都不支持Webp,十年过去了!!!不仅Safari不支持,所有以Safari内核WebKit开发的浏览器都不支持,包括iOS上的Chrome。那我们能不能不支持Safari或者说iOS平台呢?

如果你网站不在乎iOS的流量,那当然可以不支持,不过当我看到我的一个网站的统计如下时:


如上图所示,近一半的流量都来自iOS平台,所以还是得支持iOS。
简单介绍下我博客(https://bmpi.dev)的技术栈:
我们可以通过判断浏览器User Agent的类型给iOS用户返回jpg/png图片,给支持webp的浏览器返回webp格式图片,如下:
user->browser->js check ua->imgix(image convert api)->s3
不过这个方案存在两个问题:
我们换一种可行的方案:
user->browser->aws cloudfront->aws lambda->s3

如上图,当用户通过CDN访问S3中的图片时会抛出四种事件:Viewer Request、Origin Request、Viewer Response、Origin Response。AWS允许我们使用Lambda@Edge(Lambda的扩展,只能部署在us-east-1区域中,但是可以同步到各区域的cloudfront中)监听这四种事件并做一些处理,比如对浏览器请求的header与cookie做一些修改,对源响应做一些额外处理,比如转换图片之类。具体流程如下分解1:
通过AWS这篇文章3分析指出,处理了100万张图像,15GB存储和50GB数据传输也才不到90块钱人民币,的确很便宜。
在上述方案中,我们每次将处理好的webp图片存放至S3中,供下次用户查询时不需要再次转换立即就可以从S3中取出给用户,响应速度提升了,同时减少了Lambda@Edge函数的运行,可以帮我们省钱,相比新增的S3存储,因为webp格式本身是很小的,这个存储成本也是可以接收的。
在编写代码之前,确保你先看了这篇AWS官方介绍文章:《Resizing Images with Amazon CloudFront & Lambda@Edge | AWS CDN Blog》4。
最终代码放至aws-lambda-edge-img2webp5。
代码组织结构如下:
.
├── Dockerfile
├── Makefile
├── aws-sam-lambda-edge-webp.yaml
├── dist
│ ├── origin-response-function.zip
│ └── viewer-request-function.zip
├── lambda
│ ├── origin-response-function
│ │ ├── index.js
│ │ ├── package-lock.json
│ │ └── package.json
│ └── viewer-request-function
│ ├── index.js
│ ├── package-lock.json
│ └── package.json




在本地项目根目录执行make all后脚本自动使用Docker打包Lambda@Edge压缩包至dist目录,然后我们上传至AWS的us-east-1中的S3桶中,如果没有可以随便建一个,我建立的是bmpidev-aws-lambda-edge,这个必须和下面的CloudFormation配置文件一致才行,方便我们接下来使用,然后将dist中两个压缩包上传至这个S3桶中。

将以上文件上传至us-east-1区域的CloudFormation新建一个堆栈,此配置文件会自动生成Lambda函数所需的角色和AWS IAM策略,这个文件执行完毕后,你需要给IAM/角色/权限中赋予S3写入读取权限,这样Origin-Response Function才有权限将webp上传到你的图片资源桶中。
在Lambda>函数>bmpidevImgTransformResponse/bmpidevImgTransformRequest中,点击操作>功能>部署到Lambda@Edge。

如上图,确认后,开始将Lambda函数部署到Lambda@Edge并关联到你的CloudFront中,你在CloudFront监控中应该可以看到部署的Lambda函数的统计信息。
这个在调试的时候很有用,Lambda的Nodejs支持打印日志的,这样我们就可以看到函数执行的具体情况了。

同一个页面,在支持webp格式的浏览器中总加载大小从10MB降低至不到1MB,加载时间从12秒降至3秒。
https://medium.com/nona-web/converting-images-to-webp-from-cdn-9433b56a3d52 ↩︎
https://docs.aws.amazon.com/solutions/latest/serverless-image-handler/overview.html ↩︎
https://aws.amazon.com/cn/blogs/networking-and-content-delivery/resizing-images-with-amazon-cloudfront-lambdaedge-aws-cdn-blog/ ↩︎
https://aws.amazon.com/cn/premiumsupport/knowledge-center/lambda-execution-role-s3-bucket/ ↩︎
https://stackoverflow.com/questions/50008445/aws-cloudfront-returns-access-denied-from-s3-origin-with-query-string ↩︎