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