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