Facebook重寫iOS版Messenger:啓動速度快2倍,核心代碼減少84%

與之前的 iOS 版本相比,新版 Messenger 的啓動速度提升到了兩倍,體積縮減到了四分之一。我們將 Messenger 的核心代碼減少了 84%,從 170 萬行減少到 360,000 行。

爲了實現這樣的結果,我們儘可能使用原生 OS、使用 SQLite 支持的動態模板重用 UI、使用 SQLite 作爲通用系統,還構建了一個服務器 broker 充當 Messenger 和其服務器功能之間的通用網關。

Messenger 於 2011 年首次作爲獨立應用發佈。當時,我們的目標是儘可能爲用戶構建功能豐富的體驗。從那時起到現在,我們增加了支付、相機效果、故事、GIF 甚至是視頻聊天功能。但是,由於每月有超過 10 億人使用 Messenger,表面上看起來很簡單的全功能消息應用,幕後卻變得頗爲複雜。幫助我們構建、測試和管理所有這些功能所需的後端使這款應用變得更加複雜了。這款應用的二進制文件在鼎盛時期的體積超過了 130MB。這麼大的體積和龐大的代碼量拖慢了應用的啓動速度,尤其是在較舊的設備上更是雪上加霜;它還有多達 9 個標籤,讓用戶在導航時容易不知所措。

在 2018 年,我們在發佈 Messenger 第 4 版時重新設計並簡化了它的界面,但我們還想做更多改進。我們開始思考,如果我們今天從頭開始打造一款消息應用會做哪些事情。自我們十年前開始開發 Messenger 以來,情況發生了怎樣的變化?事實證明變化是很多的。實際上,移動應用的編寫方式已從根本上改變了。因此在去年的 F8 大會上,我們宣佈了讓 Messenger 的 iOS 應用變得更快、更小、更簡單的願景。我們稱其爲 LightSpeed 項目。

爲了構建 Messenger 的新版本,我們需要從頭開始重新構建架構並重寫整個代碼庫。這次重寫使我們得以利用自 2011 年初版應用推出以來,整個移動應用領域中出現的那些重大進步。此外,我們還可以利用公司在過去幾年中開發的最新技術。從今天開始,我們很高興在接下來的幾周內在全球範圍內向 iOS 推送新版 Messenger。與之前的 iOS 版本相比,新版 Messenger 的啓動速度提升到了兩倍 *,體積僅爲前者的四分之一。通過這一全新迭代,我們在 Messenger 上重新構想了構建應用的方式,並從頭開始應用了全新的客戶端核心和服務器框架。這項工作幫助我們改進了自己的最前沿技術,並且新的代碼庫可以在未來十年內實現可持續性和可擴展性,爲跨應用的私有消息傳遞和互操作性奠定了基礎。

  • 根據使用生產數據的內部測試得出:

https://www.facebook.com/Engineering/videos/500081744266613/

更小更快

我們首先假設 Messenger 必須是一個簡單、輕量級的實用程序。有些應用是身臨其境的(視頻流、遊戲);人們會在它們身上花費數小時時間。這些應用佔用大量存儲空間和電池時間等,因此需要作出權衡。但是消息只是一小段文本,發送時間不到一秒鐘。從根本上講,消息傳遞應用應該是手機上最小、重量最輕的應用之一。秉承這一原則,我們開始尋找使 iOS 應用顯著縮小的正確方法。

無論設備類型或網絡條件如何,小型應用的下載、安裝、更新和啓動速度都會更快。小型應用也更易於管理、更新、測試和優化。當我們開始考慮這個新版本時,Messenger 的核心代碼庫已增長到 170 萬行以上。僅僅改動幾部分代碼是不夠的。

獲得更小應用的最簡單方法是剝奪我們多年來添加的許多功能,但是對我們來說,保留所有最常用的功能(例如羣組視頻通話)是非常重要的。因此我們退後一步,研究瞭如何應用過去十年中所學到的知識,以及我們對當今應用中用戶需求的理解來行事。在研究了各個選項之後,我們決定越過表層的界面,深入研究應用本身的基礎架構。

完全重寫代碼庫是一項極爲罕見的工作。在大多數情況下,重寫應用所需的大量工作產生的實際效率改進收益是很小的(如果有的話)。但是這一次,早期的原型探索表明我們可以實現巨大的收益,這促使我們去嘗試做一些類似規模的應用很少做過的事情。這不是一件小事。LightSpeed 項目啓動時只有少數幾名工程師參與,但到最後需要 100 多名工程師才能完成項目,交付最終產品。

最後,我們將 Messenger 核心代碼減少了 84%,從 170 萬行減少到 360,000 行。我們重建了功能以適應簡化的架構和設計,從而實現了這一目標。我們保留了大多數功能,並且隨着時間的推移將繼續引入更多功能。更少的代碼量讓應用變得更輕巧、更快,並且簡化的代碼庫意味着工程師可以更快地創新。

