Whatsapp 系統架構

地球上有兩個社交的 App,一個是中國使用的微信,一個是其他國家使用的 WhatsApp。根據 Facebook CEO 馬克 · 扎克伯格的說法,(2020 年)每天有超過 1000 億條信息通過 WhatsApp 發送,日活用戶 20 億;相比之下,微信日活是 10.9 億,每天發送 450 億條。Whatsapp 相當於 2 個微信。有了如此近乎天文數字的流量,人們不禁想知道 WhatsApp 是如何工作?它的系統設計、服務器架構、技術。它是如何處理這麼多併發用戶和消息的?什麼樣的框架和編程語言能夠實現這種規模?他們如何保護所有數據的安全?等等。在本文中,我們將深入探討 WhatsApp 的架構和系統設計。我們將回答上述所有問題以及更多問題。

一、產品需求概述

1.1 基本信息

原作者:Brian Acton, Jan Koum

開發商:Meta Platforms(改名元宇宙平臺的臉書), Will Cathcart (WhatsApp 總裁)

第一個版本發佈時間:12 年前的 2009 年 1 月

當前穩定版本:

開發語言:Erlang

支持的操作系統:Android, iOS, KaiOS (即 Mac OS), Windows 等客戶端必須連接到移動端纔可以。

文件大小:iOS 178 兆;Android 33.85 兆

語言支持:iOS 支持 40 種語言;Android 支持 60 種語言

版權:私有軟件,支持 EULA

網站地址:WhatsApp.com

1.2 產品數據

月活增長情況

2014 年 4 月 22 日,WhatsApp 擁有 5 億月活躍用戶,7 億照片和 1 億視頻每天被分享,消息系統每天處理超過 100 億條消息。

2014 年 8 月 24 日,Koum 在他的推特賬戶上宣佈,WhatsApp 在全球擁有超過 6 億活躍用戶,每月增加約 2500 萬新用戶,即每天 833,000 名活躍用戶。

2017 年 5 月,據報道,WhatsApp 用戶每天在該應用上花費超過 3.4 億分鐘進行視頻通話。這相當於每天大約 646 年的視頻通話。

2017 年 2 月,WhatsApp 在全球擁有超過 12 億用戶,到 2017 年底達到 15 億月活躍用戶。

2020 年 1 月,WhatsApp 在谷歌應用商店註冊了 50 億安裝,成爲 Facebook 之後的第二個實現這一里程碑的非谷歌應用。

1.3 功能需求

1.4 非功能需求

二、技術棧

2.1 前端

WhatsApp 支持幾乎所有平臺。它有一個 iOS 應用程序、安卓應用程序、桌面應用程序、網絡應用程序和視窗手機應用程序。直到 2017 年,你甚至可以在黑莓上使用 WhatsApp。

有這麼多受支持的平臺,你可能已經猜到 WhatsApp 將是一個混合應用。但是,事實上,不是。他們實際上爲每個平臺構建了一個本地應用。以下是用於構建每個平臺的所有支持平臺的前端語言列表:

除了編程語言本身,WhatsApp 在前端使用的另一個重要技術是 SQLite 數據庫。SQLite 是一個獨立的、獨立的關係數據庫,旨在嵌入到應用程序中——這意味着它存在於您的設備上。WhatsApp 用它來存儲對話。由於每次打開應用程序時從雲中下載所有消息都是浪費資源,WhatsApp 選擇將消息存儲在本地。事實上,WhatsApp 只存儲消息,直到收到消息時才刪除。

2.2 後端

據我們所知,目前的 WhatsApp 後端系統設計如下:

Erlang

WhatsApp 選擇 erlang 來支持如此大規模的併發。

Erlang 是一種面向構建併發、可擴展和可靠系統的函數式編程語言。它使用基於進程的 “參與者模型”,每個獨立的小進程通過消息相互通信。這些進程可以創建新進程、發送消息、接收消息並修改其狀態。

