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