更簡單

我們的主要目標之一是最大程度地降低代碼複雜度並消除冗餘。我們知道統一的架構將允許全局優化(而不是讓每項功能都專注於局部優化),並允許以靈活的方式重用代碼。爲了構建這種統一的架構,我們建立了四項原則:利用 OS、重用 UI、利用 SQLite 數據庫,以及推送到服務器。

利用操作系統

移動操作系統正在飛快進化,日新月異。在用戶需求和競爭壓力的推動下,新功能和創新層出不窮。開發人員在構建新功能時,往往會選擇在操作系統之上構建抽象,以填補功能差距、增加工程靈活性或創建跨平臺用戶體驗。但是現有的操作系統通常可以滿足許多需求。諸如渲染、代碼轉換、線程和日誌記錄之類的操作都可以由操作系統來處理。即使可能有在局部指標上速度更快的自制解決方案,我們也會使用操作系統針對全局指標進行優化。

儘管 UI 框架功能強大,且可以提升開發人員的生產力,但它們需要不斷的升級和維護,以適應不斷變化的移動 OS 格局。我們沒有重新設計輪子,而是使用了設備原生 OS 上可用的 UI 框架來支持更廣泛的應用功能需求。在避免了緩存 / 加載大型定製框架的需求後,我們不僅減小了應用體積,還降低了複雜性。原生框架不必轉譯爲子框架。我們還使用了許多 OS 庫,包括 JSON 處理庫,而不是在代碼庫中構建和存儲我們自己的庫。

總體而言,我們的方法是很簡單的。如果操作系統做得很好,我們就使用它。我們充分利用了操作系統的全部功能,而無需等待哪個框架公開這些功能。如果操作系統沒有做到什麼事情,我們將找到或編寫最小的庫代碼來滿足特定需求,僅此而已。我們還採用了依賴平臺的 UI 和相關工具。對於任何跨平臺邏輯,我們都使用原生 C 代碼內置的操作擴展,其具有高度可移植性,效率出衆,速度飛快。我們將這種擴展用於所有全局次優的類操作系統功能,或操作系統未涵蓋的那些功能。例如,所有特定於 Facebook 的聯網功能都在擴展程序中用 C 編寫。

重用 UI

在 Messenger 中,我們一些相同的 UI 體驗有着多個版本。比如說在項目開始時,我們有 40 多個不同的聯繫人列表頁面。每個頁面的設計都有細微的差異,具體取決於電話渲染等因素——每個頁面都必須增強以支持橫向模式、黑暗模式和可訪問性等特性,這使我們需要支持的數量翻了一番。這意味着我們需要很多視圖,這些視圖在 Messenger 之類的應用中佔了很大的比例。爲了簡化和消除冗餘,我們限制了設計架構,強制對不同的視圖重用同一結構。這樣一來我們就只需要幾類基本視圖即可,並且這些視圖可以由不同的 SQLite 表驅動。

在今天的 Messenger 中,聯繫人列表是單個動態模板。我們可以更改屏幕外觀,而無需其他任何代碼。每次有人加載頁面時(要向羣組發送消息,閱讀新消息等),應用都必須與數據庫對話以加載適當的名稱、照片等。現在應用不需要存儲 40 種頁面設計了,取而代之的是數據庫包含了根據要加載的各種子功能來顯示不同構件的指令。單個聯繫人列表頁面可以擴展以支持大量功能,例如聯繫人管理、組創建、用戶搜索、消息安全性、故事安全性、共享、故事共享等等。在 iOS 世界中,這是一個單視圖控制器,具有適當的靈活性來支持所有這些需求。在我們所有的設計中使用這個更優雅的解決方案後,我們就能刪除掉大量代碼。

使用 SQLite

大多數移動應用將 SQLite 用作存儲數據庫。但是隨着功能的有機增長,每種功能最終都有自己獨特的存儲、訪問數據和實現相關業務邏輯的方式。爲了構建一個通用系統,我們從桌面世界中汲取了一個理念。我們不是去管理幾十個獨立的功能,並讓每個功能提取信息並在應用上構建自己的緩存,而是利用 SQLite 數據庫作爲一個通用系統來支持所有功能。

從歷史上看,協調各種功能之間的數據共享需要自行開發複雜的內存中數據緩存和事務子系統。在數據庫和 UI 之間傳遞這種邏輯會拖慢應用的速度。我們決定放棄這種途徑,而只使用 SQLite,並讓它處理併發、緩存和事務。現在,我們不會再讓一個系統來更新"哪些朋友現在處於活動狀態"的信息,讓另一個系統來更新聯繫人列表中個人資料圖片的更改,再讓另一個系統來檢索你收到的消息了,如今來自數據庫的數據請求都是自包含的。所有的緩存、過濾、事務和查詢都在 SQLite 中完成。UI 只會反映數據庫中的表。

