要點:
- Facebook 整合通訊系統(郵件、簡訊、聊天、訊息等);
- 用 HBase 作為後端儲存設施,每個使用者資料存放區在 HBase 的單獨一行裡,每個實體(檔案夾、主題、訊息等等)都儲存在自己的HBase列中;
- 涉及 HayStack 圖片處理基礎設施;
- 使用 Apache Lucene 維護反向索引列表;
- 鏡像了大約 10% 使用者的即時聊天和收件匣中的資訊到測試叢集中,並通過 dark launch 進行測試。
Facebook Messages 是我們曾經所建立的最具技術挑戰性的一個代表產品。
當我們發布Facebook Messages 時所提到的是我們需要打造一個專門的應用伺服器來管理其基礎架構。
我們最近討論了訊息後台和我們如何處理所有來自 email, SMS, Facebook Chat 和 Inbox 的通訊。
今天我們將深入訊息應用伺服器的核心。
應用伺服器的商務邏輯
應用伺服器整合了眾多Facebook服務和保護(shields)來自各種終端的複雜性。它提供了一個簡單介面方便用戶端進行標準訊息處理,包括:建立、讀取、刪除、更新訊息和收件匣。
下面是每一部分的流程。
當建立一個新訊息或回複訊息時,應用伺服器代表寄件者傳遞訊息到收件者。如果收件者是通過其郵件地址,則伺服器通過HayStack獲得附件(如果有的話),構造HTML主體,並建立一個RFC2822訊息。
輸出資料流
當訊息發送給使用者時,如果地址是一個回複處理,伺服器將從存在的郵件地址和傳遞的輸入訊息中獲得正確收件者的資訊。伺服器最終將訊息傳遞到使用者郵箱,運行所有必要的預先處理和後期處理過程,並決定基於多個訊號的檔案夾和主題的訊息路由。
輸入資料流
當讀取訊息時,伺服器取得有關使用者郵箱的多個統計,如容量;訊息、主題和回複數;朋友的數量等。同時也獲得檔案夾相關統計和屬性,各種搜尋條件的主題列表(檔案夾,屬性,作者,關鍵字等等),主題屬性和這個主題的其它訊息。
當刪除訊息時,伺服器標記要刪除的訊息和主題。一個離線任務具體清除訊息內容。
當更新訊息和主題時,伺服器改變訊息或主題屬性,如讀取和到達狀態,標籤等等。同時也處理多個使用者對主題的訂閱和取消訂閱的請求。
管理群組主題
Facebook Messages 使用一個聊天室模型管理群組訊息主題。使用者能加入(訂閱)和離開(取消訂閱)。
當郵件地址是是這個主題的指定接收者時這個模型是必須的,應用伺服器建立一個回複處理器,類似聊天室ID。當一個郵件接收者回複了主題,則訊息會被發送到回複處理器地址。
為了最佳化讀取效能和簡化移植和備份處理,訊息主題以一種非規格化(denormalized)的方式儲存。於是每個使用者都擁有一份主題中繼資料和訊息 的拷貝,伺服器廣播訂閱和取消訂閱事件,在一個分散的方式中同步所有接收者訂閱和回複處理的主題中繼資料。伺服器也管理類似使用者仍舊使用老的收件匣或通過他們的郵件地址訂閱的情形。
緩衝使用者中繼資料
當使用者訪問收件匣時,應用伺服器裝載最常用的使用者中繼資料(也稱活動中繼資料)並將它們儲存在最近最少使用的緩衝中(a least recently used cache,也就是LRU演算法)。隨後,同一使用者的請求會通過少量的HBase查詢被快速的處理。
我們需要減少HBase查詢,因為HBase不支援join, 為處理一個讀請求,伺服器可能需要在分開的HBase查詢中尋找多個索引和匹配中繼資料和訊息體。HBase的最佳化體現在寫操作而不是在讀取上,使用者行為 通常擁有好的時間和地區性(good temporal and spatial locality),於是緩衝能協助解決這個問題並提升效能。
我們也在通過減少使用者記憶體佔用和轉移到細粒度模式進而提升緩衝的有效性方面做出了很多努力。我們能緩衝5%-10%的使用者量和95%的活躍中繼資料緩 存命中率。我們在全域的memcache層緩衝了訪問極為頻繁的資料(如在Facebook首頁顯示沒有閱讀的訊息數)。當新訊息到達時應用伺服器標記緩衝為(dirties)(註:dirties表示修改了但還沒有寫到資料檔案的資料)。
同步
HBase對事務隔離提供了有限的支援。針對同一使用者的多個更新可能同時發生。為解決它們之間的潛在衝突,我們使用應用伺服器作為使用者請求的同步點。一個使用者在任何給定的時間裡由專屬的伺服器提供服務。這樣,同一使用者請求就可以在應用伺服器中以一種完整孤立的方式(Fashion)同步和執行。
儲存模式
MTA代理特性附件和大量訊息實體,在它們能到達應用伺服器之前被儲存在Haystack中。然而,中繼資料,包含索引資料和小的訊息體,它們儲存在 HBase中並由應用伺服器維護著。每個使用者的收件匣都是獨立於任何其它使用者的;使用者資料不會在HBase中共用(shared)。每個使用者資料存放區在 HBase的單獨一行裡,它包含了以下部分:
中繼資料實體和索引
中繼資料實體包含收件匣對象屬性,如檔案夾、主題、訊息等等。每個實體都儲存在自己的HBase列中。不像關係型資料庫(RDBMS),HBase沒 有提供用於索引的本地支援 。我們在應用級維護輔助索引(Secondary Indexes),同樣以鍵/值對的方式儲存在分開的列中。
比如,要回答查詢“loading unread threads on the second page of the Other folder,” 應用伺服器首先搜尋中繼資料索引以獲得合格主題列表,然後取出指定主題的中繼資料實體,以它們的屬性構造響應。
正如我們前面所提到的,緩衝和有效預裝載能減少HBase查詢量以獲得更好的效能。
活動紀錄
使用者郵箱中的任何更新(如發表和刪除訊息,標記主題為已讀等等)會立即以時間的順序添加到列中,這稱為一個活動紀錄(action log)。小的訊息實體也儲存在活動紀錄中。
我們能通過回放(replaying )活動紀錄的方式構造或恢複使用者郵箱的目前狀態,我們使用最後活動紀錄的ID以中繼資料實體和索引的版本回放。當使用者郵箱被載入,應用伺服器比較中繼資料版本 和最後活動紀錄ID,如果中繼資料版本滯後(lags behind)則更新郵箱內容。
活動紀錄儲存在應用級帶來極大的靈活性:
- 我們能通過回放活動紀錄無縫切換到一種新的模式並且能通過一個離線的 MapReduce 任務或線上的應用伺服器自身產生新的中繼資料實體和索引。
- 我們能在一個批處理中執行大量HBase非同步寫以節省網路頻寬和減少HBase壓縮成本。
- 它是與其它組件交換持久性資料的標準協議。比如,我們通過將活動紀錄寫到記錄日誌(Scribe log)做應用級的備份。這個移植管道轉化使用者老的收件匣資料到活動紀錄並且通過離線MapReduce產生中繼資料和索引。
搜尋索引
為支援全文檢索索引,我們維護著一個從關鍵字到匹配訊息的反向索引。當一個新訊息到達時,我們使用 Apache Lucene 去解析和轉化它到一個(keyword, message ID, positions)元組(tuples)中,然後以遞增的方式加入到 HBase 的列中。每個關鍵字都擁有自己的列。所有的訊息,包括聊天記錄,郵件和簡訊都被即時索引。
dark launch 測試
應用伺服器是我們從零開始構建的一個全新軟體,因此在將它推向5億使用者前我們需要監控它的效能、可靠性和伸縮性。我們最初開發了一個壓力測試機器人 (robot)用來產生類比請求,但是我們發現這樣的結果可能會受到其它一些新因素的影響,如訊息長度,不同類型請求的分發,使用者活躍度的分布等等。
為了模擬一個真實的產品負荷,我們製作了 dark launch,我們鏡像了大約10%使用者的即時聊天和收件匣中的資訊到測試叢集中。Dark launches 協助我們發現更多效能問題和識別瓶頸。我們也使用它作為一個令人信服的指標來評價我們所做的很多改進。接下來,我們會繼續努力為我們的所有使用者提供嶄新的訊息系統。