注意:可視化的演示參見(jiàn) slide deck。
NSQ 是繼承于 simplequeue(部分的 simplequeue),因此被設(shè)計(jì)為(排名不分先后)
單個(gè) nsqd 實(shí)例被設(shè)計(jì)成可以同時(shí)處理多個(gè)數(shù)據(jù)流。流被稱為“話題”和話題有 1 個(gè)或多個(gè)“通道”。每個(gè)通道都接收到一個(gè)話題中所有消息的拷貝。在實(shí)踐中,一個(gè)通道映射到下行服務(wù)消費(fèi)一個(gè)話題.
話題和通道都沒(méi)有預(yù)先配置。話題由第一次發(fā)布消息到命名的話題或第一次通過(guò)訂閱一個(gè)命名話題來(lái)創(chuàng)建。通道被第一次訂閱到指定的通道創(chuàng)建。
話題 和通道的所有緩沖的數(shù)據(jù)相互獨(dú)立,防止緩慢消費(fèi)者造成對(duì)其他通道的積壓(同樣適用于話題級(jí)別)。
一個(gè)通道一般會(huì)有多個(gè)客戶端連接。假設(shè)所有已連接的客戶端處于準(zhǔn)備接收消息的狀態(tài),每個(gè)消息將被傳遞到一個(gè)隨機(jī)的客戶端。例如:
http://wiki.jikexueyuan.com/project/nsq-guide/images/design1.gif" alt="nsqd clients" />
總之,消息從話題->通道是多路傳送的(每個(gè)通道接收的所有該話題消息的副本),即使均勻分布在通道->消費(fèi)者之間(每個(gè)消費(fèi)者收到該通道的消息的一部分)。
NSQ 還包括一個(gè)輔助應(yīng)用程序,nsqlookupd,它提供了一個(gè)目錄服務(wù),消費(fèi)者可以查找到提供他們感興趣訂閱話題的 nsqd 地址 。在配置方面,把消費(fèi)者與生產(chǎn)者解耦開(kāi)(它們都分別只需要知道哪里去連接 nsqlookupd 的共同實(shí)例,而不是對(duì)方),降低復(fù)雜性和維護(hù)。
在更底的層面,每個(gè) nsqd 有一個(gè)與 nsqlookupd 的長(zhǎng)期 TCP 連接,定期推動(dòng)其狀態(tài)。這個(gè)數(shù)據(jù)被 nsqlookupd 用于給消費(fèi)者通知 nsqd 地址。對(duì)于消費(fèi)者來(lái)說(shuō),一個(gè)暴露的 HTTP /lookup 接口用于輪詢。
為話題引入一個(gè)新的消費(fèi)者,只需啟動(dòng)一個(gè)配置了 nsqlookup 實(shí)例地址的 NSQ 客戶端。無(wú)需為添加任何新的消費(fèi)者或生產(chǎn)者更改配置,大大降低了開(kāi)銷和復(fù)雜性。
注:在將來(lái)的版本中,啟發(fā)式 nsqlookupd 可以基于深度,已連接的客戶端數(shù)量,或其他“智能”策略來(lái)返回地址。當(dāng)前的實(shí)現(xiàn)是簡(jiǎn)單的返回所有地址。最終的目標(biāo)是要確保所有深度接近零的生產(chǎn)者被讀取。
值得注意的是,重要的是 nsqd 和 nsqlookupd 守護(hù)進(jìn)程被設(shè)計(jì)成獨(dú)立運(yùn)行,沒(méi)有相互之間的溝通或協(xié)調(diào)。
我們還認(rèn)為重要的是有一個(gè)方式來(lái)聚合查看,監(jiān)測(cè),并管理集群。我們建立 nsqadmin 做到這一點(diǎn)。它提供了一個(gè) Web UI 來(lái)瀏覽 topics/channels/consumers 和深度檢查每一層的關(guān)鍵統(tǒng)計(jì)數(shù)據(jù)。此外,它還支持幾個(gè)管理命令例如,移除通道和清空通道(這是一個(gè)有用的工具,當(dāng)在一個(gè)通道中的信息可以被安全地扔掉,以使深度返回到 0)。
http://wiki.jikexueyuan.com/project/nsq-guide/images/design2.png" alt="nsqadmin" />
這是我們的高優(yōu)先級(jí)之一。我們的生產(chǎn)系統(tǒng)處理大量的流量,都建立在我們現(xiàn)有的消息工具上,所以我們需要一種方法來(lái)慢慢地,有條不紊地升級(jí)我們特定部分的基礎(chǔ)設(shè)施,而不產(chǎn)生任何影響。
首先,在消息生產(chǎn)者方面,我們建立 nsqd 匹配 simplequeue。具體來(lái)說(shuō),nsqd 暴露了一個(gè) HTTP /PUT 端點(diǎn),就像 simplequeue,上傳二進(jìn)制數(shù)據(jù)(需要注意的一點(diǎn)是 endpoint 需要一個(gè)額外的查詢參數(shù)來(lái)指定”話題”)。想切換到發(fā)布消息到 nsqd 的服務(wù)只需要很少的代碼變更。
第二,我們建立了兼容已有庫(kù)功能和語(yǔ)義的 Python 和 Go 庫(kù)。這使得消息的消費(fèi)者通過(guò)很少的代碼改變就可使用。所有的業(yè)務(wù)邏輯保持不變。
最后,我們建立工具連接起新舊組件。這些都在倉(cāng)庫(kù)的示例(examples)目錄中:
nsq_pubsub - 在 NSQ 集群中以 HTTP 接口的形式暴露的一個(gè) pubsub nsq_to_file - 將一個(gè)給定話題的所有消息持久化到文件nsq_to_http - 對(duì)一個(gè)話題的所有消息的執(zhí)行 HTTP 請(qǐng)求到(多個(gè))endpoints。NSQ被設(shè)計(jì)以分布的方式被使用。nsqd 客戶端(通過(guò) TCP )連接到指定話題的所有生產(chǎn)者實(shí)例。沒(méi)有中間人,沒(méi)有消息代理,也沒(méi)有單點(diǎn)故障:
http://wiki.jikexueyuan.com/project/nsq-guide/images/design3.png" alt="nsq clients" />
這種拓?fù)浣Y(jié)構(gòu)消除單鏈,聚合,反饋。相反,你的消費(fèi)者直接訪問(wèn)所有生產(chǎn)者。從技術(shù)上講,哪個(gè)客戶端連接到哪個(gè) NSQ 不重要,只要有足夠的消費(fèi)者連接到所有生產(chǎn)者,以滿足大量的消息,保證所有東西最終將被處理。
對(duì)于 nsqlookupd,高可用性是通過(guò)運(yùn)行多個(gè)實(shí)例來(lái)實(shí)現(xiàn)。他們不直接相互通信和數(shù)據(jù)被認(rèn)為是最終一致。消費(fèi)者輪詢所有的配置的 nsqlookupd 實(shí)例和合并 response。失敗的,無(wú)法訪問(wèn)的,或以其他方式故障的節(jié)點(diǎn)不會(huì)讓系統(tǒng)陷于停頓。
NSQ 保證消息將交付至少一次,雖然消息可能是重復(fù)的。消費(fèi)者應(yīng)該關(guān)注到這一點(diǎn),刪除重復(fù)數(shù)據(jù)或執(zhí)行idempotent等操作
這個(gè)擔(dān)保是作為協(xié)議和工作流的一部分,工作原理如下(假設(shè)客戶端成功連接并訂閱一個(gè)話題):
這確保了消息丟失唯一可能的情況是不正常結(jié)束 nsqd 進(jìn)程。在這種情況下,這是在內(nèi)存中的任何信息(或任何緩沖未刷新到磁盤)都將丟失。
如何防止消息丟失是最重要的,即使是這個(gè)意外情況可以得到緩解。一種解決方案是構(gòu)成冗余 nsqd對(duì)(在不同的主機(jī)上)接收消息的相同部分的副本。因?yàn)槟銓?shí)現(xiàn)的消費(fèi)者是冪等的,以兩倍時(shí)間處理這些消息不會(huì)對(duì)下游造成影響,并使得系統(tǒng)能夠承受任何單一節(jié)點(diǎn)故障而不會(huì)丟失信息。
附加的是 NSQ 提供構(gòu)建基礎(chǔ)以支持多種生產(chǎn)用例和持久化的可配置性。
nsqd 提供一個(gè) --mem-queue-size 配置選項(xiàng),這將決定一個(gè)隊(duì)列保存在內(nèi)存中的消息數(shù)量。如果隊(duì)列深度超過(guò)此閾值,消息將透明地寫入磁盤。nsqd 進(jìn)程的內(nèi)存占用被限定于 --mem-queue-size * #of_channels_and_topics:
http://wiki.jikexueyuan.com/project/nsq-guide/images/design4.png" alt="message overflow" />
此外,一個(gè)精明的觀察者可能會(huì)發(fā)現(xiàn),這是一個(gè)方便的方式來(lái)獲得更高的傳遞保證:把這個(gè)值設(shè)置的比較低(如 1 或甚至是 0)。磁盤支持的隊(duì)列被設(shè)計(jì)為在不重啟的情況下存在(雖然消息可能被傳遞兩次)。
此外,涉及到信息傳遞保證,干凈關(guān)機(jī)(通過(guò)給 nsqd 進(jìn)程發(fā)送 TERM 信號(hào))堅(jiān)持安全地把消息保存在內(nèi)存中,傳輸中,延遲,以及內(nèi)部的各種緩沖區(qū)。
請(qǐng)注意,一個(gè)以 #ephemeral 結(jié)束的通道名稱不會(huì)在超過(guò) mem-queue-size 之后刷新到硬盤。這使得消費(fèi)者并不需要訂閱頻道的消息擔(dān)保。這些臨時(shí)通道將在最后一個(gè)客戶端斷開(kāi)連接后消失。
NSQ 被設(shè)計(jì)成一個(gè)使用簡(jiǎn)單 size-prefixed 為前綴的,與“memcached-like”類似的命令協(xié)議。所有的消息數(shù)據(jù)被保持在核心中,包括像嘗試次數(shù)、時(shí)間截等元數(shù)據(jù)類。這消除了數(shù)據(jù)從服務(wù)器到客戶端來(lái)回拷貝,當(dāng)重新排隊(duì)消息時(shí)先前工具鏈的固有屬性。這也簡(jiǎn)化了客戶端,因?yàn)樗麄儾辉傩枰?fù)責(zé)維護(hù)消息的狀態(tài)。
此外,通過(guò)降低配置的復(fù)雜性,安裝和開(kāi)發(fā)的時(shí)間大大縮短(尤其是在有超過(guò) > 1 消費(fèi)者的話題)。
對(duì)于數(shù)據(jù)的協(xié)議,我們做了一個(gè)重要的設(shè)計(jì)決策,通過(guò)推送數(shù)據(jù)到客戶端最大限度地提高性能和吞吐量的,而不是等待客戶端拉數(shù)據(jù)。這個(gè)概念,我們稱之為 RDY 狀態(tài),基本上是客戶端流量控制的一種形式。
當(dāng)客戶端連接到 nsqd 和并訂閱到一個(gè)通道時(shí),它被放置在一個(gè) RDY 為 0 狀態(tài)。這意味著,還沒(méi)有信息被發(fā)送到客戶端。當(dāng)客戶端已準(zhǔn)備好接收消息發(fā)送,更新它的命令 RDY 狀態(tài)到它準(zhǔn)備處理的數(shù)量,比如 100。無(wú)需任何額外的指令,當(dāng) 100 條消息可用時(shí),將被傳遞到客戶端(服務(wù)器端為那個(gè)客戶端每次遞減 RDY 計(jì)數(shù))。
客戶端庫(kù)的被設(shè)計(jì)成在 RDY 數(shù)達(dá)到配置 max-in-flight 的 25% 發(fā)送一個(gè)命令來(lái)更新 RDY 計(jì)數(shù)(并適當(dāng)考慮連接到多個(gè) nsqd 情況下,適當(dāng)?shù)胤峙洌?/p>
http://wiki.jikexueyuan.com/project/nsq-guide/images/design5.png" alt="nsq protocol" />
這是一個(gè)重要的性能控制,使一些下游系統(tǒng)能夠更輕松地批量處理信息,并從更高的 max-in-flight 中受益。
值得注意的是,因?yàn)樗仁腔诰彌_和推送來(lái)滿足需要(通道)流的獨(dú)立副本的能力,我們已經(jīng)提供了行為像 simplequeue 和 pubsub 相結(jié)合的守護(hù)進(jìn)程。這是簡(jiǎn)化我們的系統(tǒng)拓?fù)浣Y(jié)構(gòu)的強(qiáng)大工具,如上述討論那樣我們會(huì)維護(hù)傳統(tǒng)的 toolchain。
我們很早做了一個(gè)戰(zhàn)略決策,利用 Go 來(lái)建立 NSQ 的核心。我們最近的博客上講述我們?cè)?bitly 如何使用 Go,并提到這個(gè)適合的項(xiàng)目-通過(guò)瀏覽那篇文章可能對(duì)理解我們?nèi)绾沃匾曔@么語(yǔ)言有所幫助。
關(guān)于 NSQ ,Go channels(不要與 NSQ 通道混淆),并且內(nèi)置并發(fā)性功能的語(yǔ)言的非常適合于的 nsqd的內(nèi)部工作。我們充分利用緩沖的通道來(lái)管理我們?cè)趦?nèi)存中的消息隊(duì)列和無(wú)縫把溢出消息放到硬盤。
標(biāo)準(zhǔn)庫(kù)讓我們很容易地編寫網(wǎng)絡(luò)層和客戶端代碼。只需要付出很少的努力,來(lái)整合內(nèi)置的內(nèi)存和 CPU 剖析進(jìn)行優(yōu)化。我們還發(fā)現(xiàn)它易于單獨(dú)測(cè)試組件,模擬類型接口,以迭代方式構(gòu)建功能。