一.前言 目前的轉轉app是一個典型的hybrid app,採用的是業內主流的做法: 客戶端內有大量業務頁面使用webview內載入h5頁面承載。 其優點是顯而易見的,即:web頁面上線頻度滿足快速迭代的業務需求,不受客戶端審核和發版的時間限制,也可以將各個業務線的開發工作分攤到各個業務的fe團隊上 ...
一.前言
目前的轉轉app是一個典型的hybrid app,採用的是業內主流的做法: 客戶端內有大量業務頁面使用webview內載入h5頁面承載。
其優點是顯而易見的,即:web頁面上線頻度滿足快速迭代的業務需求,不受客戶端審核和發版的時間限制,也可以將各個業務線的開發工作分攤到各個業務的fe團隊上,使得個業務線可以並行開發。
而缺點,則不言而喻的在於客戶端內webview載入h5頁面,準確來說是web應用的性能和體驗,是肯定不及客戶端的。本篇文章中,筆者將會梳理立足於本團隊內,根據團隊的特點和制約,開發並實踐web應用的靜態資源離線系統的過程與實踐。
痛點1:
現今本團隊內的端內web應用,均是由webpack構建打包而成的單頁或多頁web應用,前端工程構建完成的結果是這樣的畫風
可以看出其靜態資源中,不乏體積幾百k~幾m不等,而這些靜態資源均是首次打開頁面時需要下載的,並且在web應用有更新時,這些靜態資源文件均會發生變化,也需要重新下載。
導致:首次打開·線上h5資源更新·網路條件差的時候,或者本地頁面緩存失效的時候。 出現:
這使得移動端網頁體驗像一塊巨石—它包含了大量 CPU 計算的 JavaScript 包,拖延了頁面的載入和可交互的速度。 而對於任何一家互聯網公司,性能往往與利益直接相關。 面對海量的交易用戶,提升web應用載入的體驗成為了fe和app 工程師極力重視解決的一個問題。
痛點2
其一: 筆者之前也調研過 service worker等利用web api 來實現pwa的離線緩存方案,但目前轉轉的app使用的還是系統原生的webview。暫時不相容pwa特性.(點擊查看本團隊過往對於pwa項目的嘗試總結)
其二: 目前各個業務團隊使用的技術棧的範圍比較廣,涵蓋vue及react等生態方案。同時各個業務線均在保持快節奏的業務開發,需要設計一套能良好工作,更重要的是可以讓各業務線前端工程可以低成本無痛接入,對業務代碼不會產生侵入,不會引發風險與問題的接入方案。
我們的方案:
在調研了騰訊 手機QQ 阿裡 美團 新浪 等公司的實現方案後,我們設計了自己的web應用靜態資源離線系統方案:
圖片是粗糙的,印象是模糊的。下麵我們從 前端構建和發佈測 和 app測 兩個方向分別來分別闡述:
1.前端構建和發佈測:
我們採用騰訊alloy團隊出品的webpack離線包插件:ak-webpack-plugin,其可以根據配置,將webpack的構建出的靜態資源,壓縮成映射了靜態資源在cdn路徑url的zip壓縮包。 同時在配置的過程中,也可以選擇 排除掉 部份文件(比如圖片,並不是所有構建出的圖片都是用得到的)。其生成的壓縮結構如下:
在此過程中,我們不需要關註資源之間載入的依賴關係,更不需要關註具體的業務邏輯。 只需要關註webpack構建後生成的資源文件夾的結構。
把生成和上傳離線資源包的過程封裝成了一個npm script後,就可以方便地在各個需要接入離線系統的項目中移植,相比起pwa等方法,算是一個改造成本很低的方案。解決了接入的痛點。
生成的離線資源如何讓客戶端如何上傳呢?
交由各個fe工程師手動上傳嗎? 這樣顯然不符合效率最大化的原則。幸好我們有持續集成和發佈工具jenkins。在本團隊的發佈與上線流程中,jenkins代替fe工程師構建與部署前端項目。使前端項目也像傳統的app與後端項目一樣做到了開發與構建部署分離,提高了團隊的效率。 而我們生成和部署離線包的操作,也交由jenkins替我們完成。
2.轉轉app測
在客戶端內,預製了一份最新的各業務線的離線包與版本號的配置表。app安裝後首次啟動時,會將壓縮包解壓到手機rom中。 各業務線配置中包含app訪問線上的靜態資源時需攔截的url規則map:
-
[
-
{
-
"bizid": 13,
-
"date": "1513681326579",
-
"ver": "20171219185710",
-
"offlinePath": [
-
"c.58cdn.com.cn/youpin/activities"
-
]
-
},
-
{
-
-
"bizid": 12,
-
"ver": "20171216234635",
-
"offlinePath": [
-
"img.58cdn.com.cn/zhuanzhuan/zzactivity/ZZHeadline",
-
"m.zhuanzhuan.58.com/Mzhuanzhuan/zhuanzhuan/zzactivity/ZZHeadline"
-
]
-
}
-
]
,當app訪問到與規則map相匹配的地址時,就轉為使用本地的資源,達到離線訪問的目的。
離線化後的資源如何更新呢?
客戶端啟動後,向離線系統查詢最新的各個業務的離線包版本號,依次跟本地配置中的對應業務線比較。 如果需要更新,則再次向離線系統查詢此業務線的離線包信息,離線系統會提供此業務線的離線包的信息(包括基礎包,更新包的信息)。
是否需要更新?
判斷某一業務線是否需要更新離線包的的具體原則如下:
-
線上的各業務線的離線包的版本號與本地配置中 同一業務線的配置不同 (不論最新的離線包版本比本地的更高還是更低。)
-
線上的各業務線的配置中中包含有本地配置有沒有的業務線。
三.資源包載入的優化
1. 增量的資源更新(bsdiff/bspatch)
客戶端在內置(或者在wifi環境下載)了各業務全量包的基礎上,為了減少每次下載更新的資源包的體積,我們採用了增量更新策略,具體為:每次發佈版本的時候,如果此業務線之前已有離線包,則通過 離線系統生成差分包放在cdn。
增量更新的策略使用的是基於node的 bsdiff/bspatch 二進位差量演算法工具npm包-- bsdp。(坑點:因為安裝此node module 含有編譯c語言的過程,所以對於liunx的gcc版本有一定要求,要求必須為4.7以上版本,低於此版本則無法安裝) 客戶端下載差分包後使用bspatch合成更新包。
經過比較 影響bsdiff生成的差分包的體積的因素主要有以下幾類:
-
zip包的壓縮等級。
-
zip包中文件內容的修改:比如js進行了uglify壓縮,變數名的變化可能引起大幅的變更等。
項目A:
11月30日版本 | 12月8日版本 | bsdiff增量包 |
---|---|---|
740.2k | 740.2k | 36.85k |
項目b:
11月23日版本 | 12月19日版本 | bsdiff增量包 |
---|---|---|
415.8k | 418.4k | 172.3k |
可以看出:雖然增量包的體積與全量包的體積的比值雖然各不相同,但無疑是大大減小了客戶端升級離線資源需要下載的流量。
2. 單獨控制各個業務線web應用是否使用離線機制:
為了更好的監控離線包服務端和客戶端的運行情況,並且降低使用離線資源帶來的不可預料的風險,將隱患做到可控。 我們在每一個業務上都加入了使用離線資源的開關和灰度放量的控制。
3. 數據一致性校驗 與 數據安全性
為了防止客戶端下載離線資源時數據在傳輸過程中出現竄擾,導致下載的離線包無法解壓,我們在服務端通過介面中將資源包的md5值告知客戶端,客戶端下載後通過計算得到的資源包的md5值,與之比較,可以保證數據的一致性。
同時為了保證傳輸過程中,資源文件不被篡改,我們將上述的md5值通過rsa加密演算法進行加密。在服務端和客戶端分別使用一對非對稱的密鑰進行加解密。
4.批量下載
在啟動app時,app會集中批量的下載各個業務線的離線包資源,我們在存放離線包資源的cdn中使用了http/2協議,這樣客戶端與cdn只需要建立一次連接,就可以並行下載所有的資源。 在需要下載離線包個數較多的情況下,可以比傳統的http1 有更快的傳輸速度。 同時,客戶端只需要運行一次下載器。減少多次運行下載時對手機cpu的消耗。
四.回退機制:
在實際情況中,為了避免用戶下載離線資源或者解壓資源失敗等問題,導致無法載入相應的離線資源,我們設計了回退機制,在
本地內置的 base包(zip文件)解壓失敗的時候
離線系統介面超時
下載離線資源失敗
增量的離線資源合併失敗等情況下
我們轉而請求線上文件。
五.離線資源管理平臺
對於接入了離線系統的各個業務線的前端工程生成的不同時間版本的離線包 ,我們需要有一個直觀明晰的方案來對其進行管理。 我們開發了離線資源管理平臺,對接離線後臺系統:
其主要的功能包含有:
-
查看與管理各個業務線信息及其離線功能的灰度放量的比例。 對於新接入離線系統的前端工程,灰度放量可以使得部分用戶先使用其離線的特性,並防止不可預料的問題發生在全體用戶上。
通過業務線,版本號,發佈時間等條件,查詢各個版本的離線資源包的列表及其詳細信息。 如 離線包的類型,體積,上線時間等屬性。
併在此基礎上允許將某版本的離線包下線,以實現離線資源版本的回滾功能。
-
針對全局的離線功能,提供了離線功能的開關。
六.技術棧與選型介紹
本篇文章通篇介紹的大體都是思路,實現原理和架構,作為一個技術項目當然也要把使用的技術棧簡單的介紹一下:
-
離線系統的服務端使用的為nodejs實現,因為是一個fe工程師推動的性能基礎項目。自然選擇自己熟悉的語言開發。開發的主動權也可以掌握在自己手上。node版本為較新的LTS版本8.x。
-
node 框架的選型: 市面上主流框架有兩種,express koa ,還有一些是經過一些封裝和定製的框架,例如eggjs等。 對於框架的選型,我們看趨勢。在7.6版本之後,node 就支持了async/await 語法糖,不需要再用yield 和*函數了,koa天生選用await/async的結構,解決了回調地獄,確實是下一代的開發框架,現在也有大量的中間件幫你解決諸多的問題。 而基於koa2的企業級框架eggjs在一開始的時候考慮過, 但是eggjs的功能很強大,有很多功能,多到有些根本用不著,從而導致了他不會輕量級,擴展上的靈活性有待考究,並且eggjs對於我來說是個黑盒,如果有什麼問題,我解決起來將會花費很長的時間。
-
使用了log4js進行node日誌的採集和記錄,log4js作為目前在node上最強大的日誌記錄框架,現在每天其npm包下載量均為6位數。我們將node服務分為resLogger和errorLogger兩個不用等級的分類分別記錄,並使用-yyyy-MM-dd-hh.log的pattern分割日誌,在出問題的時候方便快速定位到日誌文件。
-
使用了log4js進行node日誌的採集和記錄,log4js作為目前在node上最強大的日誌記錄框架,現在每天其npm包下載量均為6位數。我們將node服務分為resLogger和errorLogger兩個不用等級的分類分別記錄,並使用-yyyy-MM-dd-hh.log的pattern分割日誌,在出問題的時候方便快速定位到日誌文件。
-
使用輕量級的nosql資料庫mongodb記錄各離線資源包的數據信息,使用對象模型工具mongoose進行nosql的操作。
-
md5的加密,使用node-rsa庫進行非對稱密鑰的生成,操作和加解密處理。
-
前端離線系統的後臺頁面,採用主流的vue及組件技術棧,並使用talkingdata出品的iview組件庫進行搭建,灰常好用,也向大家推薦。
-
壓力測試:因為是直接面向轉轉客戶端全量的基礎服務,並且預期要接入所有業務的m頁,我們做了幾次不同層面的壓測,保證其起性能可以達到要求。 我是用的為liunx下的基於命令行的壓力測試工具siege, 在併發數200,測試50次的情況下,其結果為:
-
Transactions: 10000 hits
-
Availability: 100.00 %
-
Elapsed time: 15.03 secs
-
Data transferred: 0.3 MB
-
Response time: 0.86 secs
-
Transaction rate: 133.07 trans/sec
-
Throughput: 0.00 MB/sec
-
Concurrency: 113.98
-
Successful transactions: 10000
-
Failed transactions: 0
-
Longest transaction: 10.43
-
Shortest transaction: 0.03
可以看出其表現相對穩定,但上線後就要依賴下述的性能監控系統了。
-
可以看出其表現相對穩定,但上線後就要依賴下述的性能監控系統了。
-
對於這樣的一套基礎服務,對其運行情況的相關監控也是非常重要的,不管是及運行的是否穩定,承載的壓力,占用伺服器硬體的資源情況,我們都需要有詳細的指標來進行觀察,才能採取措施來對服務保駕護航,並採取措施改進。
-
六.運行情況
目前離線系統支撐著轉轉幾乎所有的m頁,每天其api的訪問量為幾百萬次。介面響應時間平均只有20ms,也說明node的穩定輕量,以及koa框架的迅速。
以下為占用機器的cpu和記憶體情況,如果後續出現記憶體泄漏,性能瓶頸,我們會加入緩存層解決。
七.收益:
截止目前轉轉h5靜態資源離線系統已經無痛地 接入了多個端內web應用,在頁面靜態資源載入耗時和由此延伸的可操作時間等性能指標上,均取得了很好的收益。
我們通過錄屏的對比直觀感受一下使用了離線系統後,在載入速度上帶來的提升. 下圖為4G網路情況下,某web應用首次打開的速度對比。左側為使用離線系統,右側為未使用:
通過本團隊開發的性能統計平臺與埋點sdk,我們可以看到幾個關鍵指標的提升:
頁面的靜態資源載入(js,css)耗時的對比數據
平均時間提升約為75%左右
頁面的可操作時間耗時的對比數據
平均時間提升約為15%左右
(採樣頁面為客服中心項目)
面對海量的用戶,節約的流量和網路請求時間消耗都是我們為用戶帶來的價值。
八.展望:
對於技術完美的追求是一個永無止境的過程,而面對複雜網路情況下離線資源下載的一整套過程。我們仍有諸多細節仍需完善和優化,比如:
-
下載引擎的優化 。目前還待實現的功能有離線包下載的斷點續傳和分塊下載的功能,以及下載失敗後重試的邏輯。
-
離線資源下載的統計 雖然我們擁有完善的數據打點採集系統,但是對於各個業務線的離線資源的下載量,現在的統計還有待完善,有個下載量的統計,就可以為後續功能的完善提供建議(此統計可以在node api層或者cdn的nginx層實施)
-
2017年是PWA技術大放光彩的一年, 由它帶來的Service Worker的離線緩存和服務端推送能力可以將web應用的體驗提升一大截。雖然在安卓原生的webview中並不具備一個很好的相容性。但我們仍在探索通過接入功能更為強大的第三方瀏覽器內核來讓hybrid app可以支持.
-
除此之外,離線存儲技術也在業界不斷的探索有呈現百花齊放的局面,出了pwa和本文基於文件的離線策略,也有其他團隊開創了自己的離線存儲方法。
結束語
時至今日,Hybrid模式已經過了它最火的時候,市面上也出現如weex,react-native等直接寫原生組件的框架, 但是,現在使用最多,應用最廣的仍然要屬這種傳統的Hybrid模式,它已經進入了穩定期。但設法提升內嵌h5的性能和體驗,依然是一個永不止步的話題。