大流量的下兜底容災方案
隨著網絡的普及,上網的成本和門檻越來越低,很多網站的流量也是蹭蹭蹭的往上漲,而頁面上的數據來源也不確定,可能來自多個平臺,也可能是有專門的人員在手動維護。由于數據來源眾多,出錯的概率也會增加,為了降低頁面在大流量下的維護成本,本文做了一些闡述。
兜底容災的必要性
一個日均承載幾千萬上億流量的網頁,會經常出現哪些問題呢?
- 某個接口掛了,前端拿不到數據或者拿到的數據不夠,頁面展示就會出問題,出現空白或者某個模塊直接天窗。
- 用戶因為網絡問題或者安裝了某些插件,導致頁面廣告、接口請求掛掉,從而頁面出現問題
前者的概率不是很大,因為網頁上的請求 QPS 都是預先評估過的,只要前端請求沒有成倍激增,并且后端壓力都在系統監控范圍內,不會出太大的岔子。但是一旦出問題,頁面上就有可能空白一大塊,如果后端排查和處理問題不及時,很可能從小問題演變成故障。
第二個問題也是比較嚴峻的,據統計,不管網站做的多簡潔,總是會有千分之一的用戶因為網絡或者瀏覽器插件問題導致頁面訪問失敗或者部分接口請求失 敗,比如一個 pv 一億的網站,按照千分之一計算,一個接口每天會有 10w 左右的 pv 請求失敗,而請求接口一多,頁面上整體的請求失敗量就很高了,這個數據會達到幾百萬。
如何兜底,如何容災
兜底容災的方案有很多,目的就是讓請求失敗而頁面展示依然正常。下面說一說常用的幾個方案:
1. 再請求一次
照顧到用戶體驗,同時也考慮到一個請求的正常發送、接受時間,我們把超時時間設置為 5s,超過 5s 或者請求的結果狀態為 failed ,則重新請求一次。所以我們可以重新封裝下 Ajax 模塊,如:
// 設置請求次數 var tryTimes = 2; Ajax({ url: url, timeout: 5000, dataType: "jsonp", // try tryTimes: tryTimes });
這種處理方案對于提交訂單、選中商品到購物車的頁面比較合適,因為操作流是確定的,提交一次不成功,很自然的想到再提交一次,只是用戶等待的不同階段應該用不同的文案來提醒。而對于展示類的數據請求,不太適合多次失敗嘗試。所以首頁未采用這種方案。
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,此時會推一份通用的數據,這個通用的數據也可以作為接口的備份源。
兜底容錯實踐
我們很容易得到如下的操作流程:
而這里存在的問題是:
- 獲取緩存數據后,不好對數據格式進行判斷,一般來說,只有有效的數據才能存到本地儲存中,而判斷是否有效往往存在誤差
- 兜底數據沒有及時更新
- 程序只會報警,但是不會自動修復
存在的隱患是:
- 前端每次改版,如更換接口、更換人員,兜底數據沒有及時更新
- 如果兜底數據也存在錯誤,則頁面一定出現空白天窗
所以對整個流程做了一些改進:
數據經過統一平臺輸出,在輸出之前,我們將數據推一份到 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打個電 話,告知下等待修復,而不是急急忙忙,抓耳撓腮,擔驚受怕天窗來了。
小結
本文提供的都是偽代碼,而這些偽代碼的實現并不復雜,也沒必要寫成組件,主要是提供思路,如何處理大流量高并發下的異步數據接口的兜底容災。
如果你有更好的想法,可以提出來,一起交流下~
本文鏈接:http://www.barretlee.com/blog/2015/09/16/backup-solution-at-big-traffic/
- 作者: 小胡子哥 (Barret Lee)
- 發表日期: 2015-09-16 08:03:53
- 文章分類: 前端雜燴,網絡交互
- 文章標簽: 兜底容災
- 版權聲明: 自由轉載-非商用-非衍生-保持署名(創意共享3.0許可證)
- 最后編輯時間: 2015-09-16 08:25:06