它基於進程的特性使 Erlang 具有極高的併發性、可伸縮性和可靠性。

這些進程還可以與運行它的核心之外的進程通信。這使得水平(通過添加更多機器)或垂直(通過添加更多內核)縮放系統變得容易。最後,由於這些過程可以相互通信,更重要的是,可以相互重啓,因此很容易構建自愈系統。如果一個錯誤使一個進程崩潰,另一個進程可以重新啓動它。

FreeBSD

WhatsApp 創始人的一個有趣的技術選擇是選擇 FreeBSD 作爲操作系統,而不是更廣泛使用的系統(如 Linux)。

WhatsApp 的聯合創始人之一布萊恩 · 阿克頓在接受《連線》採訪時談到了這一決定:

“Linux 是複雜的野獸。FreeBSD 的優勢在於它是一個具有非常好的端口集合的單一發行版。"

此外,當涉及到原始性能時,尤其是在每個數據包的系統負載方面,沒有其他操作系統能打敗 FreeBSD。

然而,歸根結底,他們決定使用 FreeBSD 的真正原因可能是因爲兩位聯合創始人在雅虎有很長的使用經驗!

Ejabberd

Ejabberd 是一個用 Erlang 編寫的開源 XMPP 服務器。WhatsApp 使用 XMPP 的修改版本作爲處理消息傳遞的協議。即使是 WhatsApp 使用的 Ejabberd 服務器也是高度定製的,以優化服務器性能。

Ejabberd 處理應用程序的消息路由、可交付性和即時消息通用側面。Ejabberd 的特點包括:

Mnesia

爲了存儲數據和臨時消息,WhatsApp 使用了一個名爲 Mnesia 的基於 Erlang 的分佈式數據庫管理系統。這個數據庫管理系統提供了許多傳統數據庫所沒有的優點,例如:

Mnesia 也是唯一一個用 Erlang 編寫的數據庫管理系統。這本身就是一個好處,因爲應用程序中的 Erlang 和 DBMS 中的 Erlang 之間沒有數據結構差異。因此,編碼更快、更明確。

BEAM

BEAM,即 Bogdan’s Erlang Abstract Machine,是編譯和執行 Erlang 源代碼的虛擬機。BEAM 是專門爲高併發應用程序設計的——非常適合 WhatsApp 的用例。BEAM 的祕訣是不共享內存的輕量級進程,由調度器管理。這些調度器可以跨多個內核管理數百萬個進程。這使得 BEAM 具有高度的可伸縮性,並且能夠抵抗故障,例如由高流量負載、系統更新和網絡中斷引起的故障。

BEAM 對 WhatsApp 系統設計至關重要,WhatsApp 團隊已經發布了許多核心源代碼的補丁和修復。

YAWS

YAWS(Yet Another Web Server)是一個基於 Erlang 的網絡服務器,非常適合動態內容。WhatsApp 使用 YAWS 存儲多媒體數據。YAWS 本身使用 HTML5 WebSockets,通過在服務器和應用程序之間建立可靠而快速的連接來簡化雙向通信。通過使用這項技術,WhatsApp 能夠在數十億臺設備上近乎實時地發送和接收多媒體數據。

2.3 通訊機制

WhatsApp 在 Ejabberd 服務器上使用高度修改的 XMPP 版本(稍後會有更多介紹)與客戶端通信。

客戶端上的 XMPP 打開一個 SSL 套接字到 WhatsApp 服務器。所有發送的消息都在服務器上排隊,直到客戶端打開或重新連接到此套接字以檢索消息。一旦客戶端成功檢索到消息,成功狀態就會發送回 WhatsApp 服務器。然後,服務器將此狀態轉發給原始發件人;通過在成功發送的消息旁邊添加 “複選標記” 圖標,讓他們知道消息已經收到。

三、系統架構

3.1 軟件架構

