本篇文章會向讀者展現幾個架構設計的關鍵點,使一個社交應用可以成爲真正的下一代社交產品。如下幾個屬性將會影響到架構的設計:html
a)可用性 node
b)可擴展性 nginx
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終止,咱們仍然能夠幫助用戶從一個地方,他離開的地方從新恢復信息。
這是特別重要的,在咱們的場景中,鏈接是不可靠的,數據丟包是很正常的。數據必須可以被跨節點訪問,所以須要可用性和可擴展性。咱們能夠很好的使用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(譯者/施聰羽 審校/朱正貴 責編/仲浩 )