前端可用性保障實踐
如何定義前端服務(wù)可用性
一般可用性都是說后端服務(wù)的可用性,都說我們的服務(wù)可用性到了幾個9,很少有人把可用性放到前端來。其實對于任何一個有UI交互流程的業(yè)務(wù),都會有前端服務(wù)可用性,后端的可用性做的再高,前端一個按鈕寫的有問題點擊不起作用也會導(dǎo)致用戶無法完成流程。
前端服務(wù)可用性包含三個部分:
- 前端代碼可用性(測試質(zhì)量,線上異常)。
- 靜態(tài)資源服務(wù)可用性。
- 網(wǎng)絡(luò)鏈路可用性(DNS劫持、網(wǎng)絡(luò)性能)。
既從業(yè)務(wù)后臺服務(wù)往上,一直到用戶界面,一切都是前端服務(wù),這里面一切用戶可能遇到的問題都是前端可用性的范疇。
這就是我們認為的前端可用性,收銀臺的可用性建設(shè)就是圍繞著這三個部分展開的。
如何衡量前端服務(wù)可用性
前端服務(wù)的可用性衡量和后端的衡量方法相類似,不考慮影響范圍大小,只考慮存在故障的時常,最大化考量可用性??捎眯灾笜?biāo)不是為了讓我們通過復(fù)雜的算法來減小事故對可用性計算的影響,而是為了激勵我們在可觀測范圍內(nèi)做到?jīng)]有問題,越做越好。影響用戶數(shù)、影響訂單數(shù)、影響GMV等指標(biāo)更多的是用于做事故定級。
哪里容易出問題
前端代碼可用性:
- 空指針問題是困擾前端的一個大問題,由于JS本身是弱類型動態(tài)語言,無法在開發(fā)及編譯過程中通過工具推導(dǎo)出可能出現(xiàn)問題的點,進而在前端研發(fā)過程中很容易疏忽造成空指針問題;
- 業(yè)務(wù)邏輯覆蓋率,指的是在業(yè)務(wù)項目當(dāng)中,代碼對動態(tài)邏輯的處理能力,往往在一些復(fù)雜的業(yè)務(wù)項目當(dāng)中,邏輯混亂交錯,前端的展示和進一步的動作由后端控制,這種情況下復(fù)雜的邏輯交織在一起產(chǎn)生無數(shù)分支,邏輯環(huán)境難以模擬,進而很容易在邏輯的處理上產(chǎn)生疏忽;
- 兼容性,問題困擾著各個端的研發(fā),對于前端來說,要面臨的環(huán)境更多,包括平臺、系統(tǒng)版本、瀏覽器版本、WebView版本、Hybrid橋版本等等,很難從測試角度全部覆蓋。
靜態(tài)資源服務(wù)可用性:
- 前端靜態(tài)資源服務(wù)鏈的穩(wěn)定性,例如NGINX、Node等等;
- CDN并不是任何時候都可以正常提供服務(wù)的,可能會遇到SSL證書鏈問題、回源服務(wù)可用性問題等等。
網(wǎng)絡(luò)鏈路穩(wěn)定性:
- DNS劫持是一個老大難問題,大部分情況下是運營商為了節(jié)省跨省流量結(jié)算的費用而進行DNS劫持,走內(nèi)部的緩存,還有一部分情況是廣告,想象一下把收銀臺的代碼劫持并插入一個運營商廣告是有多可怕。
大塊的問題就是上述幾種,細枝末節(jié)的問題就不在這里一一細表,那么具體我們是怎么解決的呢?
怎樣保障才能令人信服?
記得剛剛開始負責(zé)支付業(yè)務(wù)的時候,老板(rank)經(jīng)常問一個問題:“收銀臺穩(wěn)定性怎么保障?”,我當(dāng)時想的就比較簡單,無非就是流程保障、測試保障等等,但這不是老板想聽的,不然他也不會老問我,顯然是當(dāng)時沒有回答出他想要的答案?,F(xiàn)在想想真是“too young too simple, some times naive”。
在美團點評,收銀臺是一個橫向的業(yè)務(wù)基礎(chǔ)服務(wù),是所有業(yè)務(wù)的閉環(huán)環(huán)節(jié),所有線上業(yè)務(wù)交易的最終環(huán)節(jié)全部由收銀臺來完成,它的重要性不言而喻。對于收銀臺來說,有三點需要保障,這三點分別是可用性、體驗和安全,它們共同為一個指標(biāo)服務(wù),那就是“支付成功率”。其中,對支付成功率影響最大的就是可用性。
可用性對支付成功率的影響有多大?
一個小小的bug上線后即使及時發(fā)現(xiàn)并回滾,可能也會造成幾百上千萬營業(yè)額的損失,這對整個團隊來說都是無法接受的。所以,對于收銀臺來說,保障可用性是第一優(yōu)先級。
同時,支付作為一個特殊的業(yè)務(wù)有它對可用性獨到的要求,在可用性保障上必然不是任何業(yè)務(wù)都會用到的那老幾樣兒。老板想聽的是對穩(wěn)定性保障的獨到見解,可復(fù)制的方法,有可用性保障的理論基礎(chǔ),讓任何一個日后負責(zé)這個業(yè)務(wù)的人都能夠照方抓藥,保障前端服務(wù)的穩(wěn)定性。
現(xiàn)在總結(jié)起來可用性的保障分為三個階段:
- 事前
- 事中
- 事后
保障手段分為三個大類:
- 軟的
- 硬的
- 根源的
“軟的”是指用“人”來保障的部分:
- 流程保障
- 規(guī)范保障
- 測試保障
……
“硬的”是指用“工程工具”來保障的部分:
- 靜態(tài)代碼檢查
- 單測
- Web自動化測試
- 持續(xù)集成
- 線上前端異常監(jiān)控
- 業(yè)務(wù)異常監(jiān)控
- 前端服務(wù)異常監(jiān)控
- 網(wǎng)絡(luò)異常監(jiān)控
“根源的”是整個可用性保障的核心,是指通過“技術(shù)選型”來讓系統(tǒng)更健壯,這里面有兩個核心點。
技術(shù)選型要簡單穩(wěn)健
要求在具備伸縮性的基礎(chǔ)下避免任何復(fù)雜的不可控技術(shù)方案。核心鏈路上的所有代碼,團隊要具備維護能力,要減少外部依賴。
這里面有一個關(guān)鍵的選型概念就是“場景契合度”,技術(shù)選型不是你愿意用什么,你熟悉用什么,是在這個業(yè)務(wù)場景和團隊規(guī)模下需要你用什么。
舉個例子,收銀臺是一個單頁應(yīng)用,之所以設(shè)計成單頁應(yīng)用是因為它涉及到的視圖跳轉(zhuǎn)和數(shù)據(jù)傳遞太多,單頁應(yīng)用相比多頁更具優(yōu)勢。那么在選型的時候我們當(dāng)時有React、Angular、Ember等一線前端SPA框架可以選,但最后我們還是自己做了一個簡單的視圖生命周期管理工具,為什么?
- “場景契合度”,React和Angular等前端框架更適合極端復(fù)雜的大型單頁應(yīng)用,為了能夠更好的處理這種復(fù)雜度采用了一系列厚重的工具去約束研發(fā)的過程,其中還包含一些這個項目不會遇到問題的優(yōu)化,例如渲染優(yōu)化等等。對于收銀臺來講,單個視圖中的復(fù)雜度并沒有那么高,可以遇到前端渲染性能瓶頸的項目并不多。
- “源碼維護能力”,收銀臺作為核心鏈路中的核心業(yè)務(wù),在技術(shù)上絕對不允許被動,團隊必須具有核心代碼的維護能力。而依照我們當(dāng)時的團隊規(guī)模,這是不現(xiàn)實的。
在收銀臺這個SPA場景里,我們只需要視圖生命周期管理這個功能。所以,我們參考Cocoa View Controller的生命周期設(shè)計實現(xiàn)了一個簡單的單頁視圖工具“Cyra”,它只負責(zé)視圖生命周期的管理,簡單、拓展性高、源碼可維護且無外部依賴。
避免出現(xiàn)核心鏈路上的可用性短板
舉個例子,網(wǎng)頁首幀渲染優(yōu)化有三種常見方式:
- 手工預(yù)渲染
- 編譯預(yù)渲染
- 服務(wù)器預(yù)渲染(SSR)
其優(yōu)化的核心內(nèi)容就是把盡可能多的首幀渲染所需信息在第一個請求的響應(yīng)中給出,也就是主文檔請求,讓用戶能夠盡可能快的看到內(nèi)容。
從優(yōu)化效果上來講,SSR的效果最好,它可以把JavaScript(以下簡稱“JS”)、CSS、HTML以外的動態(tài)的數(shù)據(jù)一起通過第一個響應(yīng)返回回來。
但是,最后我們選擇的是編譯預(yù)渲染,為什么?
先說什么是SSR。這個概念是新提出來的,但原理很早就存在,類似JSP、ASP這種技術(shù)早年間一直都是SSR,在服務(wù)器端把頁面拼裝好傳遞給客戶端。和佛家的人生三境界一樣,禪中徹悟后又回去了,就像現(xiàn)在的前端服務(wù)化很難做到當(dāng)年微軟ASP.NET Web Form那個水平。
后來前端行業(yè)發(fā)展迅速,發(fā)生了兩個大的變化:
- 大家開始做前后端分離,把靜態(tài)資源單獨管理,好處就不說了,有一個弊端就是當(dāng)用戶瀏覽器把靜態(tài)資源下載下來后可能還需要另外一個請求去獲取這個頁面上的動態(tài)數(shù)據(jù);
- 前端工程化的興起,大家會把CSS JS HTML結(jié)構(gòu)統(tǒng)一打包到一個JS文件中,HTML中只有JS的引用,這樣就導(dǎo)致HTML下載完成后還是白屏,只有等到這個巨型JS下載完成后首幀內(nèi)容才開始渲染。
這時就用到了SSR,通用做法是增加一個Node層,在服務(wù)器端做首屏內(nèi)容的拼接,包含靜態(tài)數(shù)據(jù),這樣能夠保障首幀渲染不僅快,還包含首屏所需要的數(shù)據(jù)。其架構(gòu)如下圖:
可以看到,Node這一層在我們界面請求的核心鏈路上,Node本身的可用性和上下游的服務(wù)相比要差很多,其自身的穩(wěn)定性需要許多其他工具去保障,那么對于這塊業(yè)務(wù)來說,Node這一層成為了“核心鏈路上的可用性短板”,這樣即使背后的各個后端系統(tǒng)可用性再好,只要Node這一層掛掉就會造成用戶無法訪問的問題。
所以基于“避免出現(xiàn)核心鏈路上的可用性短板”這一層考量,我們退而求其次選用“編譯預(yù)渲染”,在編譯期間把首屏結(jié)構(gòu)全部拼裝好,這樣可用性就得到了保障。
關(guān)于Node在服務(wù)端的應(yīng)用上,我認為其實大多數(shù)情況下,不用要比用要難得多,關(guān)于這方面的一些思考可以詳見后續(xù)文章《服務(wù)端為什么不能用Node》。
理論有了,我們是怎么做的?
“軟的”流程規(guī)范部分就不展開講了,各個團隊都差不多,只不過是完善不完善的差異。接下來主要講一下“硬的”部分。
前文提到,“硬的”保障主要指的是工程工具的保障手段,工程工具很多,這里對應(yīng)前文幾大問題的順序,講一講我們的解決方案。
前端代碼可用性部分主要有三個容易出問題的點:空指針、業(yè)務(wù)邏輯覆蓋率、兼容性。
空指針
“空指針”部分的問題解決只能從語言本身來解決,JS本身是弱類型動態(tài)語言,無法在開發(fā)及編譯過程中通過工具推導(dǎo)出可能出現(xiàn)問題的點。針對這一點我們從2015年開始實踐TypeScript(以下簡稱“TS”),當(dāng)時也看了Facebook的Flow,但當(dāng)時Flow還不夠成熟,所以沒有選用。
引入TS后,將我們的弱類型語言變成強類型語言,從編碼過程中就可以幫助過濾掉很大一部分空指針問題,TS強大的類型推導(dǎo)系統(tǒng)可以幫我們分析出系統(tǒng)中的空指針隱患,進而可以解決線上99%的空指針問題。當(dāng)然TS還有很多其他好處,這里就不展開了。
業(yè)務(wù)邏輯覆蓋率
“業(yè)務(wù)邏輯覆蓋率”這個問題的背景不再贅述,由于收銀臺的復(fù)雜度高、case多,復(fù)雜情況下的后端狀態(tài)很難模擬,因此只能采用自動化工具去解決,這就涉及到了“Web自動化流程測試”。
Web自動化流程測試在這種場景下除了可以驗證case的正確性以外,最重要的功能就是要有一個異常強大的case管理模塊。業(yè)界目前并沒有理想的工具能夠支撐我們的場景。
美團點評內(nèi)部有一個我們參與需求的Web自動化流程測試工具“Freekite”,它在case驗證功能的基礎(chǔ)上,有一個強大的可視化case管理模塊,支持復(fù)雜的case細分。除了界面操作的細分外,可以全量Mock或部分Mock后端的數(shù)據(jù)響應(yīng),根據(jù)響應(yīng)拆分出不同的case分支。除此之外,還包含智能自動化斷言功能,斷言基本不需要人工參與。
可能有人要問了,這個case錄完以后萬一遇到界面改版怎么辦?沒關(guān)系,雖然有強大的相似度匹配功能,F(xiàn)reekite還支持單獨節(jié)點的重新錄制,也就完美的解決了case的維護問題,大幅度減少工作量增強效率。緊接著我們會在項目中增加Freekite的持續(xù)集成,在項目的每一個階段進行流程上的自動化回歸驗證,業(yè)務(wù)邏輯覆蓋率的問題就基本解決了。下圖為Freekite可視化Case管理。
兼容性
“兼容性”問題公司內(nèi)部有云測平臺,可以快速在多機型真機上回歸主要流程,可以通過云測平臺覆蓋占有率95%以上的各種機型。然而兼容性也是一樣,需要從根本上選用一個可靠的選型,從而避免在處理兼容性問題上會遇到的拆東墻補西墻最后還是不放心的尷尬境地。兼容性問題在移動端除了布局外主要出現(xiàn)在兩種操作中:點擊和滾動。
前文描述的自主研發(fā)的單頁視圖工具就以最簡單的div隱藏顯示的方式來處理視圖切換,使所有元素處于正常的文檔流當(dāng)中,點擊處理也通過分級降級的方式最大化平衡體驗和兼容性,從而保障了整個項目的兼容性。
靜態(tài)資源服務(wù)可用性主要就是NGINX層的健康檢查及CDN的回源監(jiān)控,這一點公司SRE有強大的系統(tǒng)支持(有關(guān)美團點評SRE的實踐可以參考之前的博客文章),這里就不多講了。
網(wǎng)絡(luò)可用性上最頭痛的問題是DNS劫持,前文講到了DNS劫持方面除了惡意劫持以外,主要是運營商以節(jié)省跨省流量結(jié)算費用為目標(biāo)進行DNS劫持。當(dāng)運營商系統(tǒng)發(fā)現(xiàn)HTTP訪問的域名時會在區(qū)域內(nèi)的服務(wù)器中緩存一份資源,后續(xù)用戶再請求的時候其域名解析會被解析到運營商的服務(wù)器上去由運營商的服務(wù)器直接返回內(nèi)容。
其應(yīng)對方法只有使用HTTPS,但并不僅僅是在原有的域名HTTP的基礎(chǔ)上切換HTTPS那么簡單,還需要保障這個域名不支持HTTP訪問并且沒有被大范圍使用HTTP訪問過。如果不這樣做的話會出現(xiàn)一個問題,運營商在DNS解析的時候并不知道這個域名是用什么協(xié)議訪問的,當(dāng)之前已經(jīng)記錄過這個域名支持HTTP訪問后,不管后續(xù)是否是HTTPS訪問,都會進行DNS劫持。這時如果使用的是HTTPS訪問,會因為運營商的緩存服務(wù)器沒有對應(yīng)的SSL證書而導(dǎo)致請求無法建立鏈接,從而遇到請求失敗的問題。在之前業(yè)務(wù)切換HTTPS的時候就遇到了這個問題,請求成功率從99.96%降低到了96%,花了大量的時間去定位問題。當(dāng)切換了全新的域名后這個問題才得到了解決。
在事后方面,除了強大的支付后臺業(yè)務(wù)系統(tǒng)監(jiān)控外,公司還有完善的通用監(jiān)控系統(tǒng),例如異常監(jiān)控系統(tǒng)可以分級分批上報前端的JS Error及自定義異常,性能監(jiān)控系統(tǒng)Performance可以了解前端的訪問情況做性能分析,網(wǎng)絡(luò)監(jiān)控系統(tǒng)CAT可以快速定位網(wǎng)絡(luò)層性能狀況、區(qū)域DNS劫持狀況等。
作者簡介
禹霖,美團點評前端技術(shù)專家,負責(zé)金融錢包及支付前端團隊。
【本文為51CTO專欄機構(gòu)“美團點評技術(shù)團隊”的原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號聯(lián)系機構(gòu)獲取授權(quán)】































