內容來源:2017年6月24日,騰訊前端高階工程師周明禮在“騰訊Web前端大會 TFC 2017 ”進行《QQ錢包h5應用開發實踐》演講分享。IT 大咖說作為獨家視訊合作方,經主辦方和講者審閱授權釋出。
閱讀字數:3071 | 5分鐘閱讀
摘要
移動網際網路時代,提高網頁效能是每個前端團隊的目標。作為QQ錢包團隊的前端工程師,我們是如何通過自研nodejs服務和利用service worker實現H5頁面秒開?讓我們來探討一下QQ錢包H5應用的開發實踐。
QQ錢包眾多H5應用
2015年我們正式成立了錢包團隊,從剛開始QQ錢包只有一個錢包入口,一直髮展到今天,已經開發出了話費充值、卡券、積分、企鵝網咖、城市服務以及智慧校園等一系列服務。
QQ錢包H5應用開發挑戰
接入層伺服器壓力大
QQ錢包H5應用日均pv在1000w以上,推廣期pv可達上億的級別,需要解決伺服器效能優化問題。
行動網路環境複雜
為了提高使用者的體驗,需要儘快的吐出頁面,安卓平臺下一般要求為3秒,而在ios平臺則是要求2秒以內,在某些業務場景下直接要求秒開。
互動場景複雜
移動端特別是安卓平臺的web效能容易造成瓶頸,例如長列表渲染問題,圖片記憶體佔用問題,css3動畫效能問題都需要去解決。
基於SERVICE WORKER的快取管理方案
瀏覽器快取
以卡券頁面為例,整個頁面總共會發出35個請求,其中有13個請求是資料上報,剩下的都是有效請求。而這些有效請求中,又有9個是JS的請求,有8個是IMG,還有一些其它的請求。
我們大概可以評估出一個頁面可能有77%的靜態資源。一般靜態資源請求的變化比較少,我們不希望瀏覽器去重複下載一些相同的資源,所以我們就會採用到瀏覽器快取來優化我們的頁面。
我們一般通過配置一些http請求頭去控制我們的快取策略,然後通過版本號來更新我們的資源。
但在我看來,這樣的流程存在著3個小問題。
快取機制不足
更新不可靠。由於CDN的更新不是實時的,很多時候我們的資源已經發布到線上了,它的CDN還沒有更新。
離線體驗不佳。現在的瀏覽器快取在離線體驗上是不好的,明明已經快取在本地了,但是斷開網路開啟頁面之後還是會顯示未連線到網際網路。
不可定製化,例如無法增量更新。現在的瀏覽器快取主要是通過配置,但如果需要實現一些自定義的策略是做不到的。
Service Worker
ServiceWorker是瀏覽器為了解決之前AppCache在管理離線快取上的不足,而提供的在Web應用程式與伺服器之間的代理層。
總的來說,Service Worker就是一段在瀏覽器後臺自動執行的程式,負責協助瀏覽器,管理和響應所有從Web應用發出的請求,以達到更好的離線體驗。
效能有所增強,比如預取並快取使用者可能需要的資源,比如頁面中所需的靜態資原始檔;可以同步後臺資料同步;響應來自其它源的資源請求;集中接收計算成本高的資料更新;後臺服務鉤子;自定義模板用於特定URL模式以及可以在客戶端進行模組編譯和依賴管理。
等待狀態
到達installed態的Service Worker並不會直接進入activating態,如果瀏覽器中還有其他頁面執行著該Service Worker的一箇舊版本,那麼新的Service Worker就會處於等待的狀態,直到其他頁面關閉。這主要是為了避免Service Worker中所使用到的資源被意外釋放。
一旦其他相關頁面都關閉了,就意味著舊的資原始檔已經不再需要。這時候我們就可以執行下一步清理的工作。
Activate事件
MoggyCache離線包管理
QQ錢包團隊搭建了一套MoggyCache離線包管理系統。通過這套系統我們可以針對專案配置當前專案需要用到的靜態資源。並且配置了離線包當前是否開啟,或者是針對灰度使用者進行開啟。都可以配置到這個平臺上,而且儲存在內部的一個DB上。
MoggyCache工作原理
我們的node.js服務通過讀取上述的配置動態生成了兩個指令碼,一個是install指令碼,一個是worker指令碼。
install指令碼主要是讀取離線包當前的一個開關以及它當前灰度使用者的策略,來判斷當前使用者是否需要安裝我們的離線包。
一旦判斷出使用者需要安裝離線包,它就會通過註冊的過程把worker指令碼註冊成當前頁面的Service Worker。而這個worker就會把配置裡的資源列表下發到worker指令碼里面。Worker就會走一遍流程把這些資源載入並快取在本地。
MoggyCache新增特性
以上過程僅僅是簡化了我們的一些工作,還是沒有解決問題。所以我們在這基礎之上又新增了它的幾個特性。
自動同步
我們每次配置一個專案的時候,會計算出所有資源的md5,並且儲存在DB裡面。然後我們的node.js服務就會讀取到剛才的配置,把md5下發到剛才的Service Worker裡面。
現在我們更新資源需要進行釋出的時候,在釋出系統上面新增了一個後置指令碼,一旦資源釋出,這個後置指令碼就會觸發離線包系統,去重新計算每一個資源的md5,並重新推送到Service Worker。
這時Service Worker就有了兩個md5,一個是舊版本的md5,一個是當前最新的版本。通過對比這兩個md5,我們就知道哪些資源已經過期了。當發現了過期的資源,Service Worker就會重新到伺服器上拉取最新資源。整個過程是自動的,無需人工干預。這就解決了不可靠的問題。
增量更新
每當有新資源釋出的時候,我們都會通過後置指令碼的方式通知MoggyCache系統。它就會讀取新的資源並進行計算,算出格式的增量包,然後把增量包儲存在服務裡面。
因為md5已經更新了,所以worker指令碼就會重新傳送請求到我們的服務。這時如果服務發現資源有可用的增量包,就會把這個增量包直接返回給Service Worker。Service Worker通過判斷請求頭就可以執行不同的策略。
接入層服務架構
在QQ錢包成立初期,我們使用的接入層架構是PHP + APACHE。當時PHP的版本非常成舊,我們需要開20臺伺服器才能完成所有請求的響應,而單機的QPS只有200。
從這些資料裡可以看出效能還是不夠好。再加上PHP用了騰訊內部編寫的一些私有模組,各個模組之間又有不同的版本,導致它還有部署成本高,擴容困難,apache日誌缺漏,web服務缺乏監控等一系列問題。
經過一段時間的考察,我們最終選擇了NODEJS。
選擇NODEJS的原因
效能優異:node.js採用的是基於libuv庫的非同步io方案,相比apache的多程式同步阻塞方案在效能上的提升明顯的。
前端友好:前端熟悉js的語法,對node.js可以平滑過度,培養新同學更為簡單容易。
社群成熟:成熟的社群和完善的技術文件,大量的成熟模組可以為業務使用。
選擇自研框架的原因
應用場景:我們的MoggyServer服務,主要的使用場景是頁面直出。
更加可控:自研的框架,更加可控,擴充套件性更加強,這些都是我們需要考慮的。
穩定和快速:社群裡的框架大多會包含類似靜態檔案處理,json資料處理等額外的功能,這並不是我們想要的。
服務平滑重啟
我們把伺服器平滑重啟的邏輯內建到框架裡面。通過在釋出系統上配置一個後置指令碼來通知node.js的子程式有新的檔案要釋出,並在子程式接收到訊息之後把這些訊息通知傳送給舊的子程式,它就會停止對外服務。
因為新的程式碼已經發布到線上,就可以用新的程式碼重新建立新的子程式。舊的子程式把原本還在服務的使用者處理完後就會把自己登出掉,整個過程就能達到平滑的效果。
模版引擎優化
我們用積分將一個業務的頁面進行10000次渲染,然後統計它所花費的時間。
這裡我們主要是對ejs的模板巢狀語法做了精簡,所以有了效能上的提升。
用傳統的ejs渲染需要7500毫秒,而用tpl渲染只需要920毫秒。這就是我們做的模版引擎的優化。
MoggyServer線上資料
QQ錢包現在只執行了7臺伺服器,就完成了上千萬級別的服務量。單機QPS從200已經提升到了900。2017年初,我們的總請求數峰值已經到達了1.69億,申請了57臺機器去支撐,但每臺機器的CPU只用了30%。
直出頁面載入
傳統頁面載入方案:從使用者點選入口,native再去拉起webview,等待webview初始化完成後傳送http請求去node服務拉取頁面資料,最後對頁面進行渲染。
SONIC優化方案
序列改並行
相對傳統載入方案中,優化方案在native執行時候例項化webview,同時並行向sonic伺服器發起請求,將此前的序列操作優化為並行,因此此處耗時由sum(webview,request)轉變成max(webview,request),降低了客戶端層面的耗時。
頁面快取
sonic支援對h5頁面級進行本地快取,將返回的頁面分別拆分成資料層以及模版層層分開來快取,生成本地快取檔案,且快取時長為永久快取,如果頁面是沒有任何變化,這時候會完全顯示快取的資料。
增量更新
對於頁面更新的情況,sonic會去對比和計算客戶端快取中的頁面的變更地方,封裝成json資料結構返回給客戶端進行頁面更新以及快取更新,這樣可以大大減小了回包的大小,特別對於行動網路而言可以大幅度為使用者節省了請求流量。
我的分享就到這裡,謝謝大家!