Taobao FED

HTML 压缩服务治理

HTML 压缩服务治理

缘由

笔者最近在研究如何实现页面 HTML 及内联 JS/CSS 的实时压缩功能。

首先笔者尝试了在前端模块中扫描内联的 JS/CSS 并压缩,这样还可以集成至前端模块的上传工具中。观察了一段时间发现这样无法处理模板中的 JS/CSS,造成很多遗漏的 JS/CSS 不能压缩。

于是笔者转而考虑对页面进行实时 HTML 压缩,这次考察了坑过许多 Node.js 开发者的 html-minifier 模块。该模块同时包含内联 JS/CSS 的压缩功能,功能是十分丰富的。不幸的是,很快笔者发现该模块应用在线上服务器环境极为不稳定,一场 HTML 压缩服务治理的探索就这样开始了。

CPU 100%

笔者先对少量页面进行了 HTML 及内联 JS/CSS 压缩测试,发现效果还不错。随后开始寻找更多页面样板进行尝试,很快遇到了第一个坑,“html-minifier 压缩部分 HTML 片段会导致 CPU 100%”。

案例如下:

1
2
<!-- 使用 html-minifier 压缩导致 CPU 100% 案例 -->
<div style="background:url(\"FyueIVXXXXanapXXSutbFXXX.jpg\") no-repeat"></div>

很明显,案例中有 HTML 语法错误,而 html-minifier 依赖 HTML 语法树的解析,对不合理的输入未做异常处理导致 CPU 100%。恰恰这样的语法错误是前端用户经常不小心写出来的,所以笔者不能无视。提 Issue 给模块作者,得到的回复是 html-minifier 不能处理非法 HTML,没有改进计划。

这里笔者评估自己没精力独立修复此问题,因此想到了一个绕开此问题的办法,使用 vm 模块在独立的上下文处理压缩任务,并设置超时时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let vm = require('vm');
let sandbox = {
minify: minify,
config: config.minify,
src: body
};
try {
vm.runInNewContext('try{result=minify(src,config)}catch(e){err=e}', sandbox, {
timeout: config.minifyTimeout
});
} catch(err) {
// ScriptTimeout 异常,根本原因包含真超时和内联 HTML/CSS 压缩异常
debug('html minify timeout.');
return body;
}

测试该方案有效,vm 在指定的时间内完不成任务会抛出 ScriptTimeout 异常,再也不会直接导致 CPU 100% 了。

Node.js 的 vm 模块提供了一个独立的 JS 执行上下文,但并不是独立的进程,因此它并不是进程安全的。这里笔者只用来避免 JS 上下文里的 CPU 100%,vm 完全可以胜任。

要了解更多 vm 模块知识可以参考官方文档:https://nodejs.org/api/vm.html

不标准的异常信息

html-minifier 奇葩的是,遇到不标准 HTML 并非都会 CPU 100%,部分案例还是可以抛出异常的。比如下面这种:

1
<p>hi\n<!--

此时,html-minifier 抛出一个 Parse Error 的“异常”,但是这个异常是字符串对象,而不是 Error 对象。如果你需要对 Error 对象做分类处理或其它包装,需要注意这个问题。

速度慢

测试发现,页面的 HTML 压缩会花费 300-500ms,而页面的渲染时间一般在 100ms 以内。如此低的性能在实时渲染性能要求较高的应用中是不可接受的。

因此笔者为了追求更快的压缩速度,尝试了如下优化方法:

  • 舍弃部分不是不太关键的功能。比如页面的内联 CSS 很少,就可以关闭内联 CSS 的压缩功能。经测试,移除内联 CSS 压缩可以节省 100ms 的时间。
  • 内存管理优化,更少的 GC 是高效压缩的保障。观察发现在遇到 Node.js 进行大量内存回收时,HTML 压缩时间会变成平常的 4-5 倍。内存问题笔者在 Node.js 0.12.x/4.x 都有遇到,一旦出现问题,会对稳定性及性能产生很大影响。

总结

在优化 HTML 压缩的过程中,笔者解决了稳定性问题,但是没有从根本上解决性能问题。后续会考察最近新发现的 minimize 模块,该模块号称适合集成至线上服务器环境,因此比较重视压缩性能。缺点是该模块还在快速发展中,截止到截稿时,其 JS 压缩插件还未发布。

总结本文的目的,主要是抛砖引玉——我们正在研究实现一个高效稳定的 HTML 压缩模块,加入我们,一起来完善 Node.js 开发体系的基础设施吧。

Node.js 应用还在快速发展,我们遇到的问题也越来越有挑战。如果你有兴趣挑战,快点击加入我们