CDN监控和运行时切换

CDN 监控和运行时切换

CDN 是前端分发静态资源的重要工具,如果 CDN 出现问题会导致网页错乱,操作无法响应等问题。

在生产环境中也会收到用户的反馈网页错乱的问题,经排查都是用户无法访问 CDN 资源导致,以往此类问题都只能依赖用户自身去排查网络问题,效率较低。希望能找到一种方法能够快速帮助用户解决问题。

切换 CDN

我们同时采购了阿里云和网宿两家 CDN 服务商,目前测试下来两家服务商都无法保证百分百的可访问性。

最开始打算解决这个问题的时候,一开始想的是只要我们将页面上的 style 和 script 标签全部替换不就可以了么,但仔细一想这么做是远远不够的,因为我们的项目是使用 webpack 进行打包的,我们只修改了 html 中的页面入口 js,后续依赖的异步模块都是由 webpack 加载,我们没办法直接干涉。另外在 css 文件中依赖的图片资源地址也是无法动态进行修改的。

鉴于以上几个难点这个问题一直没能得到很好的解决,直到后来了解到了可以利用 webpack 的动态 publicPath 特性,总算找到了一个比较完美的解决方案。

On The Fly - 运行时

要解决问题的关键是要解决运行时的难题,否则就只能同时部署多套服务,分别对应不一样的 CDN,再为用户切换不同的服务来解决问题,成本太大。

关于运行时切换 Public Path 可参考: https://webpack.js.org/guides/public-path/#on-the-fly

除了加载 js 需要动态切换 CDN 域名,在 CSS 文件中加载图片等资源也需要切换,但 CSS 又不像 js 那样可以动态运行脚本轻松注入变量,可能 css 变量是一种方案,但操作起来也比较麻烦。所以这里利用了 CSS 加载时可以使用相对路径的方式,将静态资源连同 css 文件的地址一起切换即可。

完整方案

  1. 在 webpack 构建的应用入口最上方添加依赖文件 public-path.js(下方注意点!)
  2. 构建时将 public Path 设置成 /,目的是将 css 等构建时就生成的静态资源地址转换成相对路径。
  3. 收到请求时根据来源 ip 或者特定条件选择对应的 CDN。
  4. 输出 html 时将资源地址全部替换成特定的 CDN 地址。
  5. 输出 html 时添加 script 标签,在 window.__RUNTIME_PUBLIC_PATH__ 上注入需要切换的 CDN 地址。

注意点!

public-path.js:此文件一定要放在入口最上方通过 import 引入,例如有如下入口文件:

1
2
3
// entry.js
import "./public-path";
import "./app";

public-path.js 代码为:

1
2
3
if (window.__RUNTIME_PUBLIC_PATH__) {
__webpack_public_path__ = window.__RUNTIME_PUBLIC_PATH__;
}

__webpack_public_path__是 webpack 内置的全局变量,并不是 window 对象上的全局变量,外部只能通过 window 对象注入。

另外__RUNTIME_PUBLIC_PATH__可以是 CDN 的域名,也可以直接配置成 /,从源站拉取静态资源,如果用户能看到页面错乱,那说明其能访问到源站的域名,那么将静态资源切换成源站地址可能是最稳妥的方式(只建议临时处理,不然会给源站服务器带来较大压力)

监控

需要监控 CDN 可用性,可以利用第三方平台,从全国范围内监控 CDN 的可用性和响应时间。

也可以通过脚本收集线上静态资源的加载情况判断 CDN 是否存在问题。

关于收集线上静态资源加载情况

由于业务 js 脚本本身就是放置在 CDN 上的,所以监控脚本只能以内联的方式写在 html 中。

上报函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var url = ""; // 数据接收接口
var __crush_uid__ = ""; // 如果有的话可以收集用户信息
function errorHandler(e) {
var r = e.target || e.srcElement;
if (!(!r || !r.tagName || e.message || e.filename || e.lineno || e.colno)) {
try {
var __i__ = (window.__crush_image_hold__ = new Image());
__i__.onload = __i__.onerror = function () {
window.__crush_image_hold__ = undefined;
};
__i__.src =
"${url}?t=resourceError&env=&src=" +
encodeURIComponent(r.src || r.href) +
(__crush_uid__ ? "&uid=" + __crush_uid__ : "");
__i__ = null;
} catch {}
}
}
window.addEventListener
? window.addEventListener("error", errorHandler, true)
: window.attachEvent && window.attachEvent("onerror", errorHandler);

注意点!

想要捕获全局资源加载错误,window.addEventListener("error", errorHandler, true)第三个参数需要设置成 true,即采用捕获模式而不是冒泡模式。

另外有以下情况无法拦截到错误:

  1. 由于 css 是放在 header 中进行加载的,加载时间在执行 js 之前,如果在执行代码之前就加载失败就无法拦截到。但如果是异步加载的 css 就可以拦截到,只要是执行完 js 之后加载的资源就能够监控到。
  2. 如果是静态资源长时间 pending 的情况,也是不会触发 error 事件,而且由于资源加载往往超时时间非常地长,几十秒几分钟的情况也是胡出现的,这类情况也没办法很好地监控。

除了 css 和 js,该方法还可以会监控页面上的其他资源,包括图片资源的加载。


CDN监控和运行时切换
https://www.wobushi.top/2021/CDN监控和运行时切换/
作者
Pride Su
发布于
2021年5月11日
许可协议