天天看點

極緻的 Hybrid:航旅離線包再加速!

極緻的 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 網絡下的加載瀑布:

極緻的 Hybrid:航旅離線包再加速!

可以看到,手淘 app 裡的渲染時機被 html 網絡請求推後了,而且有一個更新後的 css 檔案請求,不适時宜的阻塞了渲染。

是以,針對去啊 h5 容器,我們寫了兩個程式來彌補 zcache 在緩存動态頁面方面的不足,

o2o 線上資源抓取程式:基于 phantomjs 解析資源

grunt-inc-offline 增量包電腦:基于 git-diff 的增量包運算

當然離線包生成器也是必不可少的,我們用信鴿平台将它們這樣整合起來:

極緻的 Hybrid:航旅離線包再加速!

o2o 定時程式監聽線上頁面變更,将其所攜帶的資源(html、css、js 和部分圖檔)抓取下來

增量包電腦會計算好與之前若幹版本之間的增量檔案,配合包生成器将增量包逐一建構打包,同時生成好每個增量包的 diff json

調用 clam 指令通過 gitlab 将資源包部署至 cdn,以備手機端更新。

gitlab 倉庫 的更新會觸發一個 hook 腳本,調用 tsync 伺服器的接口,來通知資源變更

tsync 伺服器沙箱完成消息封裝,包括了第二步生成了的 diff json 文本

tsync 長連接配接将消息指令下發給手機終端

手機終端拼好資源檔案連結,從 cdn 将增量包更新下來,随後執行 diff json中的指令,完成包的更新。

其中,擷取增量包時,手機會将本地離線倉庫版本帶上,和遠端 tsync 消息中的更新包版本一起拼成資源包的 url(一個真實的增量包 url),格式形如:

另外,diff json也很幹淨,包含了“新增”、“删除”、“更改”,這樣可以讓用戶端來删除舊檔案,減少新包覆寫後的備援。平台的易用性上,信鴿平台的操作界面裡很貼心的加上了“快速下線”功能,即一旦發現離線包更新到達率不夠,可以立即做離線包下線,端的虛拟域會自動切換到線上頁面。

極緻的 Hybrid:航旅離線包再加速!

可以看到,整個系統關鍵在兩個平台的銜接,即信鴿平台和 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 本身必要的網絡請求:

極緻的 Hybrid:航旅離線包再加速!

最終,在定時程式的幫助下,我們可以放心的将 html 也離線到端,而不必擔心更新不及時的問題,配合高性能的 h5 容器,做到秒出就是自然而然的事情了。我們來看 2g 下去啊 app 和 手淘加載航旅會場頁的速度對比,顯然,去啊 app 的離線更幹淨徹底,不管首次加載還是二次加載,速度都是很可觀的:

極緻的 Hybrid:航旅離線包再加速!

從全網的性能資料看亦是如此,下圖是航旅雙十一預售階段的資料,10月28日主會場頁在去啊、錢包、手淘裡各種網絡情況下的 domready 時間統計。在 2g 網絡下,支付寶和手淘基本都卡在 html 請求阻塞上。

三端在三個網絡下的 domready 時間對比(機關秒),結果是顯而易見的:

極緻的 Hybrid:航旅離線包再加速!

應當說明的是,這是在手淘和錢包沒有條件緩存活動頁 html 資源的情況下拉的資料,依照上面的思路,我們也寫了一個外圍工具 zcache pusher,來半自動化推送動态更新的頁面,理論上手淘也是可以做到 2g 離線加速的。

此外,zcache 較早前就有計劃和新版 tms 嘗試打通,讓離線操作自動化起來,期待能盡快看到進展,和我們一樣,zcache 也會面臨這個問題:“發版頻度和離線包更新到達率”的問題。那麼,影響包的到達率的因素都是什麼呢?

信鴿的整個體系對性能的考驗不是來自架構設計,而來自硬體瓶頸,尤其是當定時程式檢測到線上頁面頻繁部署(比如雙十一期間,航旅會場頁面每天更新頻率峰值多達四十多次每天),頻繁推包會帶來兩個問題:

手機終端是否能及時更新到最新版的離線包

如果要保證更新足夠及時,頻繁靜默更新又會大量占用手機的流量