3.2 部署架構

2014 年 WhatsApp 用戶數爲 5 億, 需要大約 550 臺服務器和超過 11,000 個運行 Erlang 的內核。2017 年,在被 Facbook 收購四年後,WhatsApp 被從 IBMSoftLayer 的雲中移除,轉到 facebook 的私有云上。2020 年用戶數超過 20 億的用戶。

從左側開始,我們有多個不同的客戶端(移動和網絡應用),每個客戶端都託管一個用於存儲對話的本地 SQLite 數據庫。

客戶端使用 HTTP WebSocket 從 YAWS Web 服務器發送和檢索圖像和視頻等多媒體數據。XMPP 是用來向其他用戶發送這些文件和其他消息的。

發送 XMPP 消息時,將執行上面描述的一系列步驟。首先,它被髮送到 WhatsApp 在 BEAM 和 FreeBSD 上運行的自定義 Ejabberd 服務器。Ejabberd 服務器將消息保存在 Mnesia 數據庫表中,並將其放入隊列。當接收用戶打開應用程序,從而重新連接到套接字時,隊列中的消息將通過 Ejabberd 服務器路由並傳遞給接收方。一旦確認成功傳遞,消息將從 Mnesia 數據庫中刪除。

3.3 核心用例實現

1、當兩個用戶都在線時

假設用戶 U1 想要向用戶 U2 發送消息。現在我們可以從流程圖中看到,U1 連接到 WSH1(Web Socket Handler)。Web Socket Handler 是一種輕量級服務,即採用長鏈接來對接到活躍的用戶。考慮到 WhatsApp 和 FacebookMessenger 每天處理的規模,系統中會有大量的 WebSocket Handler 服務。

評估 Web Socket Handler 所需要的服務器數量。每臺機器支持有大約 65K 開放端口。預留 5K 端口用於內部使用以及與系統中的其他服務通信,剩餘 60K 端口可用於 Web Socket Handler 服務,也就是一臺機器可以服務於 6 萬用戶, 20 億用戶需要 3 萬多臺機器。此外這些機器還需要分佈到世界各地,就近部署來減少延遲。

Web Socket Handler 將連接到 Web Socket Manager,這個 Manager 記錄 Web Socket Handler 和所連接的用戶的映射信息, 後端採用 Redis 數據庫,記錄如下信息:

當用戶斷開和 Web Socket Handler 之間的連接並重新連接到另一個 Handler 時,Manager 會將這個信息更新到 Redis 中。

Web Socket Handler 在調用 Web Socket Manager 的同時還調用消息服務。消息服務是系統中所有消息的存儲庫。它提供開放接口, 支持通過各種過濾器獲取消息,如用戶標識、消息標識、傳遞狀態等。這個消息服務採用 Cassandra 作爲數據庫。可以預期的是,新用戶將每天不斷添加到系統中,新老用戶將每天不斷進行新對話,即消息服務需要建立在能夠處理持續增加的數據的數據存儲之上。由於它向 WebSocket Handler 開放的 API 數量有限,可以執行的查詢參數也是有限的。Cassandra 是最適合這些需求和查詢模式的數據庫。在選擇最佳存儲解決方案的一文中介紹 Cassandra 和 Redis 的比較。

IM 系統對消息存儲有兩種方式:

  1. 以臉書 Messenger 爲代表,永久存儲所有信息;

  2. 以微信、WhatsApp 爲代表,只存儲信息,直到它們無法傳遞。一旦我們收到消息被成功接收的確認,它們就可以從系統中刪除。

存儲方式會影響數據庫的選擇。Cassandra 中刪除操作處理效率不高,對 WhatsApp 來說並非是一個好的選擇。

