大流量下的兜底容災方案

原文地址: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打個電話,告知下等待修復,而不是急急忙忙,抓耳撓腮,擔驚受怕天窗來了。

再附上京東的首頁接口和模板正常請求流程圖:

 

小結

本文提供的都是僞代碼,而這些僞代碼的實現並不複雜,也不必寫成組件,主要是提供思路,如何處理大流量高併發下的異步數據接口的兜底容災。

var ajax = require( 'load_async');
// 本質就是$.ajax方法
ajax({
url: '//f.3.cn/index-floor/?argv=aggr',
jsonpCallback: 'jsonpCallbakcAggr', // jsonp回調函數名
params: {}, // 參數
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;
}
});
相關文章
相關標籤/搜索