CDN容灾方案

1,317 阅读4分钟

背景

现在的web页面中,绝大多数资源都采用了CDN资源,CDN资源节省了服务器资源,也可以带来静态资源加载的速度提升,可以说是不二选择。那这带来的问题是什么呢?一旦出现部分域名被运营商封禁,或是CDN服务商出现问题,可能会导致全量的静态资源不可用。所以对于页面内的所有CDN的静态资源必须有域名的的容灾方案才行。

页面中有哪些CDN资源

  1. 项目中的构建出来的js资源
  2. 项目中的样式资源
  3. 项目中的图片视频等静态资源
  4. 服务端下发需要展示的一些静态资源
  5. 动态化方案中下发的嵌入的页面资源或者是代码资源
  6. 可能使用的第三方库中存在的静态资源

如何处理

上面的六种资源从出现的来源上基本上可以分为三类:1,2,3是由前端代码产生的,4和5是从接口中获取的,6是前端引入第三方库导致的。先看4和5,这些接口返回的数据我们可以很轻易的通过,后端动态配置或者是接口数据清洗来做到。然后看一下6,显然第三方的代码没有可能来支持这种容灾的场景,所以先直接忽略这些资源吧。

前端CDN资源

前端项目中产生的CDN资源一般有两种方式带来:一种是在代码中写死的CDN静态资源,包含了css中常见引入的背景图地址,js中可能引入的图片地址;另一种是项目中构建生成的js,css及一些静态资源会在线上构建后被同一上传到cdn上,项目中则是通过publicpath+资源名的形式去引用,这部分一般是通过webpack实现的。

publicPath动态化

首先我们知道线上的publicPath是由我们写死的cdnURL+ 项目path组成的。在构建产物中会存在一个常量publicPath,而所有构建生成的所有静态资源都是用这个常量做的拼接。那么我们的问题就是如何让现在的publicPath变成一个动态的地址+项目path。 可以发现HTML资源一般就是是通过域名访问ng转发的方式返回给用户的,所以只要返回的html中可以动态的插入一段js,例如插<script>window.globalCDNUrl = 'xxx.com'</script>。这个工作在ng服务器上可以通过文件字符串处理的脚本实现,比如脚本会扫描html文件,将其中的##globalCDNUrl字符串替换为xxx.com,而项目中输出的HTML文件头部加上

<script>
    window.globalCDNUrl = '##globalCDNUrl';
    if(window.globalCDNUrl === ('#' + '#globalCDNUrl') ) {
        window.globalCDNUrl === 'alicdn.com'
    }
</script>

兜底处理一下ng上脚本失败的场景。 最后我们的publicPath = window.globalCDNUrl + '/path'的方式,就可以做到publicPath动态化了。最后一步publicPath如何设置为一个变量的方式,可以通过现成的webpack插件来实现。

静态代码的处理

这里比较粗暴的方案是代码搜索手动替换,问题是代码侵入性比较高,需要消耗大量人力。下面简单看一下使用插件的方案。

js中的静态地址

目的是扫描到所有的静态cdn地址,替换为 window.globalCDNUrl的变量 考虑写一个babel的插件,demo如下

module.exports = ({type: t}) => {
    return {
        visitor: {
            StringLiteral: (path, state) => {
                state.opts.rules.map((opt) => {
                    //判断是不是字符串
                    const isStringLiteral = path.node.type === 'StringLiteral';
                    //判断有没有cdn的静态地址,这里可以用正则
                    if(isStringLiteral && path.node.value.match('cdnpath')) {
                        //地址字符串替换
                        let newPath = path.node.value.replace('cdnpath', '/');
                        //以下以react项目为例子
                        if (path.parentPath.node.type === 'JsxArribute' || path.parentPath.node.type === 'JSXArribute') {
                        //需要转为jsx 代码
                            path.replaceWith(t.JSXExpressionContainer(t.binaryExpression('+', t.Identifier('globalCDNUrl'), t.stringLiteral(newPath))))
                        } else {
                           //需要转为变量名+newPath
                           path.replaceWith(t.binaryExpression('+', t.Identifier('globalCDNUrl'), t.stringLiteral(newPath)))
                        }
                    }
                })
            }
        }
    }
}

css中的静态地址

css中的静态资源只要都改为相对路径即可,因为css中的相对路径是相对于css资源的地址的。 这里可以用一个loader来把css中的绝对路径直接都改为相对路径。

module.exports = function(source) {
  //这里就把需要的正则加上
  source = source.replace('your reg', '');
  return source;
};

如何验证

  1. 仓库代码扫描cdn地址
  2. 使用puppeteer工具启动页面进行资源扫描,可以手动替换globalCDNUrl进行实验检测,有遗漏
  3. 容灾演练

问题

  1. 第三方资源不容易处理
  2. 验证过程未见得完备