阿里巴巴技術文章分享:極致的 Hybrid:航旅離線包再加速!
去年(2015)四月份, 我 在 QCon 北京大會上分享了 阿里旅行 Hybrid 實戰經驗 ,作為航旅在 Hybrid 方向探索的一個收尾。當下集團內的重量級 App(手淘、錢包等)在 H5 容器建設上成長迅速,形成了宏大的技術體系,到去年雙十一,H5 容器所承載的流量已經遠遠超過了有限的 Native Page。就航旅來說,H5 承載的流量是 App 的至少四倍。無疑,處在應用層的 Web 技術棧,以其獨有的運行時環境(WebKit)、普適的技術標準(W3C & ES 5、6)以及極具優勢的研發靈活性,成為面向 UI 和交互無線研發的不二解決方案。而 H5 離線技術體系的逐漸成熟,讓 H5 和 Native 的融合達到前所未有的深度。
資源離線的思路簡單、場景復雜,最復雜的就是 H5 活動頁面的離線化。今天跟大家分享的就是航旅去年在 H5 活動頁推包體系 建設的一些實踐。
就加載性能來說,資源離線和發版頻度是一對矛盾。活動頁結構簡單,不具備復雜的業務邏輯,但變更極其頻繁,對時效性要求很高,這種更新頻繁程度在雙十一期間表現最甚。我們在去年 4 月份立項的 獨角獸項目 ,集中精力建設 活動頁推包體系 ,試圖克服這一對矛盾。
資源離線是加載性能優化的“二向箔”
Mobile Web 在弱網提速的唯一的辦法就是堅決杜絕不必要的(運行時)網絡請求 ,即除了 Json 格式的動態數據和其攜帶的商品配圖之外,不應再有其他網絡請求(埋點請求除外)。所以,HTML 和業務數據之間必須解耦。在此基礎上,航旅的 信鴿平臺 力爭完成這四項任務:
- 定時程序:緊隨頁面結構(HTML)變更進行推包
- 增量包:增量包(保性能)和全量包(保安全)必須同時提供
- 服務器推:基于長連接的消息推送 & 客戶端靜默更新
- 自動化:推包過程必須自動化,解放人工,搭好頁面點擊“發布”即完成推包
航旅的頁面也會通過 Zcache 在手淘中做離線,但由于缺少對線上頁面變更的監控,所以雙十一 會場頁面 仍然無法在 Zcache 中緩存 HTML,只能緩存 JS、CSS 和 Img。我們知道,HTML 是否緩存對弱網加載速度影響很大,下圖是航旅 雙十一主會場頁面 在兩個端里 2G 網絡下的加載瀑布:
可以看到,手淘 App 里的渲染時機被 HTML 網絡請求推后了,而且有一個更新后的 CSS 文件請求,不適時宜的阻塞了渲染。
定時程序動態推包
所以,針對去啊 H5 容器,我們寫了兩個程序來彌補 Zcache 在緩存動態頁面方面的不足,
- o2o 在線資源抓取程序 :基于 phantomjs 解析資源
- grunt-inc-offline 增量包計算器 :基于 git-diff 的增量包運算
當然 離線包生成器 也是必不可少的,我們用 信鴿平臺 將它們這樣整合起來:
- o2o 定時程序監聽線上頁面變更,將其所攜帶的資源(HTML、CSS、JS 和部分圖片)抓取下來
- 增量包計算器 會計算好與之前若干版本之間的增量文件,配合 包生成器 將增量包逐一構建打包,同時生成好每個增量包的 Diff Json
- 調用 Clam 命令通過 Gitlab 將資源包部署至 CDN,以備手機端更新。
- Gitlab 倉庫 的更新會觸發一個 Hook 腳本,調用 tSync 服務器的接口,來通知資源變更
- tSync 服務器沙箱完成消息封裝,包括了第二步生成了的 Diff Json 文本
- tSync 長連接將消息指令下發給手機終端
- 手機終端拼好資源文件鏈接,從 CDN 將增量包更新下來,隨后執行 Diff Json 中的指令,完成包的更新。
其中,獲取增量包時,手機會將本地離線倉庫版本帶上,和遠端 tSync 消息中的更新包版本一起拼成資源包的 URL( 一個真實的增量包 URL ),格式形如:
http://g.alicdn.com/trip/h5-op2op/{$線上最新版本}/{$本地包版本}.zip
另外, Diff Json 也很干凈,包含了“新增”、“刪除”、“更改”,這樣可以讓客戶端來刪除舊文件,減少新包覆蓋后的冗余。平臺的易用性上, 信鴿平臺 的操作界面里很貼心的加上了“快速下線”功能,即一旦發現離線包更新到達率不夠,可以立即做離線包下線,端的虛擬域會自動切換到線上頁面。
可以看到,整個系統關鍵在兩個平臺的銜接,即 信鴿平臺 和 tSync Server 。兩者的分工很明確:
- 信鴿平臺面向在線的 URL 完成資源抓取、構建和部署。
- tSync Server 完成消息推送和動態更新。
只要思路捋順,整個推包的邏輯設計并不難,難的是這些模塊的實現是否可靠健壯,其中較為核心的模塊就是 o2o 定時抓取程序,o2o 定時程序是@弘樹 開發的獨立的命令行腳本,將資源離線的過程中,需要根據文件內容來決定文件路徑的哈希值,即只要文件內容不變,離線后的引用路徑就不變,這樣就比較容易由 git-diff 程序來計算增量文件,畢竟文件是否屬于“新增”應當看內容是否有變化,而不應該根據文件名(或者版本號)的不同來判斷。
由于 o2o 是基于 phantomjs 來抓資源,所以,以 o2o 為原型我們衍生出了 o2o-capture 項目,將線上頁面完全靜態化到本地,用來做 TMS 系統掛掉的容災備份方案 ,也是棒棒的。
有了完整的外圍設施,手機端就可以聚焦在文件 IO 的性能優化上了, 之前也介紹過 ,航旅 H5 容器用多層保障來加速 Local File 的文件讀寫,一方面避免不必要的 IO、另一方面將資源池管理和運行時的緩存管理隔離開,確保各自程序任務聚焦、高效,下圖是手機端的兩個重要進程:
- 資源預加載進程:在實際訪問頁面之前,將資源預加載到緩存池并更新 Cache Map
- 創建 WebView 進程:只聚焦本地資源讀寫,別的什么也不干
所以手機端 touch 到網絡的環節收斂到了兩處,第一,Package Update Controller,第二,WebView 本身必要的網絡請求:
最終,在定時程序的幫助下,我們可以放心的將 HTML 也離線到端,而不必擔心更新不及時的問題,配合高性能的 H5 容器,做到秒出就是自然而然的事情了。我們來看 2G 下去啊 App 和 手淘加載 航旅會場頁 的速度對比,顯然,去啊 App 的離線更干凈徹底,不管首次加載還是二次加載,速度都是很可觀的:
從全網的性能數據看 亦是如此,下圖是航旅雙十一預售階段的數據,10月28日主會場頁在去啊、錢包、手淘里各種網絡情況下的 DomReady 時間統計。在 2G 網絡下,支付寶和手淘基本都卡在 HTML 請求阻塞上。
三端在三個網絡下的 DomReady 時間對比(單位秒),結果是顯而易見的:
應當說明的是,這是在手淘和錢包沒有條件緩存活動頁 HTML 資源的情況下拉的數據,依照上面的思路,我們也寫了一個外圍工具 zcache pusher ,來半自動化推送動態更新的頁面,理論上手淘也是可以 做到 2G 離線加速的 。
此外, Zcache 較早前就有計劃 和新版 TMS 嘗試打通 ,讓離線操作自動化起來,期待能盡快看到進展,和我們一樣,Zcache 也會面臨這個問題:“ 發版頻度和離線包更新到達率 ”的問題。那么,影響包的到達率的因素都是什么呢?
發版頻度 vs 離線包更新到達率
信鴿的整個體系對性能的考驗不是來自架構設計,而來自硬件瓶頸,尤其是當定時程序檢測到線上頁面頻繁部署(比如雙十一期間,航旅會場頁面每天更新頻率峰值多達四十多次每天),頻繁推包會帶來兩個問題:
- 手機終端是否能及時更新到最新版的離線包
- 如果要保證更新足夠及時,頻繁靜默更新又會大量占用手機的流量
顯然,這兩點是相互矛盾的。 我們既希望用戶盡可能及時的更新到最新版的離線包,又不希望這種頻繁推送過多占用手機流量。這種情況下,增量包只能確保用戶及時更新的情況下,盡可能少的占用流量,而無法完全杜絕 。也就是說,不管是手淘、錢包還是航旅 App,目前離線包的推送策略仍然過于“全量”,稍顯粗暴,用戶“無條件”接受所有更新。即目前所有端都做不到對用戶行為的精準判斷,做不到用戶需要(訂閱)A,我就給他推包 A。所以, 信鴿平臺 和 tSync Server 都存在很大升級空間。
So,在這次雙十一執行過程中,在航旅 App 里,這一對矛盾究竟如何表現?下圖是10月28日會場頁面最新一次推包后的 客戶端更新比例 ,左圖是推包 1 小時后,右圖是推包 2 小時后。
天哪,看看今天執行了多少次推包!
可以看到,相比于過去傳統的線上頁面部署,離線包的部署時效性顯然慢一些,不夠100%所見即所得。這也是為了換取加載速度不得不做的犧牲。但基本上每次推包 3 個小時候可以完成 90% 以上的更新。受用戶所在網絡和打開時機等因素影響,散落在手機端里的離線包舊版本的碎片化依然非常嚴重。 不管用了多少優化手段,頻繁修改頁面、頻繁推包都會對頁面體驗帶來負面影響。
所以,對于時效性很強的頁面,比如凌晨零點的發布需要切會場的場景,需要頁面即時部署即時生效。若要走離線有兩個方案可以選擇:
- 提前(至少四個小時)推離線包,需要在頁面中寫好定時切換的邏輯
- 提前推消息指令,先將離線頁面從端刪除,在切換時同時執行線上部署和離線推包, 去啊客戶端支持虛擬域 , 在線離線頁面 URL 在容器中保持一致,在保證線上頁面可用的情況下,逐步擴大離線包在端的覆蓋率。
但不論哪個方案,我們都不可能像過那樣部署線上資源那樣輕松了。 如果需要更高的時效性 + 更快的加載速度,則必須適度減少瑣碎需求變更,降低推包次數。從這個角度看,決定性能的因素這里已經主要不在技術上、而在工程上了。
今年手淘(天貓)雙十一主會場頁面也遇到同樣的狀況,本來 Weapp 可以非常優雅的將 H5 Page 轉義成 Native Page,理論上是可大大提速的,但還是 為了滿足頁面動態性更新和個性化配置,不得不引入一些額外的網絡開銷,這些網絡開銷不合時宜的阻塞了布局的渲染,在弱網里的影響更大。 我們來看去啊 App 里首次進航旅主會場(H5)和手淘中首次進天貓主會場在移動 3G 下的加載速度:
可以看到,H5 頁面是逐級加載的,Native Page 是等待請求完成后瞬時渲染的。所以,不管是 H5 還是 Native,只要是應對這種頻繁修改部署變更的活動頁面,都會遇到加載上的瓶頸。 信鴿平臺 是解決這對矛盾的一個緩沖,但根本上還是要從控制頁面靈活性角度來求解。
但是,這種離散性未必都是壞事,它能適度緩解安裝包體積膨脹的壓力。
安裝包體積極限
大家相信嗎, 手淘和貓客的安裝包都過百兆了。這已經到了一款電商 App 包體積的極限 。在 上一篇文章中也提到 ,目前客戶端技術架構在面對新功能的井噴時顯得力不從心。H5 是一個方案,將資源和內容置于遠端,但又會極大稀釋客戶端的體驗。離線包技術就很合事宜的彌合這對矛盾。
但這顯然不夠,和錢包 App 的早期一樣,航旅 App 在構建安裝包時就會“預裝”一部分重要 H5 資源,但面臨高速迭代的產品需求,這個緩沖區是遠遠不夠用的。目前,錢包和手淘顯然已經將這個緩沖擠占完全擠占:
以去啊 App v6.0 為例 ,7 M 的 H5 離線包承載了將近 40% 的功能性頁面 和 100% 的活動頁面。所以只要你的 H5 頁面質量夠高,H5 離線包的體積消耗是非常劃算的。 這也是在航旅 BU 正在發生的事情,即便是在交互極其華麗的“去啊 App 6.0 行程”項目中,PM 還是不斷從前端團隊抽調同學參與一些關鍵頁面的研發,一方面大家已經不擔心 H5 體驗的瓶頸,另一方面,“前端同學搭界面真是快!!!”
所以, 高性能 H5 容器 配合 高效的離線包推送體系 ,再加上 高質量的 H5 代碼 (通過 Clam 工具來保證),做出全網絡“無縫秒出”的體驗是完全沒有問題的( 體驗視頻 ):
大家還可以感受下航旅這次雙十一的無線 狂歡城和跑步游戲在3G網絡中的表現 ,
經過上面這些折騰,最終就達成了我們希望的結果:
- 快速的頁面研發
- 靈活的部署、持續交付
- 無縫秒出
- 劃算的功能體積比
- 時效性強的活動頁也能做到高效離線化!
這就是航旅無線技術團隊正在做的事情。好像很爽的樣子,下面我們來說但是吧。
另一種混合
由于 WebView 對 App 進程來說是一個沙盒,所以 H5 頁面的內存分配和 CPU 分片都是 WebView 獨立完成,前端代碼因為普遍缺少細致的內存管理,所以內存泄露時有發生,以至于H5 容器一定程度會影響 Crash 率。比如手淘 Android 就會限制打開的 WebView 堆棧的個數來減少內存壓力。
ReactNative 是一個解法,就像我跟@小馬 開玩笑時講的,“前端同學用 React 搭了一個 App,就好像 Java 開發同學用 Bootstrap 搭了一個后臺界面一樣興奮”。 和 Bootstrap 一樣,ReactNative 是業余 Native 開發同學的腳手架,是無法做出面向 C 端的產品的,只能做一做“阿里內外”這種量級的應用。
還有一個方向就是重新設計動態的 Native 頁面布局,集團內代表性的就是 鳥巢 、 Dynative 和今年雙十一手淘在嘗試的基于 Web Component 在三個端同構渲染的 Weapp,盡管對于復雜列表還是偶有性能問題,但至少整個 UI 的渲染已經脫離了 WebView,內存更加可控。 我們也必須承認,在運行時性能上,不管是 H5 翻譯 Native 還是原生 Native,都要強過 H5 容器的,尤其是在超長復雜列表的渲染上 。相對于鳥巢和 Dynative 的全 UI 渲染,航旅也在規劃 H5 和 Native 的交叉渲染技術的嘗試,總體思想是借助 JSCore 和一個刪減版的 Webkit 內核來渲染翻譯好的 HTML 片段,頁面交由 Native 來拼裝:
這個方向我們也是剛剛起步,希望能和兄弟 BU 們一同共建。
小結
航旅在 Hybrid 方向上做的事情,并非要證明航旅 H5 比其他端快,畢竟應用場景不同(比如手淘就 有選擇的忽略 3G 和 2G 網絡 )。但從航旅和集團各 BU 的混合開發實踐來看,其實大家都在朝著一個方向努力, Native 期望獲得 H5 快速開發和部署能力、H5 期望獲得更快的速度和更高的硬件調用權限 ,兩個思路:
-
H5 容器技術(得益于 Clam 工具的保障,手淘、錢包、去啊 各自的容器,已經做到90%相互兼容):
- 優勢:獨立的 WebView,對前端開發友好,天然獲得多端兼容的特性
- 劣勢:外圍體系化建設難度大
- 需要克服:一,硬件調用能力,通過 橋協議 解決;二,秒出保證,通過“離線包體系” + “精心編程的 H5 頁面”解決
-
H5 代碼 Native 化( 鳥巢 、 Dynative 、ReactNative、Weapp,互不兼容):
- 優勢:H5 代碼編譯成二進制代碼直接運行,天然的秒出體驗
- 劣勢:對前端開發極其不友好
- 需要克服:一,閹割版的布局能力,通過增加對 CSS3 標簽的支持來解決;二,無法做到多端兼容,通過限制 H5 語法來解決 </ol> </li> </ol>
可見,兩個思路都各有取舍、各有克服,都很難完美,從實踐效果來看,H5 容器更加適合哪些對多端部署有要求的 BU,航旅就是一個典型。而 H5 的 Native 化方案更加適合獨立客戶端的一些私有場景,比如支付寶和天貓。所以要根據自身需求來選擇技術路線。
我作為一名傳統的前端工程師,從 PC 時代轉戰到無線,從去年航旅開始無線研發模式的探索以來,我也很幸運的嘗鮮各種 Native 技術,從開始的好奇,到現在一大堆體系和工具的落地,一系列探索讓 我自己腦洞大開 ,也打破了之前固有的編程理念,這兩年前端技術被顛覆的如此劇烈,讓人有點 不敢相信自己的眼睛 。我想 一方面,PC Web 開發的迅速衰落為無線前端技術快速崛起帶來契機,另一方面,無線技術快速崛起,帶來的不僅僅是技術體系的混合(H5 和 Native),更多的呼喚人的混合 。我們很欣慰的看到一大堆前端同學在研究 ReactNative,另外一大堆 Native 同學在研究 W3C 和 HTML5。打破技術邊界、擁抱無線技術的 All In,充滿好奇,用懷疑一切的眼睛去看待自己固有的技術理念,投身并享受新一輪的無線技術變革,我想,或許正是因為此,使得阿里無線端技術體系伴隨業務的增長,不斷走向百花齊放、走向多元的吧。
To Be Continue...
一些 Q & A:
Q:現在 Wifi 是最多的用戶網絡,為什么要糾結于弱網用戶?
A: 首先,我們希望在最嚴苛的環境中考練技術,再者,廣大(地級)市、縣鄉這些 Wifi 覆蓋率低的地區,我們都親身體驗過那里 4G 和 3G 是什么網速。
Q:這種強混合的研發模式,對前端技術框架有什么影響?
A: 影響是顛覆式的。前端的模塊化編程過于依賴 Loader 和上層的模塊規范,而 Mobile Web 里是不是還強依賴 Loader 要打一個問號,一方面,Loader 本身太重,另一方面,Loader 是在運行時去組織資源依賴加載,顯然會搶占寶貴的運行時資源,資源的組織和加載應當下沉交給更底層的 H5 容器或者工具去解決。
Q:H5 容器即是瀏覽器,怎么去平衡 Web 的加載性能和運行時性能?
A: 這篇文章主要說的就是加載性能的解決方案。運行時性能我們也有一攬子的 最佳實踐 ,另開一篇介紹。
Q:基于 H5 容器畢竟不像瀏覽器,怎么快速打離線包調試?
A: 這方面手淘、錢包 都有最佳實踐。航旅通過工具可以實時構建 zip 離線包, 直接拷貝到手機即可 。目前是最土,也是最有效的辦法。
Q:航旅怎么做到 7 M 的離線包完成 App 里40%的功能頁面?
A: 我們化了一整年來做體積瘦身的優化,另開篇介紹吧。
Q:上面提到的離線數據從哪里來的?
A: 航旅自己提供了一個非常全面的打點方案 tracker ,目前部署上抽離的還不是很夠,但基本上我們想要的關鍵數據可以比較精確的取到了。大部分數據可以通過 魚眼 來查看。
該文章來自阿里巴巴技術協會( ATA )精選集
</div>來自: http://yq.aliyun.com/articles/2939?spm=5176.100239.yqblog1.10
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!