大家可能對億級流量沒有什么概念侵贵,我首先以10億級流量來進(jìn)行一下設(shè)備需求的評估:
每天需要承載10億+請求流量數(shù)據(jù),一天24小時蝇率,對于平臺來說拥坛,晚上12點到凌晨8點這8個小時幾乎沒多少數(shù)據(jù)涌入的。這里我們使用「二八法則」來進(jìn)行預(yù)估读处,也就是80%的數(shù)據(jù)(8億)會在剩余的16個小時涌入糊治,且8億中的80%的數(shù)據(jù)(約6.4億)會在這16個小時的20%時間 (約3小時)涌入。
通過上面的場景分析,可以得出如下:
QPS計算公式 = 640000000 ÷ (3 * 60 * 60) = 6萬
也就是說高峰期集群需要抗住每秒6萬的并發(fā)請求罚舱。
首先從網(wǎng)絡(luò)設(shè)備來看井辜,目前比較普及的是千兆網(wǎng)卡绎谦,首先我們以千兆網(wǎng)卡來進(jìn)行設(shè)備臺數(shù)的估算:
假設(shè)每條數(shù)據(jù)平均按20kb(生產(chǎn)端有數(shù)據(jù)匯總)來算,高峰期的時候粥脚,1000M網(wǎng)卡不可能打滿帶寬窃肠,我們按80%來算,所以每臺機(jī)器能處理的請求數(shù)是
1000*1024kb%0.8/20kb =4W
再考慮到高可靠性問題阿逃,每臺機(jī)器至少需要3個副本铭拧,所以每臺機(jī)器能處理的請求大約是1W左右。
隊列如何設(shè)計恃锉?
從上面的估算我們得出,一臺機(jī)器是搞不定10億請求的呕臂,所以我們需要多臺機(jī)器來解決問題破托。在大學(xué)課本上我們學(xué)過,隊列具備先進(jìn)先出的特性歧蒋,從數(shù)據(jù)結(jié)構(gòu)上來說土砂,需要一個頭尾指針,如果是多機(jī)部署谜洽,那么我們這個頭尾指針就不能記錄到一臺機(jī)器上萝映,所以我們需要一個記錄頭尾指針的地方。
用隊列解決問題阐虚,其實是用生產(chǎn)者消費模式來解決問題序臂,生產(chǎn)者不停往隊列生產(chǎn)數(shù)據(jù),消費者不停進(jìn)行數(shù)據(jù)消費实束。所以兩者需要緊密配合奥秆,不然有一方速度跟不上都會導(dǎo)致10億的目標(biāo)無法實現(xiàn)。
假設(shè)生產(chǎn)者消費的過程中生產(chǎn)的速度很快咸灿,消費的速度很慢构订,如果不限制生產(chǎn)者的生產(chǎn)速度那么可能會出現(xiàn)內(nèi)存的OOM,因為內(nèi)存是有限制的避矢,但是如果為了避免限制生產(chǎn)者的生產(chǎn)悼瘾,因為如果限制了就無法達(dá)到10億的目標(biāo),所以我們要解決這個問題就不可能用內(nèi)存去存儲數(shù)據(jù)审胸,要么把數(shù)據(jù)存儲在磁盤上亥宿,或者數(shù)據(jù)庫。
總體如何設(shè)計歹嘹?
從以上分析我們可以知道箩绍,總體上我們采用分布式的架構(gòu),為了存儲隊列的頭尾指針尺上,我們設(shè)計一個類似于元數(shù)據(jù)的東西材蛛,就是要分布式存儲隊列的頭尾指針來記錄隊列的生產(chǎn)和消費情況圆到,同時我們需要一個服務(wù)節(jié)點來進(jìn)行分布式協(xié)調(diào),我們需要這個服務(wù)節(jié)點來接受生產(chǎn)者生產(chǎn)的數(shù)據(jù)卑吭,同時將數(shù)據(jù)傳遞到消費者手里芽淡,同時這個服務(wù)節(jié)點需要將數(shù)據(jù)進(jìn)行分布式存儲,要么存儲到磁盤豆赏,或者是數(shù)據(jù)庫挣菲。
通過以上的分析這個架構(gòu)基本成型,但是我們還需要在細(xì)節(jié)上去考慮一些東西掷邦,不然還是無法實現(xiàn)目標(biāo)白胀。
首先,我們要考慮消費者如何去服務(wù)節(jié)點獲取數(shù)據(jù)抚岗,一般獲取數(shù)據(jù)方式的有兩種或杠,要么是推(push),要么是拉(pull)宣蔚,那么我們應(yīng)該怎么選擇呢向抢?如果采用推模式,那么這個服務(wù)器節(jié)點必須記錄狀態(tài)胚委,不然無法保證數(shù)據(jù)的順序性和連續(xù)性消費挟鸠,我們知道一旦服務(wù)器有了狀態(tài)是很災(zāi)難的事情,首先服務(wù)器處理起來相當(dāng)復(fù)雜亩冬,其次服務(wù)器需要記錄大量狀態(tài)并進(jìn)行狀態(tài)管理導(dǎo)致資源擴(kuò)張的問題艘希,因為我們不知道我們的客戶即消費者的規(guī)模,而導(dǎo)致服務(wù)器承擔(dān)管理相當(dāng)多的資源鉴未,可能是無限的枢冤,同時推模式因為有了狀態(tài)導(dǎo)致服務(wù)節(jié)點無法做到橫向擴(kuò)展。所以我們只能選擇拉模式铜秆,將狀態(tài)和復(fù)雜性留著客戶端淹真,這樣才能做到服務(wù)端達(dá)到無限水平擴(kuò)展。
其次连茧,我們要考慮數(shù)據(jù)如何存儲的問題核蘸,如果存儲在磁盤上,由于非SSD的磁盤對隨機(jī)讀不友好啸驯,所以我們需要考慮數(shù)據(jù)如何寫入到磁盤的問題客扎,考慮到性能方法肯定是順序?qū)懯切阅茏詈玫模侨绻菍懭氲酱疟P上罚斗,我們考慮到海量數(shù)據(jù)的檢索問題徙鱼,所以需要對文件進(jìn)行分塊存儲和進(jìn)行索引問題。如果是存儲到數(shù)據(jù)庫,我們可以利用數(shù)據(jù)庫的天生的分庫和自帶的索引功能來避免復(fù)雜的設(shè)計袱吆。
如何做到高性能和高可用厌衙?
在高性能方面,可以參考我之前寫的Netty簡介和Dubbo的線程模型兩篇文章绞绒,總體上來說就是借助于IO多路復(fù)用模式同時綜合使用線程池和隊列等技術(shù)來充分壓榨服務(wù)的CPU和IO來為我們所用婶希,總體原則是不能浪費服務(wù)器資源,同時也不能讓服務(wù)器閑置蓬衡。
在高可用方面喻杈,我們需要考慮考慮服務(wù)節(jié)點和存儲數(shù)據(jù)的多副本機(jī)制,然而既然存在多副本狰晚,我們就需要處理多副本的同步問題筒饰,如果不想折騰可以直接使用Zookeeper,zk使用zab協(xié)議保證多副本的同步問題家肯,對于分布式同步協(xié)議可以參考我之前寫的分布式協(xié)議簡介龄砰,很多分布式數(shù)據(jù)為了不依賴于zk,自己實現(xiàn)了Paxos或者Raft實現(xiàn)了副本同步問題讨衣。
由于在存儲方面我們設(shè)計了多分區(qū)存儲,那么可以利用多分區(qū)的特性來加速生產(chǎn)者的生產(chǎn)和消費者的消費式镐,在生產(chǎn)端我們可以讓多個生產(chǎn)者同時將數(shù)據(jù)生產(chǎn)到不同的分區(qū)反镇,消費端讓消費者消費不同的分區(qū),這樣就可以大大提高生產(chǎn)和消費的速度娘汞,對于消費端還要考慮橫向擴(kuò)展問題歹茶,就是如果有消費者上下線要考慮怎么做重平衡問題,保證消費端的均衡消費你弦。
at least once和exactly once
at least once是對一個隊列產(chǎn)品的基本要求惊豺,如果滿足不了這個要求一般都不是一個合格的隊列產(chǎn)品,at least once一般要求客戶端能處理重復(fù)消費的問題禽作,即保證無狀態(tài)尸昧。exactly once是最難的,要實現(xiàn)這點旷偿,我們需要保證消息僅僅被投遞一次烹俗,那么我們需要在服務(wù)端不能丟消息,也不能重復(fù)投遞消息萍程,為到達(dá)這點我們可以參考關(guān)系數(shù)據(jù)庫的redo和undo日志的實現(xiàn)來進(jìn)行設(shè)計幢妄,同時需要在業(yè)務(wù)上確定消息的唯一性,我們需要給消息加入唯一標(biāo)識茫负,而保證消息不被重復(fù)投遞蕉鸳,這個跟數(shù)據(jù)庫的主鍵很像。
其他方面
一個隊列的開發(fā)我們可以使用任何語言忍法,如果使用java的話潮尝,我們還可以做其他優(yōu)化榕吼,比如為了避免jvm的垃圾回收機(jī)制導(dǎo)致內(nèi)存頻繁的回收,我們可以使用堆外內(nèi)存設(shè)計內(nèi)存池來加快內(nèi)存的分配和重復(fù)利用衍锚,同時可以利用零拷貝技術(shù)避免內(nèi)存數(shù)據(jù)在用戶態(tài)和內(nèi)核態(tài)重復(fù)的進(jìn)行拷貝來提高性能友题。我們也可以使用自定義協(xié)議或者壓縮技術(shù)來實現(xiàn)數(shù)據(jù)的高效傳輸?shù)取?/p>
總結(jié)
設(shè)計一個高性能和高可用的隊列產(chǎn)品是一個比較有難度和有技術(shù)的活,我們可以利用分布式存儲技術(shù)戴质,分布式協(xié)調(diào)協(xié)議度宦,以及IO多路復(fù)用技術(shù)等來為我們所用,但是從細(xì)節(jié)方面來說告匠,要實現(xiàn)一個生產(chǎn)上可用的合格隊列產(chǎn)品戈抄,需要付出相當(dāng)?shù)呐Σ拍軐崿F(xiàn)。