顯然,這兩點是互相沖突的。我們既希望使用者盡可能及時的更新到最新版的離線包,又不希望這種頻繁推送過多占用手機流量。這種情況下,增量包隻能確定使用者及時更新的情況下,盡可能少的占用流量,而無法完全杜絕。也就是說,不管是手淘、錢包還是航旅 app,目前離線包的推送政策仍然過于“全量”,稍顯粗暴,使用者“無條件”接受所有更新。即目前所有端都做不到對使用者行為的精準判斷,做不到使用者需要(訂閱)a,我就給他推包 a。是以,信鴿平台和 tsync server 都存在很大更新空間。

so,在這次雙十一執行過程中,在航旅 app 裡,這一對沖突究竟如何表現?下圖是10月28日會場頁面最新一次推包後的用戶端更新比例,左圖是推包 1 小時後,右圖是推包 2 小時後。

極緻的 Hybrid:航旅離線包再加速!

天哪,看看今天執行了多少次推包!

可以看到,相比于過去傳統的線上頁面部署,離線包的部署時效性顯然慢一些,不夠 <code>100%</code> 所見即所得。這也是為了換取加載速度不得不做的犧牲。但基本上每次推包 3 個小時候可以完成 90% 以上的更新。受使用者所在網絡和打開時機等因素影響,散落在手機端裡的離線包舊版本的碎片化依然非常嚴重。不管用了多少優化手段,頻繁修改頁面、頻繁推包都會對頁面體驗帶來負面影響。

是以,對于時效性很強的頁面,比如淩晨零點的釋出需要切會場的場景,需要頁面即時部署即時生效。若要走離線有兩個方案可以選擇:

提前(至少四個小時)推離線包,需要在頁面中寫好定時切換的邏輯

提前推消息指令,先将離線頁面從端删除,在切換時同時執行線上部署和離線推包,去啊用戶端支援虛拟域, 線上離線頁面 url 在容器中保持一緻,在保證線上頁面可用的情況下,逐漸擴大離線包在端的覆寫率。

但不論哪個方案,我們都不可能像過那樣部署線上資源那樣輕松了。如果需要更高的時效性 + 更快的加載速度,則必須适度減少瑣碎需求變更,降低推包次數。從這個角度看,決定性能的因素這裡已經主要不在技術上、而在工程上了。

今年手淘(天貓)雙十一主會場頁面也遇到同樣的狀況,本來 weapp 可以非常優雅的将 h5 page 轉義成 native page,理論上是可大大提速的,但還是為了滿足頁面動态性更新和個性化配置,不得不引入一些額外的網絡開銷,這些網絡開銷不合時宜的阻塞了布局的渲染,在弱網裡的影響更大。我們來看去啊 app 裡首次進航旅主會場(h5)和手淘中首次進天貓主會場在移動 3g 下的加載速度:

極緻的 Hybrid:航旅離線包再加速!

可以看到,h5 頁面是逐級加載的,native page 是等待請求完成後瞬時渲染的。是以,不管是 h5 還是 native,隻要是應對這種頻繁修改部署變更的活動頁面,都會遇到加載上的瓶頸。信鴿平台是解決這對沖突的一個緩沖,但根本上還是要從控制頁面靈活性角度來求解。

但是,這種離散性未必都是壞事,它能适度緩解安裝包體積膨脹的壓力。

大家相信嗎,手淘和貓客的安裝包都過百兆了。這已經到了一款電商 app 包體積的極限。在上一篇文章中也提到,目前用戶端技術架構在面對新功能的井噴時顯得力不從心。h5 是一個方案,将資源和内容置于遠端,但又會極大稀釋用戶端的體驗。離線包技術就很合事宜的彌合這對沖突。

但這顯然不夠,和錢包 app 的早期一樣,航旅 app 在建構安裝包時就會“預裝”一部分重要 h5 資源,但面臨高速疊代的産品需求,這個緩沖區是遠遠不夠用的。目前,錢包和手淘顯然已經将這個緩沖擠占完全擠占:

極緻的 Hybrid:航旅離線包再加速!

以去啊 app v6.0 為例,<code>7 m</code> 的 h5 離線包承載了将近 <code>40%</code> 的功能性頁面 和 <code>100%</code> 的活動頁面。是以隻要你的 h5 頁面品質夠高,h5 離線包的體積消耗是非常劃算的。這也是在航旅 bu 正在發生的事情,即便是在互動極其華麗的“去啊 app 6.0 行程”項目中,pm 還是不斷從前端團隊抽調同學參與一些關鍵頁面的研發,一方面大家已經不擔心 h5 體驗的瓶頸,另一方面,“前端同學搭界面真是快!!!”

