一个前端自动切换负载均衡的方案,请教 V 友一下。
有个前端项目,部署了 N 台 CDN,每台机器都分配了独立域名:
...
步骤一:
进入页面执行前,先判断 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,然后刷新页面重走步骤一,确保用户看到的是完整的页面。
这样做的好处:
低成本的负载均衡
能快速切换有问题机器,对于 DNS 负载均衡,生效有时间局限性。
机器方面可减少人工运维,降低成本
坏处:
每个页面都得加入一段冗余的判断
增加 Cookie 和冗余的脚本浪费 HTTP 传输(当然,Cookie 也可以用 localStorage 解决)
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 事件,似乎是没用的。。。
都不行,请问哪位大神可以指点一二?
要求:
P.S. 这种文案实际是可行的,好像应用的企业不多?
1
azh7138m 2019-05-13 00:02:24 +08:00
9102 年了,怎么还 document.write ?
前厂用的 onerror 来处理,简单说就是自己实现一个 fallback 的逻辑,页面有一个 CDN 列表,资源传多个 CDN,优先加载某一个,一旦有报错就加载另一个,需要配合 AMD loader (其实也不用配合,都是符合 AMD 规范的 js )。 |
2
azh7138m 2019-05-13 00:09:55 +08:00 1
样式需要封装成 js,onerror 目前无法处理 style 的网络问题。
不过操作 stylesheet 的性能低的可怕....... 不过话说回来,service worker 提供了 FetchEvent,可以代理掉所有的请求,也是可以做的,这个倒是没有见到有人这么实现,可能是还要兼容老的浏览器? |
3
jamblues OP @azh7138m 老哥,可能我把问题写太长,让你误解啦~ TAT。。
现在情况简短描述一下: ``` 用户只能访问到 html 页面,CDN 域名因为服务器或者用户网络原因加载失败了, 可以通过哪些前端(简单、高效)的手段,来判断用户载加失败这个问题? ``` 我举 document.write 也只是尽可能简单的说明问题。 样式封装成 JS 或者引用 AMD Loader 之类的,都是有可能加载失败的,不但没有解决问题,而且还会增加问题的复杂度。。。(当然如果把库或者 css 写入页面就另说,例如百度的大搜索,就是这么做的) |
4
xylophone21 2019-05-13 00:56:31 +08:00
第一感觉这个方案很奇怪,关注一下听听大家的说法
|
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 自己封装逻辑 |
6
Accat1024 2019-05-13 03:25:03 +08:00 via Android
客户端代理一般好像都是 vpn 用的,对比 nginx 负载均衡优势好像不明显。
|
7
KuroNekoFan 2019-05-13 08:31:57 +08:00 via iPhone 2
要知道 css 有问题的,可以有这样一个方法:写一个特殊一点的 css rule (反正能覆盖 user agent style 即可),然后在一个不影响 ui 的元素上应用这个 rule,再检测这个元素的 computedstyle,超过一定时间的阈值之后,就认为对应的 css 没有加载到
|
8
sm0king 2019-05-13 08:50:07 +08:00
判断其中一个 css 是否生效可以否?
|
9
zephyru 2019-05-13 09:14:33 +08:00
感觉,如果说 CDN 的目的是为了快速的展示网页..这个方案在很多情况下收益是负的....
要实现效果..无论是 CSS 封装 JS 还是,定时器去判断某个样式是否生效..效率都太低了... 尤其是后者,如果有多个 CSS 文件的情况下基本不可用... 即使有一个简单的判断方式...总觉得还是负收益..关注看看... |
10
mytry 2019-05-13 09:20:08 +08:00 1
这话题过去研究了好长时间,可以参考之前写的文章: https://yq.aliyun.com/articles/236582
|
11
Tomorr 2019-05-13 09:31:50 +08:00
|
12
opengps 2019-05-13 09:34:57 +08:00
负载均衡是自动的,手动负载均衡的目的是什么?
防止一个节点失效,自动切换另外一个节点吗? |
13
opengps 2019-05-13 09:36:40 +08:00
另外还有个问题,文中描述肚的是 CDN,这跟负载均衡又是另外一码事
|
14
opengps 2019-05-13 09:38:09 +08:00 1
如果非要手动判断失效的 cdn,那么应该考虑的是使用备用域名,而不是同域名的备用二级解析
因为域名故障,域名阻断等情况,往往是整个域名出问题,这种同域名下的节点切换,仅仅是针对单个二级域名解析出故障有效果 |
15
jamblues OP @sm0king 只需要判断任何一个 css 是否正常加载即可(前提是 css 最好不要有特征依赖)
|
16
jamblues OP @zephyru 不一定是快速展示页面的需求 就像上边二楼老哥说的 而应该算是一个 fallback 的备选方案;相比如果用 dns 做负载的话 生效时间有延迟。如果用 nginx 做负载的话 不一定能实时检测出用户和服务器网络是否通畅。所以才会想出这样的方案
|
17
jamblues OP @mytry 老哥 你这个应该和我的需求差不太多 但是感觉写文章理论偏多 实际应用上还有很多细节没考虑到呃…
|
18
RainFinder 2019-05-13 10:15:36 +08:00
楼上说得很对
|
20
jamblues OP @opengps 其他疑问我在楼上解释了一下。老哥优秀~考虑的比较远哈,域名多样性,服务器稳定性会做为第二步继续考虑…只是第一步都还没解决…
|
22
mikoshu 2019-05-13 10:34:07 +08:00
关注一波 我也想知道
|
23
Tomorr 2019-05-13 11:11:02 +08:00
根据 fetch 试探一波,再适当做个缓存,应该是好的办法;
在回调里面 append 节点,是可行的; 实际上,我最初想到要做两个功能,另一个是 ping,但是利用 fetch 有跨域的问题,而针对 CDN,一般都支持跨域,所以利用 fetch 试探是可行的 |
24
shuax 2019-05-13 11:12:49 +08:00
原来 cdn 是这样用的,我一直用错了。
|
25
jamblues OP @mytry SW 方案是可行的 但是目前不稳定因素有点多 也无法做到优雅降级 所以…还是非常感谢提供的信息
|
26
jamblues OP @Tomorr 感谢🙏 之前想过 但是因为不一定是 SPA 应用 会导致每个页面都需要 perfetch (或缓存)。 假设只有 10% 的用户会加载失败 意味着需要牺牲 90% 用户第一次打开的体验
|
27
johnnyNg 2019-05-13 11:29:19 +08:00
ajax 请求 css 文本,成功就把文本添加到 style 标签中,失败就换一个 cdn 请求,但是感觉会降低首屏加载速度,可以的话弄一个骨架屏或者正在加载的提示
|
28
jamblues OP |
29
learnshare 2019-05-13 12:34:17 +08:00
前端不负责做负载均衡
|
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); 这个???? |
31
jamblues OP |
33
zhengwhizz 2019-05-13 17:42:15 +08:00 via Android
方案一就行啊,干嘛这么麻烦绕来绕去,多引一个 js 有什么影响?而且你这个网站一个 js 都没用到?
|
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]); } ``` |