我們高興地宣布,新版 Messenger 已經開始在 iOS 上推送了。為了讓 iOS 版 Messenger 應用變得更快、更小、更簡單,我們重新建構了它的架構并重寫了整個代碼庫,這是一項非常了不起的工作,并且來自全公司的工程師都參與了進來。
與之前的 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 世界中,這是一個單視圖控制器,具有适當的靈活性來支援所有這些需求。在我們所有的設計中使用這個更優雅的解決方案後,我們就能删除掉大量代碼。
https://mmbiz.qpic.cn/mmbiz_gif/XIibZ0YbvibkWJT1prP3sVBbxy1AMPSsqSwvprIumstOuU5dvXO2c7JA7tkKtd82sy0aD12JthftIMq6TMpE9G2w/640?wx_fmt=gif&wxfrom=5&wx_lazy=1
使用 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 項目做出貢獻的所有人們。