現在讓我們回到 U1 和 U2 正在進行的對話。正如我們從架構流程圖中看到的,U1 連接到 Web Socket Handler,即 WSH1。因此,當 U1 決定向 U2 發送消息時,它將此消息傳達給 WSH1。然後,WSH1 將調用 Web Socket Manager,獲取正在處理 U2 的 Web Socket Handler 即 WSH2。同時 WSH1 調用消息服務,該服務將消息保存到 Cassandra 中,併爲其分配消息 id,例如 M1。現在 WSH1 有一個消息 id M1,並且知道 U2 連接到 WSH2,它將與 WSH2 對話。

用戶只要在線, 都會被連接到 Web Socket Handler。這時候有兩種情況:

  1. U2 可能正在和 U1 聊天,在這種情況下,M1 將立即被傳遞給 U2, U2 可以看到這個消息;

  2. U2 在線, 可能和別人而不是 U1 聊天,在這種情況下,消息只會被傳遞到 U2 即可。

相應地,WSH2 將通知消息服務 U2 已經看到或者收到這個消息了。還有一種可能是我們不想存儲消息的已讀 / 已收狀態,但這個狀態還得保存一段時間。考慮這種情況:U1 向 U2 發送消息,然後 U1 就離線了。現在 U2 已經收到消息 M1,但是 U1 不知道。因此,只要消息還沒有傳達給 U1,狀態都將存儲在 Cassandra 中。一旦 U1 收到 “消息的狀態”(不是消息),如果需要,它可以被刪除。

在向 U1 傳達 “U2 已經收到或看到消息” 的同時,WSH2 將再次通過 Web Socket Manager 找到連接到 U1 的 WSH1,然後與 WSH1 交互。如果 U1 和 U2 有一個包含多個消息的完整對話,則 Web Socket Manager 將會收到大量的調用。爲了避免這種情況,每個 Handler 將緩存 2 種類型的信息:

還有一個很重要的問題,就是緩存時間的設置。Web socket 需要把最近對話的信息緩存多久?這個緩存時間會非常低。正如我們前面提到的,Web Socket Handler 將分佈在全球各地,以減少延遲。但這也意味着,可能由於連接問題、網絡鏈路或其他此類原因,用戶和 Web Socket Handler 之間的連接可能會中斷,用戶將與另一個 Handler 連接,即關於其他 Web Socket Handler 的信息將變得過時了。我們不想讓有限的緩存空間充滿過時的信息,因此緩存時間設置得很短。

2、當接收者離線時

我們已經看到了兩個用戶都在線時的流程。當 U1 試圖向離線的 U3 發送消息時會發生什麼?WSH1 調用 WebSocket Manager 以找出哪個 Handler 連接到 U3,發現 U3 沒有連接到任何 Handler,因此這部分流程到此結束。與此同時,它還調用消息服務,在該服務中,消息存儲時被分配一個 id,例如 M2。現在,當 U3 上線時,假設它連接到 WSH3,WSH3 做的第一件事是檢查消息服務是否有任何未交付的 U3 消息,這些消息將通過 WSH3 發送到 U3。然後 WSH3 將以前面討論的方式向 WSH1 傳達狀態的這一變化。

3、競爭條件(Race Condition)

是的。這裏可能存在競爭條件。WebSocket Handler 對 WebSocket Manager 和消息服務的調用是並行的。此外,請注意,在與服務通信時,系統已進行更改,預計會有輕微延遲。當 WSH1 調用 Web Socket Manager 來覈實 U3 狀態時,恰巧 U3 上線了,這裏會發生一些事情 -

現在 WSH1 認爲 U3 不在線,將消息存儲在消息服務中,並認爲它的工作已經完成。WSH3 認爲它已經爲 U3 獲取了所有未傳遞的消息。但實際上,M2 還沒有交付。有一些方法可以處理這種競爭條件,其中一個更流行的方法是輪詢。WebSocket Handler 將每隔幾分鐘輪詢一次消息服務,以確保他們沒有錯過任何消息。

4、本地緩存