是以,高性能 h5 容器配合高效的離線包推送體系,再加上高品質的 h5 代碼(通過 clam 工具來保證),做出全網絡“無縫秒出”的體驗是完全沒有問題的(體驗視訊):

極緻的 Hybrid:航旅離線包再加速!

大家還可以感受下航旅這次雙十一的無線狂歡城和跑步遊戲在<code>3g</code>網絡中的表現,

經過上面這些折騰,最終就達成了我們希望的結果:

快速的頁面研發

靈活的部署、持續傳遞

無縫秒出

劃算的功能體積比

時效性強的活動頁也能做到高效離線化!

這就是航旅無線技術團隊正在做的事情。好像很爽的樣子,下面我們來說但是吧。

由于 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 來拼裝:

極緻的 Hybrid:航旅離線包再加速!

這個方向我們也是剛剛起步,希望能和兄弟 bu 們一同共建。

航旅在 hybrid 方向上做的事情,并非要證明航旅 h5 比其他端快,畢竟應用場景不同(比如手淘就有選擇的忽略 3g 和 2g 網絡)。但從航旅和集團各 bu 的混合開發實踐來看,其實大家都在朝着一個方向努力,native 期望獲得 h5 快速開發和部署能力、h5 期望獲得更快的速度和更高的硬體調用權限,兩個思路:

h5 容器技術(得益于 clam 工具的保障,手淘、錢包、去啊 各自的容器,已經做到<code>90%</code>互相相容):

優勢:獨立的 webview,對前端開發友好,天然獲得多端相容的特性

劣勢:外圍體系化建設難度大

需要克服:一,硬體調用能力,通過橋協定解決;二,秒出保證,通過“離線包體系” + “精心程式設計的 h5 頁面”解決

h5 代碼 native 化(鳥巢、dynative、reactnative、weapp,互不相容):

優勢:h5 代碼編譯成二進制代碼直接運作,天然的秒出體驗

劣勢:對前端開發極其不友好

需要克服:一,閹割版的布局能力,通過增加對 css3 标簽的支援來解決;二,無法做到多端相容,通過限制 h5 文法來解決

可見,兩個思路都各有取舍、各有克服,都很難完美,從實踐效果來看,h5 容器更加适合哪些對多端部署有要求的 bu,航旅就是一個典型。而 h5 的 native 化方案更加适合獨立用戶端的一些私有場景,比如支付寶和天貓。是以要根據自身需求來選擇技術路線。

我作為一名傳統的前端工程師,從 pc 時代轉戰到無線,從去年航旅開始無線研發模式的探索以來,我也很幸運的嘗鮮各種 native 技術,從開始的好奇,到現在一大堆體系和工具的落地,一系列探索讓我自己腦洞大開,也打破了之前固有的程式設計理念,這兩年前端技術被颠覆的如此劇烈,讓人有點不敢相信自己的眼睛。我想一方面,pc web 開發的迅速衰落為無線前端技術快速崛起帶來契機,另一方面,無線技術快速崛起,帶來的不僅僅是技術體系的混合(h5 和 native),更多的呼喚人的混合。我們很欣慰的看到一大堆前端同學在研究 reactnative,另外一大堆 native 同學在研究 w3c 和 html5。打破技術邊界、擁抱無線技術的 all in,充滿好奇,用懷疑一切的眼睛去看待自己固有的技術理念,投身并享受新一輪的無線技術變革,我想,或許正是因為此,使得阿裡無線端技術體系伴随業務的增長,不斷走向百花齊放、走向多元的吧。

to be continue...

一些 q &amp; 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 裡<code>40%</code>的功能頁面?

a:我們化了一整年來做體積瘦身的優化,另開篇介紹吧。

q:上面提到的離線資料從哪裡來的?

a:航旅自己提供了一個非常全面的打點方案 tracker,目前部署上抽離的還不是很夠,但基本上我們想要的關鍵資料可以比較精确的取到了。大部分資料可以通過魚眼來檢視。

該文章來自阿裡巴巴技術協會(ata)精選集