[请教+讨论] 前端做负载均衡,如何判断 css 成功加载? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
jamblues
V2EX    程序员

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

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

    一个前端自动切换负载均衡的方案,请教 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 试探是可行的
    momo1999
        24
    momo1999  
       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     Solana     2833 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 32ms UTC 00:22 PVG 08:22 LAX 16:22 JFK 19:22
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86