目前的轉轉app是一個典型的hybrid app,採用的是業內主流的作法: 客戶端內有大量業務頁面使用webview內加載h5頁面承載。css
其優勢是顯而易見的,即:web頁面上線頻度知足快速迭代的業務需求,不受客戶端審覈和發版的時間限制,也能夠將各個業務線的開發工做分攤到各個業務的fe團隊上,使得個業務線能夠並行開發。前端
而缺點,則不言而喻的在於客戶端內webview加載h5頁面,準確來講是web應用的性能和體驗,是確定不及客戶端的。本篇文章中,筆者將會梳理立足於本團隊內,根據團隊的特色和制約,開發並實踐web應用的靜態資源離線系統的過程與實踐。vue
現今本團隊內的端內web應用,均是由webpack構建打包而成的單頁或多頁web應用,前端工程構建完成的結果是這樣的畫風node
能夠看出其靜態資源中,不乏體積幾百k~幾m不等,而這些靜態資源均是首次打開頁面時須要下載的,而且在web應用有更新時,這些靜態資源文件均會發生變化,也須要從新下載。react
致使:首次打開·線上h5資源更新·網絡條件差的時候,或者本地頁面緩存失效的時候。 出現:webpack
這使得移動端網頁體驗像一塊巨石—它包含了大量 CPU 計算的 JavaScript 包,拖延了頁面的加載和可交互的速度。 而對於任何一家互聯網公司,性能每每與利益直接相關。 面對海量的交易用戶,提高web應用加載的體驗成爲了fe和app 工程師極力重視解決的一個問題。nginx
其一: 筆者以前也調研過 service worker等利用web api 來實現pwa的離線緩存方案,但目前轉轉的app使用的仍是系統原生的webview。暫時不兼容pwa特性.(點擊查看本團隊過往對於pwa項目的嘗試總結)web
其二: 目前各個業務團隊使用的技術棧的範圍比較廣,涵蓋vue及react等生態方案。同時各個業務線均在保持快節奏的業務開發,須要設計一套能良好工做,更重要的是可讓各業務線前端工程能夠低成本無痛接入,對業務代碼不會產生侵入,不會引起風險與問題的接入方案。算法
在調研了騰訊 手機QQ 阿里 美團 新浪 等公司的實現方案後,咱們設計了本身的web應用靜態資源離線系統方案:sql
圖片是粗糙的,印象是模糊的。下面咱們從 前端構建和發佈測 和 app測 兩個方向分別來分別闡述:
咱們採用騰訊alloy團隊出品的webpack離線包插件:ak-webpack-plugin,其能夠根據配置,將webpack的構建出的靜態資源,壓縮成映射了靜態資源在cdn路徑url的zip壓縮包。 同時在配置的過程當中,也能夠選擇 排除掉 部份文件(好比圖片,並非全部構建出的圖片都是用獲得的)。其生成的壓縮結構以下:
在此過程當中,咱們不須要關注資源之間加載的依賴關係,更不須要關注具體的業務邏輯。 只須要關注webpack構建後生成的資源文件夾的結構。
把生成和上傳離線資源包的過程封裝成了一個npm script後,就能夠方便地在各個須要接入離線系統的項目中移植,相比起pwa等方法,算是一個改形成本很低的方案。解決了接入的痛點。
生成的離線資源如何讓客戶端如何上傳呢?
交由各個fe工程師手動上傳嗎? 這樣顯然不符合效率最大化的原則。幸虧咱們有持續集成和發佈工具jenkins。在本團隊的發佈與上線流程中,jenkins代替fe工程師構建與部署前端項目。使前端項目也像傳統的app與後端項目同樣作到了開發與構建部署分離,提升了團隊的效率。 而咱們生成和部署離線包的操做,也交由jenkins替咱們完成。
在客戶端內,預製了一份最新的各業務線的離線包與版本號的配置表。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相匹配的地址時,就轉爲使用本地的資源,達到離線訪問的目的。
離線化後的資源如何更新呢?
客戶端啓動後,向離線系統查詢最新的各個業務的離線包版本號,依次跟本地配置中的對應業務線比較。 若是須要更新,則再次向離線系統查詢此業務線的離線包信息,離線系統會提供此業務線的離線包的信息(包括基礎包,更新包的信息)。
是否須要更新?
判斷某一業務線是否須要更新離線包的的具體原則以下:
線上的各業務線的離線包的版本號與本地配置中 同一業務線的配置不一樣 (不論最新的離線包版本比本地的更高仍是更低。)
線上的各業務線的配置中中包含有本地配置有沒有的業務線。
客戶端在內置(或者在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 |
能夠看出:雖然增量包的體積與全量包的體積的比值雖然各不相同,但無疑是大大減少了客戶端升級離線資源須要下載的流量。
爲了更好的監控離線包服務端和客戶端的運行狀況,而且下降使用離線資源帶來的不可預料的風險,將隱患作到可控。 咱們在每個業務上都加入了使用離線資源的開關和灰度放量的控制。
爲了防止客戶端下載離線資源時數據在傳輸過程當中出現竄擾,致使下載的離線包沒法解壓,咱們在服務端經過接口中將資源包的md5值告知客戶端,客戶端下載後經過計算獲得的資源包的md5值,與之比較,能夠保證數據的一致性。
同時爲了保證傳輸過程當中,資源文件不被篡改,咱們將上述的md5值經過rsa加密算法進行加密。在服務端和客戶端分別使用一對非對稱的密鑰進行加解密。
在啓動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的性能和體驗,依然是一個永不止步的話題。