假設當 U1 向 U3 發送消息時,U1 處於脫機狀態,即沒有連接到任何 Web Socket Handler。在這種情況下,消息將被存儲在設備上的本地數據庫中。一旦設備連接到網絡和 WebSocket Handler,它將從本地數據庫中提取所有消息併發送它們。

5、羣聊

到目前爲止,我們討論的是 WhatsApp 上 1:1 的聊天,即單聊是如何實現的。羣聊實現有所不同。假設 U1 想向 G1 發送一條消息。現在,組的行爲會與用戶有點不同。Web Socket Handler 無法跟蹤到組,它只跟蹤活動用戶。因此,當 U1 想要向 G1 發送消息時,WSH1 調用消息服務,U1 想要向 G1 發送消息,消息 M3 存儲在 Cassandra 中。消息服務現在將與 Kafka 通信。M3 被保存在 Kafka 中,指令它必須被髮送到 G1。現在 Kafka 將與 Group Message Handler 進行交互。 Group Message Handler. 監聽 Kafka 併發出所有組消息。有一種叫做 Group Service 的東西可以跟蹤所有羣組中的所有成員。 Group Message HandlerGroup Service 對話,找出 G1 中的所有用戶,並遵循與 Web Socket Handler 相同的過程,將消息逐個傳遞給所有用戶。

6、傳遞資料

文本消息並不是 WhatsApp 上分享的唯一內容。當人們通過 WhatsApp 發送圖像、文件和視頻時,將在設備端被壓縮和加密,加密的內容將被髮送到接收者。即使在接收的同時,內容也會在設備端以加密的格式被接收並解密。在本文中,讓我們只考慮壓縮步驟。

假設 U3 正在向 U2 發送圖像。這將分兩步進行。

如前所述,Web Socket Handler 是一個輕量級服務器,即 Web Socket Handler 上沒有繁重的邏輯。圖像將在設備端壓縮,並通過負載均衡器發送到 Asset Service(資產服務),資產服務將內容存儲在 S3 上。S3 接收的圖像或者其他的內容按需要設置是否加載到 CDN 上。一旦圖像被加載到 S3,資產服務將發送圖像 id(例如 I1)到 U3,U3 將通過所討論的 Web Socket Handler 將其傳遞給 U2。

這裏我們可以進行一些優化。例如,在一個引人注目的政治或體育事件中,很多人最終可能會分享同一個圖像,我們可能會得到同一個圖像的多個副本。爲了避免這種情況,在將圖像上傳到資產服務之前,U3 將發送圖像的哈希值,如果哈希值已經存在於資產服務中,則不會再次上載內容,但會將針對相同圖像 / 內容的 id 發送到 U2。我知道,你一定在想多重碰撞?爲了解決這個問題,我們將發送通過多種算法計算的多個哈希,而不是隻發送 1 個哈希。如果所有哈希值都匹配,則很有可能是相同的圖像,不必再次上傳。

現在我們已經討論了您在流程圖中可以看到的幾乎所有內容。但是這個 User Service 用戶服務是什麼呢?而 Group Service 羣服務到底是做什麼的呢?

用戶服務存儲用戶相關信息,如姓名、id、個人資料圖片、偏好等,通常存儲在 MySQL 數據庫中。此信息也緩存在 Redis 中。

組服務維護所有組相關信息,如哪個用戶屬於哪個組、用戶標識、組標識、創建組的時間、每個用戶入羣的時間、狀態、組圖標等。這些數據也將存儲在 MySQL 數據庫中,該數據庫將在不同的地理位置有多個從服務器,以減少延遲。當然,這些數據將被緩存在 Redis 中。通常,用戶服務和組服務將首先連接到 Redis 來查找數據,只有在 Redis 中找不到數據的情況下才會查詢 MySQL DB。

7、數據分析

