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