原文地址:http://www.barretlee.com/blog/2015/09/16/backup-solution-at-big-traffic/ javascript
注:本博主稍有修改!前端
随着网络的普及,上网的成本和门槛愈来愈低,不少网站的流量也是蹭蹭蹭的往上涨,而页面上的数据来源也不肯定,可能来自多个平台,也多是有专门的人员在手动维护。因为数据来源众多,出错的几率也会增长,为了下降页面在大流量下的维护成本,本文作了一些阐述。java
兜底容灾的必要性
一个日均承载几千万上亿流量的网页,会常常出现哪些问题呢?git
- 某个接口挂了,前端拿不到数据或者拿到的数据不够,页面展现就会出问题,出现空白或者某个模块直接天窗。
- 用户由于网络问题或者安装了某些插件,致使页面广告、接口请求挂掉,从而页面出现问题
前者的几率不是很大,由于网页上的请求 QPS 都是预先评估过的,只要前端请求没有成倍激增,而且后端压力都在系统监控范围内,不会出太大的岔子。可是一旦出问题,页面上就有可能空白一大块,若是后端排查和处理问题不及时,极可能从小问题演变成故障。github
第二个问题也是比较严峻的,据统计,无论网站作的多简洁,老是会有千分之一的用户由于网络或者浏览器插件问题致使页面访问失败或者部分接口请求失败,好比一个 pv 一亿的网站,按照千分之一计算,一个接口天天会有 10w 左右的 pv 请求失败,而请求接口一多,页面上总体的请求失败量就很高了,这个数据会达到几百万。ajax
如何兜底,如何容灾
兜底容灾的方案有不少,目的就是让请求失败而页面展现依然正常。下面说一说经常使用的几个方案:json
1. 再请求一次后端
照顾到用户体验,同时也考虑到一个请求的正常发送、接受时间,咱们把超时时间设置为 5s,超过 5s 或者请求的结果状态为 failed ,则从新请求一次。因此咱们能够从新封装下 Ajax 模块,如:浏览器
// 设置请求次数 var tryTimes = 2; Ajax({ url: url, timeout: 5000, dataType: "jsonp", // try tryTimes: tryTimes,
success: fn,
complete: fn(){
if (status == 'timeout') {
abort...
}
}
});
这种处理方案对于提交订单、选中商品到购物车的页面比较合适,由于操做流是肯定的,提交一次不成功,很天然的想到再提交一次,只是用户等待的不一样阶段应该用不一样的文案来提醒。而对于展现类的数据请求,不太适合屡次失败尝试。因此首页未采用这种方案。缓存
2. 缓存每一次请求到本地
如今的浏览器都支持本地储存(不管使用 userData 仍是 localStorage),当每次请求到达用户浏览器的时候,把请求的数据缓存一份到本地储存,那么下次请求失败就可使用上次的数据啦~
Ajax({ url: url, dataType: "jsonp", success: function(data){ // 缓存数据到本地 cache(DATAKEY, data); show(data); }, error: function(){ // 请求失败,获取本地缓存数据 var data = cache(DATAKEY); show(data); } });
这种方式是比较经常使用的,每次请求成功都会缓存最新的数据。不过这里存在两个问题:
- 若是用户第一次访问就失败了呢?要知道新用户是比较多的。
- 缓存的数据是否具备时效性,若是过时了呢?好比是一个推荐接口,推荐的商品用户已经购买过了,可是访问的时候接口挂掉,依然现实用户购买过的商品,这个逻辑是不太能接受的。
固然,有总比没有好吧,就算是第一次访问,这个几率是至关低的,就算数据过时,可是依然是正确的连接,因此基本能够接受。
3. 备用接口(硬兜底)
会给本身的网页接口准备备用接口的网站,估计不会不少。咱们能够作一个包装:
Ajax({ url: url, // 备份接口 backUrl: backUrl });
一旦请求失败,进入备用数据接口请求备份数据。一样的,这里也存在一个问题:若是接口是个性化的,则每一个用户访问这个接口拿到的数据都不同,那么这个备份接口该如何推数据?若是备用接口的数据跟正常接口同样,那还不如直接去请求两次。
因此这里提到的备用接口,主要是数据的硬兜底,硬兜底的来源有两个:
- 运营维护一份数据,推送到 CDN,每一份数据都有一个固定的地址
- 后端向 CDN push 一份通用数据。咱们知道个性化都是使用 cookie 去识别用户的,对于没有浏览器记录的新用户就没有 cookie,此时会推一份通用的数据,这个通用的数据也能够做为接口的备份源。
4. 京东容灾封装
首页经过封装改造 $.ajax 来实现,使用 $.ajaxPrefilter 和 $.ajaxTransport 方法对每一个异步请求进行捕获处理,将接口,模板请求的重试,超时,缓存,兜底调用等封装起来:
var ajax = require('load_async');
// 本质就是$.ajax方法
ajax({
url: '//f.3.cn/index-floor/?argv=aggr',
jsonpCallback: 'jsonpCallbackAggr', // jsonp回调函数名
param: {},
needStore: true, // 是否须要缓存
storeSign: '3aad2efsdf', //用户判断缓存是否过时的标记
timeout: 3000,
times: 2, // 超时重试次数
backup: '//www.3.cn/bak/aggr', // 兜底借口
dataCheck: function (result) { // 接口数据校验,校验接口返回数据,若为true则走正常逻辑,为false则自动调用兜底逻辑
if (result && result.code === 0) {
return true;
}
return false;
}
});
Athena 兜底接口服务,能够指定接口生成一份兜底数据接口,平台会定时去抓取指定接口数据,而后生成兜底数据到CDN,从而生成对应的兜底接口,这样让正常接口多一份兜底保障。
兜底容错实践
咱们很容易获得以下的操做流程:
而这里存在的问题是:
- 获取缓存数据后,很差对数据格式进行判断,通常来讲,只有有效的数据才能存到本地储存中,而判断是否有效每每存在偏差
- 兜底数据没有及时更新
- 程序只会报警,可是不会自动修复
存在的隐患是:
- 前端每次改版,如更换接口、更换人员,兜底数据没有及时更新
- 若是兜底数据也存在错误,则页面必定出现空白天窗
因此对整个流程作了一些改进:
数据通过统一平台输出,在输出以前,咱们将数据推一份到 CDN 做为备份,产生另外一个接口,一旦原始接口请求失败,则直接请求备份的接口,这个在规则对应和即时更新上能够作到很赞!那么基本的流程就是这样:
不过为了确保无误,个人建议是,页面上每一个接口必须对应一个运营手填的数据,这个做为最后的硬兜底,而这个硬兜底也会被缓存到本地,整个流程就造成一个闭环。那么,剩下的工做就只有监控和警报了。
下面是一串伪代码:
var url = interfaceURL; var backUrl = interfaceBackURL; var hardBackUrl = hardDataURL; var cacheTime = 10day; Ajax({ url: url, backurl: backUrl, success: function(){ // 缓存数据到本地 cache(DATAKEY, data, cacheTime); show(data); }, error: function(){ // 请求失败,获取本地缓存数据 var data = cache(DATAKEY); if(data) { Reporter.send(/*WARN*/); show(data); } else { Reporter.send(/*ERROR*/); _failed(); } } }); // 请求硬兜底 function _failed() { Ajax({ url: hadrBackUrl, success: function(data){ // 缓存数据到本地 cache(DATAKEY, data, cacheTime); show(data); }, error: function(){ Reporter.send(/*SUPER_ERROR*/); show(data); } }); }
注意到,咱们在上面使用了缓存失效时间,考虑到数据的及时性,设置为 10 天。backUrl 是 url 的备份地址,hardBackUrl 是运营填写的备份数据,整个流程都在闭环之中,因此出问题的几率就大大下降了,即使是后端接口出错,咱们也能够看着监控信息,放心的给后端开发GG打个电话,告知下等待修复,而不是急急忙忙,抓耳挠腮,担惊受怕天窗来了。
再附上京东的首页接口和模板正常请求流程图:
小结
本文提供的都是伪代码,而这些伪代码的实现并不复杂,也不必写成组件,主要是提供思路,如何处理大流量高并发下的异步数据接口的兜底容灾。