導讀: 2021年10月21日,「QCon 全球軟體開發大會」在上海舉辦,網易智企技術 VP 陳功作為出品人發起了「AI 時代下的融合通訊技術」專場,邀請到網易雲信、網易音視訊實驗室、網易雲音樂的技術專家與大家一起分享融合通訊技術趨勢和演進方向、視訊通訊關鍵技術探索及實踐、音訊 AI 演算法在 RTC 中的實踐、網易雲音樂網路庫跨平臺化實踐等話題。
我們會針對四個演講專題逐一進行介紹與分享,本期是我們的第四期,網易雲音樂網路庫跨平臺化實踐。
嘉賓介紹:陳鬆茂,2020年底加入網易雲音樂,一站式網路解決方案技術負責人,目前從事跨平臺網路解決方案相關研究,旨在降低多端網路工作的研發成本,以及多應用間的接入成本,用較低的成本獲得持續可觀的效能及能效提升。曾就職於阿里,長期從事於 Chromium 相關技術研究和應用,擁有豐富的瀏覽器開發和核心升級經驗。
前言
在做網路優化的過程中,由於系統網路庫的差異,你不得不把同一型別的優化,在各端進行適配,你感覺工作量在成倍增長。
正因為多種適配版本的存在,一致性無法完全保證,另外由於系統網路庫提供的資料採集能力不同,你很難採集到完全對齊的資料,這意味著服務端需要做一些相容。
隨著移動網際網路的崛起,PC 在變得小眾化,但這不代表 PC 不需要對應的網路優化或監控服務,但你確實無暇顧及。大廠可以不惜代價,籌建龐大的團隊深入協議棧進行重新開發優化,但你希望只投入有限的資源,就可以換來可觀的網路效能提升。
不知道這些場景是否有戳中你的痛點,或者你曾經也被困擾過?如果我告訴你,接下來的分享能幫你解決上述所有的問題,你是否有興趣瞭解下?如果我告訴你,你可能什麼都不需要做,就能輕鬆讓網路的響應時間縮短 30%~40%,你是否更有興趣了?
本次演講主題為:網易雲音樂網路庫跨平臺化實踐。本文聚焦跨平臺化,主要涉及工程化相關思路,分四個部分,分別是背景介紹、方案設計、落地實踐及後續規劃.
背景介紹
下圖是當前雲音樂網路庫在各端的架構,可以看到上層的網路策略和監控服務等等在各端都有獨立的實現,這會帶來如下問題:
- 重複造輪子:所有的網路策略、質量監控,每個端都有實現。當你需要加一個新的策略的時候,每個端都得實現一遍,這是嚴重的人員浪費。
- 缺乏一致性:網路庫及人的差異,導致輪子的標準和完成度不一致。
- 資源不對等:移動端研發比較多,但 PC 端研發就很少,這導致 PC 端很多網路策略和質量監控都是殘缺的。
- 深度優化難:對網路庫來說,要深層次的優化,需要對網路模組有充分的理解。但是這當下,我們可以在 Android 上進入深度的挖掘,但是不可能四個端裡面都有做網路的同學來做專門的優化。這導致每個端只做一些淺層次的優化,不會做深層次的挖掘,因為沒有人。
通過前面的分析,大家可以清楚的看到,其實每個端都實現一套,是不靠譜的。結合當下面臨的一些痛點,自然而然產生了一個共同的訴求:讓所有的端都共享一套網路解決方案。再進一步,是不是所有的 App 都能共享一套網路解決方案。
解決上述問題最主要的做法是整體方案跨平臺化,但也面臨著不少挑戰。
- 首先是跨平臺性。功能層面上是不是隻有策略需要下沉,策略下沉以後,三方 SDK 是否能下沉?另外跨平臺方案有很多,可以僅僅是移動端的跨平臺化,也可以是移動端加桌面端都跨平臺化。
- 其次是能力複用。使用 C++ 重寫的網路策略能不能在不同場景下複用,除了重構業務,是不是可以提煉一些框架?這些框架是不是能複用?另外後續新的需求出來了,是不是能在現有的基礎上很輕易的擴充套件上去?
- 最後是後期推廣。一開始建立這個專案的時候就明確後期需要進行推廣,不僅僅提供給網易雲音樂使用。所以,其它 App 在接入網路庫時成本怎麼樣?是不是能很簡單的接入?這決定著後期能否順利推廣。另外,每個 App 都有各自的特點,比如網易雲音樂有免流功能,但其它 App 可能不需要免流,換句話說,不同的 App 可以根據實際情況進行定製,這也是我們面對的挑戰。
針對上述挑戰進行的方案設計,是整個分享的核心。
方案設計
跨平臺設計思路
一開始設計思路很簡單,下圖中右側綠色部分是用 C++ 重寫的包含核心邏輯的 SDK,在這個基礎上,由 Android、iOS 分別接入這個 SDK,底下的網路庫不變。這個改造成本很低,但接入成本較高,移動端接入 SDK 的時候,需要對我們提供的 SDK 進行二次包裝。於是我們就在想,能不能接入的時候不要搞五六個 SDK,合併成一個 SDK?
接下來就有另外一個方案,我們想把上層的綠色部分全部合併到一個 SDK,這需要在現有的網路庫之上抽象出網路代理層,基於網路代理,上面做各種策略和業務邏輯封裝,這樣才能把所有邏輯合併到一個 SDK 裡面,這個做完了以後整體接入成本就會大大降低。但網路代理層很厚,隨著時間的遷移,底下的網路庫會變,上面的網路代理層需要不斷適配,這樣適配工作就會沒完沒了。
更進一步思考,能不能讓網路庫統一掉?我們把上面的網路代理層抽掉,選用一個通用的跨平臺網路庫進行替代,在這基礎之上,封裝我們的策略和服務,這樣整體鏈路就跨平臺化了,這是一個思路。
最後小結一下,跨平臺的方法有很多,在網易雲音樂,我們選擇了最徹底的一種,即整個網路解決方案包括網路庫、上層策略以及一些服務全部跨平臺化。
跨平臺網路庫設計
上面講了跨平臺最關鍵的點就是選擇一個合適的跨平臺網路庫,我們選擇了 Cronet,它跨五端,完全能支援雲音樂的場景;協議層面支援傳統的 HTTP、HTTP/2,QUIC;另外普及程度上,Google 系全部用了 Cronet,國內百度、微博、網易傳媒,已經接入了官方的 Cronet,他們在官方 Cronet 基礎上,各端做了自己的策略包括一些監控服務。頭條系、蘑菇街改造比較徹底,在 Cronet 基礎上進行二次定製開發,形成了自己的網路庫;自從 Chromium 開源以後,國內所有瀏覽器都是基於這個專案做的,並在不斷的演進中,非常活躍;最後一點是開源協議,Chromium 主要使用的是 BSD 協議,也就是說我們基於 Cronet 做的修改,不需要強制開源,這對商業公司來說,很關鍵,也是我們選擇 Cronet 的重要考量。
下圖是整個跨平臺網路庫的整體架構。
最底下分別是 OS、Base 和 Net;我們在 Net 的基礎上封裝了一層 Common API,作為膠水層隔離 Net,並擴充套件一些自己的基礎能力,包括 BI、FunctionBridge、插拔服務等;在通用能力基礎上又做了一層元件,包括網路策略、APM 監控、HTTPDNS 服務等等;最後介面層,我們對外暴露了元件 API,方便 App 呼叫。
我們可以通過 Cronet API 進行網路收發,並在這個基礎上新增了部分擴充套件 API(比如原先的 Cronet API 不支援超時[包括建連和包間超時]設定;對於成功和失敗的請求,也不支援返回具體的 IP;另外,對於 HTTPDNS 支援也不是很方便)。
對於 App 來說,除了看到 Cronet,還能看到一個元件系統。App 通過統一配置,把內部的元件按需啟用(比如這裡預設會啟用 HTTPDNS 和 APM,但是對於網路策略就不一定需要);理論上元件之間是完全隔離的,但是有一些特殊場景,比如網路策略需要跟 HTTPDNS 元件做一些通訊,我們內部封裝了一些可插拔的服務,HTTPDNS 元件將能力暴露到插拔服務中,網路策略藉助插拔服務對 HTTPDNS 元件進行呼叫;App 通過呼叫橋接(類似於 JSBridge)直接與元件進行通訊,這樣後續元件接入和擴充套件起來成本就很低了。
元件不直接和網路核心通訊,我們在網路核心基礎上封裝了基礎能力,包括一些必要的請求攔截,請求轉發及網路監控能力。
我們希望通過這次網路庫跨平臺化實踐,沉澱出一個可複用的網路框架:這部分網路框架可以在網易內部複用,不包含業務邏輯,僅僅是一個框架,裡面會定期更新 Cronet 核心,包括一些安全補丁(我們也接過 Cronet 官方版本,執行一段時間後發現,線上有一些 Crash,這個需要我們通過打補丁的方式修復);我們在 Cronet 的基礎上提供一些基礎的元件管理和基礎能力封裝,旨在降低 C++ 級別定製成本。
另一方面,基於可複用網路框架,我們擴充套件了一部分能力集:所有業務都放在能力集裡面,變成三方庫,以元件的形式進行復用;接入方可以根據自身需求,靈活組合;能力集可以提供通用的能力(比如 APM 監控、HTTPDNS 服務),也可以提供個性化的能力(比如免流服務),旨在滿足接入方多變的業務場景和定製述求。
最後,簡單總結一下網路庫設計,我們直接基於開源的 Cronet 方案,構建了雲音樂自己的統一網路庫方案,並採用了“可複用網路框架+可擴充套件能力集”的模式進行業務下沉。
Cronet 升級
我們在選這個方案時候經過了大量的網上調研,我們發現大部分公司在方案選型時,選擇了直接使用 Cronet,而不是基於 Cronet 進行二次開發定製。他們都提到了一個共同點,如果對 Cronet 進行了定製,後續核心升級把控起來難度有點大或者成本會很高,這是他們不選這個方案的最主要理由。
那麼面對升級我們該怎麼辦呢?首先講一下為什麼要升級,升級原因很簡單,一個是要修復問題(比如出現一個安全漏洞,希望通過升級或者修補丁的方式修復這個問題);另外一個是獲得特性(比如現在的 QUIC 在快速演進中,國內主流用的都是 gQUIC,但是 iQUIC 標準正在慢慢的統一,Google 也在逐漸向 iQUIC 靠攏,我們希望通過升級直接支援 iQUIC);最後,Google 對 Cronet 也在持續進行優化,我們希望通過升級,直接享受到對應的優化成果。
那升級具體有哪些痛點呢?簡單點講,可以把升級類比成一次程式碼提交:如果你提交足夠及時,修改一部分就馬上提交,你幾乎不會遇到衝突,很順利;有些同學習慣不好,寫了兩三天的程式碼,才一次性提交,這時候就比較容易遇到程式碼衝突,可能需要花點時間才能解決;如果繼續放慢程式碼提交的節奏,像我們對 Cronet 的二次開發,可能半年或一年甚至更久,也不會與官方的最新 Cronet 進行合併,等到需要進行 Cronet 升級時,你會發現新版 Cronet 框架可能都變了,這個時候合併程式碼就非常痛苦了。
以前,我們做瀏覽器,每次進行核心升級,就有幾千上萬個檔案衝突,光合並程式碼這部分要花半個月的時間。另外程式碼合併的時候,你其實不知道怎麼合併它,變化太多了,合併很麻煩。最後一個,即便僥倖編譯連結通過了,你會發現很多功能衰退。
基於此,我們想了一些對應的解決思路,比如對於程式碼衝突來說,我們基於原始碼二次定製時儘量減少修改,儘量做好隔離讓修改的地方能夠很輕易的辨識出來。這樣合併者可以很清楚的知道這個地方要合,這個地方不要合。對功能衰退最有效的方法就是做單測,Chromium 專案本身單測覆蓋率很廣,你只要在新增的程式碼上做一些單測覆蓋就夠了。
下面重點講下,如何減少侵入並做好隔離,這兩點其實是很簡單的技巧,沒有什麼難度,大家可以很輕易的參考並實踐。
減少侵入我們是這樣考慮的,主要是三個詞:一個是提介面,第二個是基於介面搭框架,最後基於框架擴元件。
對 Cronet 的侵入,我們更多以介面的方式進行,而不是直接魔改。提介面包括新增一些代理、觀察者或者攔截器(運用了部分設計模式的思路),把所有的修改都匯聚到幾個小的點上,整個實踐下來,提介面這部分,我們改造了快一年了,介面部分只佔修改裡面的 5% 甚至更少;基於新增的介面,我們搭了一個網路框架,網路框架主要做一些能力封裝,包括元件機制和通道封裝,再包括一些插拔服務等等;最後,我們會把各種策略、監控、業務都往元件上堆,整個下來,對原始碼的侵入程度控制得還是可以的。
下圖程式碼是我們對 Cronet 原始碼進行修改的一處示例。左邊是原始檔關於 Socket 複用的程式碼,右邊做了一些改造,我們做了一個回撥,由業務側決定是否可以進行 Socket 複用。乍一眼看,無法察覺兩者的差異,這給核心升級帶來一個很嚴重的挑戰。我們程式碼完全沒加任何隔離措施,一合併完全不知道自己做了哪些修改,完全沒法合併。
怎麼辦呢?有同學可能會想到加註釋這一類的。以前,剛開始做瀏覽器的時候,我們團隊也是用加註釋這種方式,這種方式簡單“高效”,但總感覺有一點不舒服,雖然進行了隔離,但是有時候定位一個問題,你覺得這個 bug 不應該是 Cronet 的,應該是我們修改出來的,這時候,你可能需要編譯原始程式碼進行驗證,但是加註釋的方式導致你無法快速編譯出一個源版本。
對於 C++ 來說其實很簡單,就是加一個巨集開關,通過巨集開關你可以很清楚的看到哪些地方改動了,一目瞭然,並且可以快速進行源切換,這是以前我所在的團隊長期實踐下來的經驗。
大家看到所有修改都把原始碼放在前面,把我們的修改放在後面,為什麼要這樣做?有一個很大的好處是,上面的程式碼因為都不是你修改的,合併的過程中不太會有衝突,後續如果有問題,你可以通過合併工具快速的識別差異。
對於 C++ 以外的程式碼,我們也做了與巨集開關類似的開關進行程式碼隔離,如下圖所示。
最後我們還做了檔案級別的隔離。紅框部分是新增的檔案目錄,我們把修改全部放在 wow 下面,把所有修改都隔離在自己的檔案裡面。舉一個例子,net 目錄下有一個檔案,我們對它進行了修改,我們會把新建的檔案放在 wow 下的 net 目錄中,並對新增的檔案加上 wow 字首。
Cronet 升級無法避免,也沒有銀彈,我們在這裡採用低成本、可推行的“技巧”,讓整個升級變得相對簡單一點。
避坑指南
解決了升級的顧慮,你是不是已經躍躍欲試了?別急,你還需要最後一步,先讓 Cronet 程式碼跑起來。
下圖是 Cronet 官方的文件,對原始碼的 check out、build and run 給出了詳盡的描述,你只需要對著文件一步步操作就行了。由於 Cronet 未單獨開源,Cronet 與 Chromium 的程式碼是混在一起的,共用同一個程式碼倉庫和構建系統,所以程式碼拉取和編譯環境準備與 Chromium 一致,對於 Cronet 的編譯,Google 給出了單獨的文件描述,並且為 Android 和 iOS 提供了單獨的編譯指令碼,簡單點說就是一條命令列的事情。
年初我們對 Cronet 進行了調研,那時 Chromium 的核心版本是 M88,整個拉程式碼編譯除錯都很順利,當然我們只測試了 Windows 端。我們接入 Cronet 的初衷只是為跨平臺化,但隨著調研的深入,我們看到了各大網際網路公司關於 QUIC 方面的實踐,他們給出了不同版本的效能提升資料,由於 Cronet 天然支援 QUIC,我們就想,等完成網路庫跨平臺化改造後,我們也開啟 QUIC,看下效果,如果真有提升,那就更好了。
但當我們對 QUIC 進行一番調研後,我們發現國內雲廠商主要支援的 QUIC 版本是 gQUIC 43,並且 Chromium M88 版本已經不預設支援 gQUIC43,也就是說我們不得不回退到某個預設支援 gQUIC 43 的低版本 Chromium,為了更好的做資料對比並降低 QUIC 接入的風險,我們選取了與傳媒一致的版本 M72。
由於 M72 與 M88 隔了近2年的時間,當我們將 Chromium 程式碼切回 M72 時,我們發現無論是 Windows、Ubuntu、Mac 下,都編譯不過,有各種奇怪的問題。搗騰了很久,後來才猛然意識的,可能是文件的問題,畢竟2年過去了,怎麼找到2年前 M72 對應的官方文件呢?
Chromium 的官方文件和程式碼是統一管理的,你只需在 src 目錄下找到對應的文件即可,或者線上直接根據 tags 找到對應的文件即可,有了匹配的文件,各種編譯連結問題就少多了。
下圖是根據官方文件和我們的使用習慣整理的 Cronet 在各端下的開發工具、編譯環境和構建系統。唯一需要強調的是,在編譯 iOS 版本時,需要將 XCode 中的 Command line tools 指向低版本、Win10 下記得安裝對應的 SDK。另外,整個 SDK 的構建,使用了 GN+Ninja 的方式,可能剛接觸的同學會有一點不適應,其實熟悉幾天就可以上手了。
另外一個是除錯。做開發的都知道,學一個東西很簡單,就是把程式碼拉下來編譯,然後 Debug。出乎我們意料的是,在 Android 下原生不支援除錯 Cronet SDK,官方推薦一個是 VLOG,第二個是 NetLog。因為我們三端都有對應的開發同學,比如我習慣 Windows,直接基於 Cronet 建一個 Demo 進行除錯,大部分場景都能覆蓋。如果覆蓋不了就用 VLOG 除錯一下,這個問題是一個痛點,暫時還沒有找到很好的解決方法。
最後是 App 接入。使用 Cronet 發起請求,Google 提供兩種實現,一種是非同步的 UrlRequest,另一種是符合移動端協議標準的,在 UrlRequest 基礎上封裝了流操作的 HttpURLConnection 和 NSURLProtocol 實現。
由於我們對網路請求進行了一些介面擴充套件,左側的方案只要修改 URLRequest,右側的方案既需要修改 UrlRequest,又需要修改上層的協議封裝,出於減少對 Cronet 侵入的考慮,我們更傾向於所有的端都使用同一套網路介面,即 UrlRequest,事實上在主站 API 請求接入的過程中,我們的確是這樣操作的。但是當接入 CDN 請求時,面對流操作,UrlRequest 使用起來並不太方便,業務側需要更多的適配和修改才能接入,最後出於讓 App 更低成本接入方面的考慮,我們還是整體切換到了使用 HttpURLConnection 和 NSURLProtocol 的方式。
這裡面有一些試錯成本,分享給大家,大家可以少走點彎路。避坑指南主要是基於 Cronet 進行二次開發相關,如何讓程式碼跑起來只是一個小坑,跳過了以後就是康莊大道。
落地實踐
我們一開始對 Cronet 不太瞭解,先接入了官方的 Cronet SDK,進行了試驗;再基於 Cronet 做一些網路框架的搭建,然後將業務元件逐個下沉到網路庫(用 C++ 重寫);下沉完以後在 Android 端進行了落地(放量中);然後逐步把 QUIC 開啟,程式碼裡面只要一行程式碼開啟就行了;最後,iOS 端也在接入過程中。
線上資料方面,Cronet 在未開啟 QUIC 的情況下,響應時間較 OK 提升 16%~20%,開啟 QUIC 後,響應時間進一步提升到 37%~41%。這個 40% 的效能提升非常可觀,你可能優化一年也趕不上這個效果。我們為了把上層業務跨平臺化,最終選擇將底層網路庫也一起跨平臺化,因為方案選型的原因,我們直接享受了 Cronet 帶來的網路效能的優化成果。
後續規劃
整個跨平臺網路庫,已經在網易雲音樂的主 App 側部分落地了,接下來會把 Windows 和 Mac 覆蓋掉;網路庫,在雲音樂主 App 完全落地後,我們會在雲音樂產品矩陣中逐個落地;未來,我們會在網易內部進行推廣。
Cronet 接入了,後面的調優之路才剛剛開始,包括預連線、引數調優、“競速”優化、連線複用率、連線遷移、QUIC 叢集獨立部署等。
今年 QUIC 的標準化版本已經出來了。未來,等時機成熟,我們會通過升級 Cronet 核心版本直接支援 iQUIC。
我們實踐下來網路庫是最值得和最應該進行跨平臺化改造的;推薦 Cronet 作為跨平臺網路庫的首選,稍微有一點點門檻,但是這個門檻不高;我們線上資料 Cronet 在 HTTP/2 的基礎上已經有不錯的效能優勢,大概 20% 左右,開啟 QUIC 後優勢進一步放大;對於接入 Cronet,一開始我們也是觀望的,你可以跟百度、微博、網易傳媒一樣,先嚐試一下用官方的 Cronet 進行接入,再決定是否基於Cronet進行二次開發。