V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jamblues
V2EX  ›  程序员

[请教+讨论] 前端做负载均衡,如何判断 css 成功加载?

  •  
  •   jamblues · 2019-05-12 23:57:34 +08:00 · 4305 次点击
    这是一个创建于 2026 天前的主题,其中的信息可能已经有所发展或是发生改变。

    一个前端自动切换负载均衡的方案,请教 V 友一下。

    有个前端项目,部署了 N 台 CDN,每台机器都分配了独立域名:

    cdn1.xxx.com

    cdn2.xxx.com

    ...

    步骤一:

    进入页面执行前,先判断 Cookie 中是否有 CDN 变量,如果没有,从以上域名中随机挑出一个写入 Cookie 的 CDN 变量中。

    步骤二:

    加载 css 和 js 的时候,都是通过此类格式加载:

    <script>document.write('<link href="'+CDN+'/css.css" />')</script>
    <script>document.write('<script src="'+CDN+'/js.js"><\/script>')</script>
    

    步骤三:

    页面加载完成后,如果通过判断页面正常加载了,那么就不做任何操作。如果没有正常加载,则可能是该 CDN 挂掉了,然后重新挑 CDN 域名写入 Cookie,然后刷新页面重走步骤一,确保用户看到的是完整的页面。


    这样做的好处:

    1. 低成本的负载均衡

    2. 能快速切换有问题机器,对于 DNS 负载均衡,生效有时间局限性。

    3. 机器方面可减少人工运维,降低成本

    坏处:

    1. 每个页面都得加入一段冗余的判断

    2. 增加 Cookie 和冗余的脚本浪费 HTTP 传输(当然,Cookie 也可以用 localStorage 解决)

    3. CDN 域名更新起来会稍有些麻烦


    那么重点来了,有哪些方法,可以用来判断 JS 或者 CSS 正常加载?

    方案一,已投入使用:

    加入 cdn.js 文件写入一行:

    var CDN_LOADED=1;
    

    引用 cdn.js 后,使用判断

    <script>
    if(typeof CDN_LOADED =='undefined'){
    	// 切换 CDN
    }
    </script>
    

    因为第一种每次都要多请求一个文件,所以不是特别理想。

    引出方案二。

    方案二,求大神指导:

    <script>document.write('<link rel="stylesheet" type="text/css" href="'+CDN+'/css.css" />')</script>
    

    在样式表后,使用

    var styles = document.styleSheets || document.styleSheetList;
    
    if(???) {
       // 切换 CDN
    }
    
    

    这个??? 地方,我试了各种

    // 不行,rules 报错
    if(styles[0].rules == 0)
    // 不行,rules 异常
    if(typeof styles[0].rules != 'undefined')
    ...
    

    也试过 onerror 事件,似乎是没用的。。。

    都不行,请问哪位大神可以指点一二?

    要求:

    • 不要依赖 css.css 里的 rules,只做最简单的判断,最好不要有循环去读 rules 里的内容。

    P.S. 这种文案实际是可行的,好像应用的企业不多?

    34 条回复    2019-05-13 19:23:49 +08:00
    azh7138m
        1
    azh7138m  
       2019-05-13 00:02:24 +08:00
    9102 年了,怎么还 document.write ?
    前厂用的 onerror 来处理,简单说就是自己实现一个 fallback 的逻辑,页面有一个 CDN 列表,资源传多个 CDN,优先加载某一个,一旦有报错就加载另一个,需要配合 AMD loader (其实也不用配合,都是符合 AMD 规范的 js )。
    azh7138m
        2
    azh7138m  
       2019-05-13 00:09:55 +08:00   ❤️ 1
    样式需要封装成 js,onerror 目前无法处理 style 的网络问题。
    不过操作 stylesheet 的性能低的可怕.......
    不过话说回来,service worker 提供了 FetchEvent,可以代理掉所有的请求,也是可以做的,这个倒是没有见到有人这么实现,可能是还要兼容老的浏览器?
    jamblues
        3
    jamblues  
    OP
       2019-05-13 00:45:25 +08:00
    @azh7138m 老哥,可能我把问题写太长,让你误解啦~ TAT。。

    现在情况简短描述一下:

    ```

    用户只能访问到 html 页面,CDN 域名因为服务器或者用户网络原因加载失败了,

    可以通过哪些前端(简单、高效)的手段,来判断用户载加失败这个问题?

    ```

    我举 document.write 也只是尽可能简单的说明问题。

    样式封装成 JS 或者引用 AMD Loader 之类的,都是有可能加载失败的,不但没有解决问题,而且还会增加问题的复杂度。。。(当然如果把库或者 css 写入页面就另说,例如百度的大搜索,就是这么做的)
    xylophone21
        4
    xylophone21  
       2019-05-13 00:56:31 +08:00
    第一感觉这个方案很奇怪,关注一下听听大家的说法
    azh7138m
        5
    azh7138m  
       2019-05-13 01:22:02 +08:00
    @jamblues 封装到 js 里面是为了能用 onerror 监听到错误,还能重新加载。在用 style 加载样式的时候没法直接做 reload,我是想表达这个意思。
    你不用 AMD 规范也行,最后还是会重新发明 AMD。

    或者就是直接放弃 chrome 40 以下用户,使用 FetchEvent 来做这个事情。

    Q:如何知道失败了
    A:
    - 将 css 封装成 js,使用 onerror
    - 使用 FetchEvent 自己封装逻辑
    Accat1024
        6
    Accat1024  
       2019-05-13 03:25:03 +08:00 via Android
    客户端代理一般好像都是 vpn 用的,对比 nginx 负载均衡优势好像不明显。
    KuroNekoFan
        7
    KuroNekoFan  
       2019-05-13 08:31:57 +08:00 via iPhone   ❤️ 2
    要知道 css 有问题的,可以有这样一个方法:写一个特殊一点的 css rule (反正能覆盖 user agent style 即可),然后在一个不影响 ui 的元素上应用这个 rule,再检测这个元素的 computedstyle,超过一定时间的阈值之后,就认为对应的 css 没有加载到
    sm0king
        8
    sm0king  
       2019-05-13 08:50:07 +08:00
    判断其中一个 css 是否生效可以否?
    zephyru
        9
    zephyru  
       2019-05-13 09:14:33 +08:00
    感觉,如果说 CDN 的目的是为了快速的展示网页..这个方案在很多情况下收益是负的....
    要实现效果..无论是 CSS 封装 JS 还是,定时器去判断某个样式是否生效..效率都太低了...
    尤其是后者,如果有多个 CSS 文件的情况下基本不可用...
    即使有一个简单的判断方式...总觉得还是负收益..关注看看...
    mytry
        10
    mytry  
       2019-05-13 09:20:08 +08:00   ❤️ 1
    这话题过去研究了好长时间,可以参考之前写的文章: https://yq.aliyun.com/articles/236582
    Tomorr
        11
    Tomorr  
       2019-05-13 09:31:50 +08:00
    opengps
        12
    opengps  
       2019-05-13 09:34:57 +08:00
    负载均衡是自动的,手动负载均衡的目的是什么?
    防止一个节点失效,自动切换另外一个节点吗?
    opengps
        13
    opengps  
       2019-05-13 09:36:40 +08:00
    另外还有个问题,文中描述肚的是 CDN,这跟负载均衡又是另外一码事
    opengps
        14
    opengps  
       2019-05-13 09:38:09 +08:00   ❤️ 1
    如果非要手动判断失效的 cdn,那么应该考虑的是使用备用域名,而不是同域名的备用二级解析
    因为域名故障,域名阻断等情况,往往是整个域名出问题,这种同域名下的节点切换,仅仅是针对单个二级域名解析出故障有效果
    jamblues
        15
    jamblues  
    OP
       2019-05-13 10:04:44 +08:00 via iPhone
    @sm0king 只需要判断任何一个 css 是否正常加载即可(前提是 css 最好不要有特征依赖)
    jamblues
        16
    jamblues  
    OP
       2019-05-13 10:09:44 +08:00 via iPhone
    @zephyru 不一定是快速展示页面的需求 就像上边二楼老哥说的 而应该算是一个 fallback 的备选方案;相比如果用 dns 做负载的话 生效时间有延迟。如果用 nginx 做负载的话 不一定能实时检测出用户和服务器网络是否通畅。所以才会想出这样的方案
    jamblues
        17
    jamblues  
    OP
       2019-05-13 10:15:35 +08:00 via iPhone
    @mytry 老哥 你这个应该和我的需求差不太多 但是感觉写文章理论偏多 实际应用上还有很多细节没考虑到呃…
    RainFinder
        18
    RainFinder  
       2019-05-13 10:15:36 +08:00
    楼上说得很对
    jamblues
        19
    jamblues  
    OP
       2019-05-13 10:16:53 +08:00 via iPhone
    @Tomorr 老哥 看了代码 你这个只能做接口的 fallback 没法满足我这需求哇😂
    jamblues
        20
    jamblues  
    OP
       2019-05-13 10:22:57 +08:00 via iPhone
    @opengps 其他疑问我在楼上解释了一下。老哥优秀~考虑的比较远哈,域名多样性,服务器稳定性会做为第二步继续考虑…只是第一步都还没解决…
    mytry
        21
    mytry  
       2019-05-13 10:31:49 +08:00
    @jamblues 细节根据自己的业务实现啊
    mikoshu
        22
    mikoshu  
       2019-05-13 10:34:07 +08:00
    关注一波 我也想知道
    Tomorr
        23
    Tomorr  
       2019-05-13 11:11:02 +08:00
    根据 fetch 试探一波,再适当做个缓存,应该是好的办法;
    在回调里面 append 节点,是可行的;

    实际上,我最初想到要做两个功能,另一个是 ping,但是利用 fetch 有跨域的问题,而针对 CDN,一般都支持跨域,所以利用 fetch 试探是可行的
    shuax
        24
    shuax  
       2019-05-13 11:12:49 +08:00
    原来 cdn 是这样用的,我一直用错了。
    jamblues
        25
    jamblues  
    OP
       2019-05-13 11:15:47 +08:00 via iPhone
    @mytry SW 方案是可行的 但是目前不稳定因素有点多 也无法做到优雅降级 所以…还是非常感谢提供的信息
    jamblues
        26
    jamblues  
    OP
       2019-05-13 11:19:42 +08:00 via iPhone
    @Tomorr 感谢🙏 之前想过 但是因为不一定是 SPA 应用 会导致每个页面都需要 perfetch (或缓存)。 假设只有 10% 的用户会加载失败 意味着需要牺牲 90% 用户第一次打开的体验
    johnnyNg
        27
    johnnyNg  
       2019-05-13 11:29:19 +08:00
    ajax 请求 css 文本,成功就把文本添加到 style 标签中,失败就换一个 cdn 请求,但是感觉会降低首屏加载速度,可以的话弄一个骨架屏或者正在加载的提示
    jamblues
        28
    jamblues  
    OP
       2019-05-13 11:37:05 +08:00
    @johnnyNg 是的,这个方案就是方案一。

    目前是按这个方式做的,不过不是 fetch css 太费劲了,js 会好一些。

    如果能减少这个请求,用其它不知道会不会有更好的体验。
    learnshare
        29
    learnshare  
       2019-05-13 12:34:17 +08:00
    前端不负责做负载均衡
    mikoshu
        30
    mikoshu  
       2019-05-13 16:31:24 +08:00
    var cssnum = document.styleSheets.length;
    var ti = setInterval(function() {
    if (document.styleSheets.length > cssnum) {
    // needs more work when you load a bunch of CSS files quickly
    // e.g. loop from cssnum to the new length, looking
    // for the document.styleSheets[n].href === url
    // ...

    // FF changes the length prematurely :()
    CSSDone('listening to styleSheets.length change');
    clearInterval(ti);

    }
    }, 10);

    这个????
    jamblues
        31
    jamblues  
    OP
       2019-05-13 17:26:44 +08:00
    @mikoshu

    我也在 google 上找到过这个,确实兼容浏览器,

    但是这个初衷用来判断 css 是否正常加载,

    加上 try , 改改勉强可以用,但效率实在不高...不如方案一了,so...
    mikoshu
        32
    mikoshu  
       2019-05-13 17:37:12 +08:00
    @jamblues 没看懂方案一,你不是要判断 link 加载的 css 是否加载成功吗
    zhengwhizz
        33
    zhengwhizz  
       2019-05-13 17:42:15 +08:00 via Android
    方案一就行啊,干嘛这么麻烦绕来绕去,多引一个 js 有什么影响?而且你这个网站一个 js 都没用到?
    hailiang88
        34
    hailiang88  
       2019-05-13 19:23:49 +08:00
    方案二,看看这段代码有没有帮助
    ```javascript
    function loadCssWithCallback(uri, callback) {
    var style = $('<link rel="stylesheet" media="all" href="' + uri + '" type="text/css"/>');
    $('head').append(style);

    // try to access the css rules
    var _canGetNodeRules = function (node) {
    var s = node.sheet || node.styleSheet;
    try {
    // try to load the css rules
    var r = s.cssRules;
    return true;
    } catch (e) {
    return false;
    }

    };

    // watch the css loading
    var cssLoadWatcher = function (node) {
    // when the link element has finished processing it's data, we can access the stylesheet and rules
    if (node.sheet || node.styleSheet && _canGetNodeRules(node)) {
    if (callback) callback(uri);
    } else {
    // not yet, let's wait
    window.setTimeout(function () {
    cssLoadWatcher(node);
    }, 10);
    }
    };

    // start watching
    cssLoadWatcher(style[0]);
    }

    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3243 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 12:53 · PVG 20:53 · LAX 04:53 · JFK 07:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.