本篇文章會向讀者展示幾個架構設計的關鍵點,使一個社交應用能夠成為真正的下一代社交產品。以下幾個屬性將會影響到架構的設計:
a)可用性
b)可擴展性
c)性能和靈活性可擴展
目標
a)確保用戶的內容數據能夠很方便的被其他用戶發現和獲取.
b)確保內容推送是相關的,不僅在語義上,也是從用戶設備的角度。
c)確保實時更新生成、推送和分析。
d)盡可能地節省用戶的資源。
e)不論服務器負載變化如何,用戶體驗應保持不變。
f)確保應用整體上是安全的
總之,我們要處理一個相當大的挑戰,我們必須處理不斷擴大的海量用戶生成的內容數據,不斷增長的用戶,和一個不斷迭代的新項目,同時必須確保性能足夠出色。為了應對上述的挑戰,我們必須學習架構某些關鍵的元素,這將影響到系統的設計。以下是一些關鍵的決定和分析。
數據存儲
數據和數據模型的存儲是一個好架構的關鍵設計之一。一個社交產品應該能夠處理多種類型的數據,因此首先得充分分析數據並透徹理解,之后再設計數據模型和數據存儲。
第一步,我們要確定哪些數據是經常查詢的熱點數據,哪些不是經常需要的那些數據(如歸檔數據用於分析)。對於高頻訪問的數據,它必須總是可用,能夠快速讀寫和水平可擴展。目前我們所有業務場景使用的都是MySQL,即使我們的用例不一定需要使用關系數據庫系統。隨着我們數據的增長,我們的讀寫將成為我們應用程序性能瓶頸。我們應該為每秒鍾數十億的查詢做好准備。
讓我們對我們的數據進行分類:
a)主要的數據或靜態形式的數據,如用戶資料
b)語義數據
c)用戶產生的內容數據
d)會話數據
找到一個高效的數據存儲方式,滿足所有這些類型的數據,真的很難。因此,我們將為每個數據類型選擇特定的數據存儲方式。
靜態數據:對於靜態數據,最好是選擇基於文檔的存儲方式,其中鍵和值都是可查詢的。我們可以選擇如MongoDB這種文檔型數據庫,選擇MongoDB最大的優勢是它提供了在文檔級別的ACID。
MongoDB可以在多個分布式數據中心的范圍內進行縮放。它將允許我們使用副本集來保持冗余,從而解決我們的可用性問題。
數據分片是一個重要的考慮因素,數據分片可以確保數據的擴展與查詢速度。幸運的是,MongoDB透明的支持了數據分片。
關聯的或關系數據(核心數據):我們大部分數據本質上是關聯的,例如,A是B的朋友,C是A和B的朋友,這樣高度語義的數據最適合圖處理模型。我們應將這樣的數據存儲在圖數據庫,如Neo4j。這樣做的優勢很明顯;我們可以存儲所有關聯數據的節點,從而節省了計算數據之間連接關系的額外步驟。圖形數據模型也將有助於我們捕捉到屬性之間的關系。當試圖探索關聯數據時,豐富的屬性關系絕對是關鍵。圖數據庫支持ACID規則以及自動索引。
再次聲明,我們的要求是達到可用性和可擴展性。我們可能會有成百上千的並發事務,同時寫入數據庫,同時會有數百和數千查詢請求。它應該能夠處理一個數據集上的許多字節,超過十億每秒的讀取速度。
我們將會需要一個系統,幫助我們自動伸縮寫入和讀取。其他需要考慮的因素是數據分片,這是系統可伸縮的關鍵。
Neo4j已經被設計為可水平擴展,並且有數據冗余功能來保證可用性。但到目前為止,它還不支持數據分片。我們可能需要更多的分析,才能做出抉擇。其他可供選擇的圖數據庫有FlockDB、AllegroGraph和InfiniteGraph。
二進制數據(UGC):我們還必須處理大量的與用戶相關的二進制數據。處理二進制數據不太容易,考慮到它們的規模。上面已經討論過,我們需要一個系統可以運行相當高的性能,秒級別(尖峰),當決定在哪里存儲時,可伸縮和可用性是最關鍵的素。我們不能依靠磁盤文件系統來存儲我們的二進制數據。我們必須考慮可用性和可擴展性,文件系統的緩存會消耗大量的CPU。相反的,我們應該依靠一個現有的可用的系統,例如亞馬遜S3,S3是非常流行的對象存儲系統,具有可用性和彈性存儲。
我們也可以考慮谷歌雲存儲或Rackspace的雲文件等,但S3似乎是明顯的贏家,它提供更優質的服務。
S3已經支持數據分區。S3能夠水平伸縮,冷熱數據拆分,並根據keys分區。但是只實現存儲數據是不夠的,與這些內容相關的元數據必須能夠被搜索,並且搜索可伸縮,速度夠快。我們也可以嘗試一些新的東西,如圖像的自動維度識別,基於內容自動打標簽等。這是一個潛在的知識產權領域。我們將在文章的索引部分討論索引需求。但現在,讓我們只需要注意,我們將用標識符存儲內容,並且在某個地方做了索引。似乎亞馬遜的S3最適合這種情況。
Session數據
正確的認識和理解session數據是非常重要的。Session數據將幫助我們保持用戶的狀態。Session數據必須使用與服務器無關的方式,方便我們服務端可伸縮部署。這將有助於保持我們的設計靈活,確保session不會綁定到特定的節點或服務器。
我們得用一種新的方式來更新用戶的實際session,如果用戶的session終止,我們仍然可以幫助用戶從一個地方,他離開的地方重新恢復信息。
這是特別重要的,在我們的場景中,連接是不可靠的,數據丟包是很正常的。數據必須能夠被跨節點訪問,因此需要可用性和可擴展性。我們可以很好的使用MongoDB本身來保存數據。后來,我們想轉移到純粹的鍵值存儲,如Redis。
注:所有推薦和離線作業都應該只運行在非服務節點上。
索引
索引是我們系統的關鍵。用戶可以搜索任何內容,這是我們的主要用例之一。為了提升搜索性能,我們必須非常認真地對待索引。這里有兩點需要考慮:首先是,創建索引本身,然后就是索引系統本身。
為了做一個有意義的搜索系統,我們必須設計一個實時索引,針對一段時間窗口的實時數據進行處理。首先,我們可以寫一個非常簡單的系統,對產生的內容數據做倒排索引。后來,隨着輸入數據的增加,我們可以方便地用實時數據處理引擎取代它,如Apache的Storm,這是一個分布式的,容錯和高度可擴展的系統。它可以負責生成索引的邏輯。
索引系統:由於Lucene受歡迎程度和其性能,因此,Lucene是一個顯而易見的好選擇;它的性能是無與倫比的。我們可以使用SolrCloud。它已經透明的支持分片,復制和讀寫方面的容錯。
隊列&消息推送
每次我們的應用程序被觸發一個事件,我們將需要向他/她的追隨者/朋友推送消息。重要的是,我們的系統不能錯過任何這些信息,更重要的是,能夠在發生故障時恢復這些事件。為了達到這些要求,我們必須尋找一個隊列解決方案。我們可以使用ActiveMQ,這是最可靠的隊列軟件。它支持集群的高可用性,支持分布式隊列。
消息推送是另一個領域,要把通知發送給我們的用戶。在這里我們需要估計一下規模。我們應該准備好支持像nps這樣上億的規模。這里有許多選擇,但也許pyapns、CommandIQ和APP Booster才是最流行的。
我們需要自己管理一些事情,特別是要保證消息傳遞可靠性,即使用戶的設備處於離線狀態。我建議我們實現一個雙向的系統,保持狀態的通知,並在后台持久化到磁盤。所以每次一個通知失敗時,它的狀態都被處理並標上狀態碼,添加到重試隊列中。最后,當通知被送達,移出重試出列。
緩存策略
像我們這樣的系統,我們的目標是使其支撐十億RPS,因此,好的緩存策略是極重要的。我們的業務邏輯會在多層緩存中,並且能夠智能的清除失效緩存。讓我們看看最頂層緩存。
應用層緩存(內容緩存):為了最大限度地減少緩存未命中,並確保緩存始終是最新的數據,我們必須尋找一個從未過期的緩存,並始終保持數據。這基本上意味着在一般使用情況下,我們將永遠不用查詢我們的數據庫,因此節省了大量的資源。我們還應該確保我們緩存的數據總是以一種不需要額外處理的格式,隨時准備好呈現。這基本上意味着將我們的在線負載轉換為離線負載,從而節省了延遲。要做到這一點,我們必須確保每一次的內容被輸入到系統中,我們要做兩件事情:
a)原內容是非規格化形式保存在緩存。為了安全起見,我們將永遠設置一個有效的期限。
b)原內容也寫在我們的數據存儲區中。
我們使用Redis來做這個緩存,Redis是一種具有良好故障恢復的內存緩存。它具有高度的可擴展性,較新的版本透明的支持了數據分片。支持主從節點配置。最好的部分是,我們能夠保存任何格式的數據,這使得它很容易做增量寫,這是至關重要的,我們支持內容feeds
還值得指出的是,我們需要支持對大內容對象進行大量的讀 - 修改 - 寫操作和少量讀,Redis是已知的,對這些操作在性能方面是最好的。
緩存代理:反向代理層的緩存也是至關重要的。它有助於減少直接請求我們服務器的負載,從而減少延遲。為了使代理服務器緩存更有效,需要正確設置HTTP響應頭。代理服務器有很多種,但最受歡迎的是nginx和ATS。
二級緩存(代碼級緩存):這是一個實體數據的本地存儲,用於提高應用程序的性能。它有助於通過減少昂貴的數據庫調用以提高性能,保持實體數據的本地化。EhCache是一個很受歡迎的選擇。
客戶端緩存:這實際上是設備或瀏覽器緩存。所有靜態項目都應該盡可能地緩存。如果API響應HTTP緩存頭已經被合理設置,很多相關資源的內容都會被緩存。我們應確保其如預期的那樣工作。除此之外,我們應該盡可能緩存其他內容,可以使用設備自己的內存,或使用SQLite。所有昂貴的對象都應該緩存。例如NSDateFormatter和NSCalendar,初始化緩慢,應該盡可能多的重用。iOS Lot可以調整和應用,但是在這里,它是超出我們的研究范圍。
數據壓縮
考慮到我們的用戶主要是要處理大量的圖像和視頻,需要下載大量的數據,所以優化下載大小是非常重要的。它將節省用戶的數據量,提高應用程序的性能體驗。
其他要考慮的方面,如我們的網絡,我們的用戶主要是在非LTE網絡,使用2.5G或3G,需要考慮帶寬,並且連接通常是不可靠的,數據使用成本高。在這種情況下,智能壓縮是一個關鍵的需求。
但是實際上圖像壓縮和視頻壓縮並不是想象中那么直接簡單,往往需要進行深入的分析。我們所處理的圖像和視頻,可以無損和有損,這取決於用戶的設備質量。所以我建議使用多個壓縮技術來處理這種情況。在這種情況下,我們可以嘗試幀內壓縮和幀間壓縮技術。
但總的來說我們可以采用zpaq和fp8來應對所有壓縮需求。我們也可以嘗試非常適合我們業務場景的WebP。一般情況下,我們的API會使用gzip,我們API response總是經過gzip壓縮過的。
數據轉碼
考慮到我們需要處理多個設備,多個操作系統和屏幕分辨率,我們的內容存儲和處理時應與設備無關。但服務層應該基於用戶的設備,理解並調整響應的內容。所以,圖像和視頻的轉碼是必不可少的。
我們的應用程序需要收集設備的配置,如內存、編碼和屏幕分辨率,作為API的上下文。我們的API應該使用此上下文來修改/選擇內容版本。基於我們接受到的設備上下文,我們可以預先准備好一些最頻繁被請求的版本的內容。
我們可以使用FFMPEG轉碼,FFMPEG是最可靠和應用最廣的轉碼框架。我們可以修改FFMPEG,使其滿足我們的需求。轉碼是在數據輸入端完成的。
傳輸協議
考慮到我們的網絡場景(非LTE,不可靠的連接等),關鍵是要盡可能地節省資源,使通信盡可能地輕量。我建議我們所有的HTTP請求都使用okhttp客戶端,okhttp使用SPDY協議,能夠彈性處理連接失敗,透明恢復。
我們所有的通訊需求,都應該切換到MQTT,這是一個輕量級的機器對機器的連接協議。
安全問題
保證我們應用程序的安全是非常重要的。我們整體架構都要有安全上的考慮。我在這里只談架構為滿足安全要求做出的改變,我們不談實施過程的改變。
這里是一些必須添加到架構里的:
1. 我們所有的用戶數據必須加密。MongoDB和Neo4j已經支持存儲加密。在這基礎上,我們可以決定加密哪些用戶關鍵信息。所有與數據庫相關的傳輸調用必須啟用加密。
2. 安全套接字層:所有代理服務器的訪問都應該使用SSLed。代理服務器可以充當SSL終止點。
3. 我們所有的API端點應該運行在非默認端口,並且必須實現OAuth。
4. 所有的DB讀取都應該通過Rest endpoints。
5. 有關密碼的配置必須特殊處理。密碼必須hashed,文件應該被限制只能在應用啟動時讀取。這允許我們通過文件系統權限來控制應用程序身份實例。只有應用程序用戶可以讀,但不能寫,其他用戶不可以讀取。所有類似的配置都要用keydb打包並需要密碼。
組件
以下是我們架構用到的組件:
1. 負載均衡器:這層是用來轉發所有對代理服務器的請求,基於定制的策略。這一層也將有助於我們通過基於容量重定向的方式來保障可用性。
2. 代理服務器:所有即將到來的調用都必須以這里為入口。這也是我們SSL的終止點。它緩存所有基於策略定義的HTTP請求。FE層:該層運行一個node服務器。
3. 數據輸入引擎:這個組件涉及所有內容的輸入,它做了一系列的工作:非規范化模型,轉碼,緩存等。將來如果可以的話,所有內容的處理,都可以在這里完成。
4. Rest服務:這層負責與所有DB交互,並返回數據。它的訪問是受OAuth保護的。這可以用Tomcat容器以及edge緩存來實現。
5. 事件處理:這層處理所有的事件,主要負責分發的功能。它讀取ActiveMQ並使用通知引擎生成通知。
6. 推薦引擎:這個組件通過分析所有收集到的用戶動態來做推薦。根據實際收集到的動態,我們可以部署各種基於親和力的算法。我們可以使用Apache Mahout提供的各種算法接口
系統的邏輯視圖:
結語
本篇文章更像是對關鍵組件高抽象層次的分析。如果需要實施的建議,可以做一個階段性的方式,但如果我們需要擴展性並支持真正的用例,必須遵循我提出的這些規范。我沒有提起任何設計領域相關的內容。這只是設計階段,需要更深入的分析和了解系統的當前狀態。
來自HackerNews的評論:https://news.ycombinator.com/item?id=9930752
原文鏈接:Architecting Backend For A Social Product(譯者/施聰羽 審校/朱正貴 責編/仲浩 )