這樣就可以讓邏輯保持簡單和高效,並限制了其對應用其他部分的影響。但是我們走得更遠。我們爲所有功能開發了一個集成的架構。我們爲 SQLite 擴展了存儲過程的功能,使 Messenger 功能開發人員可以編寫可移植的、面向數據庫的業務邏輯,最後,我們構建了一個平臺(MSYS)來編排對數據庫的所有訪問,包括隊列更改、延期或可重複執行的任務,並支持數據同步。

MSYS 是一個用 C 編寫的跨平臺庫,可操作我們需要的所有原語。將所有代碼整合到一個庫中讓管理一切事務變得更容易了。它更集中,更專注。我們嘗試以單一的方式來做各種事情——向服務器發送消息的方式只有一種,發送媒體的方式只有一種,記錄的方式只有一種,等等。使用 MSYS 後,我們就有了全局視圖。現在我們可以確定負載的優先級。假設"加載消息列表"的任務比更新"幾天前是否有人在線程中讀取消息"的任務具有更高的優先級;我們可以將高優先級任務上移到隊列中。一個通用系統可以簡化我們對應用的支持工作。有了 MSYS,我們可以更輕鬆地一站式追蹤所有這些功能的性能表現、發現性能退化並修復錯誤。此外,我們在自動化測試上投入資源,使系統的這一重要部分變得異常穩健,結果讓 MSYS 邏輯的代碼行覆蓋率達到了(在行業中很少見)的 100%。

使用服務器

對於不屬於上述任何類別的內容來說,我們會將它們推到服務器上。我們必須建立新的服務器基礎架構,以支持客戶端上 MSYS 的單個集成數據和同步層的存在。原始 Messenger 的客戶端 - 服務器交互的工作方式與傳統應用是一樣的:對於每個功能,客戶端都有明確的協議和連接格式,以便客戶端同步數據並向服務器更新任何更改。然後,該應用必須實現該協議,並協調正確的數據庫更新以驅動 UI。這意味着對於應用中的每個功能,都有很多(到頭來是不必要的)自定義的平臺特定業務邏輯。

客戶端和服務器之間的協調邏輯非常複雜,並且容易出錯,而且隨着功能數量的增加會更容易出錯。例如,接收文本消息這個操作涉及到消息列表的更新、相關線程片段的更新、最後修改時間 / 線程的更新、刪除可能已插入的任何樂觀版本的消息(例如從通知中刪除)、刪除正在處理消息樂觀版本的任務、解密,以及其他衆多任務。這些類型的客戶端 - 服務器交互涵蓋了應用中的所有功能。結果,應用需要重複處理相似的問題,並且就所有這些事件和交互的組合方式而言,整個應用運行時的行爲是不確定的。隨着時間的流逝,我們的應用已經變成了繁忙的高速公路,兩個方向都堵上了長長的車隊。

在今天的 Messenger 中,我們有一個通用的靈活同步系統,該系統允許服務器定義和實現業務及同步邏輯,並確保客戶端和服務器之間的所有交互都是統一的。與客戶端上的 MSYS 相似,我們構建了一個服務器代理來支持所有這些情況,而實際的服務器後端基礎架構負責支持這些功能。服務器 broker 充當 Messenger 和所有服務器功能之間的通用網關,而在過去,所有客戶端功能都使用各種各樣的方法直接與服務器功能通信。

https://www.facebook.com/Engineering/videos/3509976602377949/

防止未來的代碼膨脹

如今的 Messenger 是非常輕巧的——代碼庫已從 170 萬行減少到 360,000 行。應用的二進制大小現在是原來的四分之一。但是在將新的代碼庫投入生產之前,我們必須確保它不會隨着新添加的修補程序、更新和功能而再次膨脹起來。爲此,我們爲每個功能設置了預算,並責成我們的工程師遵循上述架構原則來堅持遵守這些預算約束。我們還構建了一個系統,使我們能夠了解每個功能帶來了多少二進制大小權重。我們要求工程師負責遵守預算約束,作爲功能接受標準的一部分。按時完成功能很重要,但達到質量目標(包括但不限於二進制大小的預算約束)更爲重要。

構建今天的 Messenger 經歷了一段漫長的旅程,並且公司中的許多工程師都參與了它的開發工作。但對於使用這款應用的用戶來說,它的外觀或感覺不會有太大不同。它的啓動速度會更快,但仍將提供人們期望的,與舊版本相同的出色消息體驗。但這僅僅是一個開始。

我們在重建 Messenger 方面所做的工作,將使我們在邁向未來的過程中能夠繼續創新和擴展消息體驗。除了構建可在未來十年或更長時間內可持續發展的應用之外,這項工作還爲我們整個應用系列中的跨應用消息傳遞奠定了基礎。它還爲我們以隱私爲中心的消息傳遞體驗打下了根基。

我們要感謝爲 LightSpeed 項目做出貢獻的所有人們。

原文鏈接

Project LightSpeed: Rewriting the Messenger codebase for a faster, smaller, and simpler messaging app

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章