用戶執行的每個動作都可以用於某種分析,比如如果一個人談論了很多關於運動的事情,他們可能就是運動愛好者。這些事件要麼被 Analytics Service 分析服務發送到 Kafka,要麼被當作消息直接發送到 Kafka。如果是發送圖像的事件,分析服務將處理圖像並附加一些標籤,如 “體育”、“足球”、“梅西” 等,並將這些信息發送給 Kafka。Kafka 可以進一步連接到 Spark 流處理服務,該服務可以在內容流入時進行分析,也可以將其轉儲到可以運行各種查詢的 Hadoop 集羣中。

8、最後上線時間(Last Seen)

最後上線時間服務會偵聽各種事件,從中計算用戶最後看到某條消息的時間。最後上線時間保存在 Redis 或者 Cassandra 中。Redis 不適合存儲大顆粒的數據,如果有一些額外的信息需要和已閱時間存在一起, Cassandra 會更合適。

Whatsapp 中的事件分爲兩種,由應用程序觸發的事件和由用戶觸發的事件。

9、水平伸縮

到目前爲止,我們討論的所有組件都是水平可伸縮的。我們可以根據需要增加或刪除實例。監控和其他一些需要滿足我們 “無滯後” 要求的東西。我們將監控所有服務的 CPU 利用率、網絡服務的延遲、Kafka 和數據庫的磁盤利用率等,當我們看到性能下降或任何延遲時,我們會添加更多節點。基於這種監控和警報,我們可以編寫腳本來自動執行一些操作,例如如果磁盤實用程序達到 80%,則可以啓動另一個數據庫實例,其 CPU 利用率達到 85%,添加另一個 Web Socket Handler 服務的實例,等等。

四、安全機制


4.1 消息傳輸安全

WhatsApp 使用端到端加密。理想情況下,這意味着只有消息的原始發送者和真正的接收者才能閱讀純文本消息。

當您發送消息時,它會使用特定的加密協議進行加密(接下來會有更多內容)。WhatsApp 然後將此加密消息存儲在他們的服務器上,直到將其傳遞給收件人。發送時,收件人的設備使用唯一的密碼密鑰將消息解密回可讀的明文消息。在整個過程中,WhatsApp 永遠不知道你信息的內容。

WhatsApp 的加密技術被稱爲 Signal Encryption Protocol,,由 Open System Whispers 公司開發,是異步消息系統的現代、開源、強加密協議。2016 年 4 月 5 日,WhatsApp 和 Open Whisper Systems 宣佈,他們已經完成了對 WhatsApp 上 “各種形式的通信” 的端到端加密,用戶現在可以相互驗證密鑰。用戶還可以選擇啓用首次使用信任機制,以便在通信人的密鑰更改時得到通知。根據與公告一起發佈的白皮書,WhatsApp 消息是用信號協議加密的。WhatsApp 調用使用 SRTP 加密,所有客戶端 - 服務器通信都在單獨的加密通道內分層。WhatsApp 使用的信號協議庫是開源的,並根據 GPLv3 許可發佈。端到端加密可能會讓你在理論上感到安全。在實踐中,端到端加密並不像人們希望的那樣保護隱私。

4.2 數據備份安全

2021 年 10 月 14 日,Whatsapp 爲 iOS 和安卓用戶推出端到端的數據加密備份機制。在安卓和 iOS 上,備份數據被存儲在 iCloud 或者谷歌 Drive 上, 這意味着蘋果公司或者谷歌可以按照政策要求將數據提交給政府。爲此, WhatsApp 允許用戶爲備份數據設置密碼或者 64 位的加密密鑰,理論上來說是無法破解的。

如上是從現有資料中分析的 WhatsApp 的架構, 對本文有興趣的同學,可以評論下留言,歡迎交流。

參考資料

CodeKarle:WhatsApp 系統設計 | Slack/FBMessenger 系統設計

Understanding WhatsApp's Architecture & System Design

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/NKfzEuAckMwA3wDX67r0Yg