消息中間件本質(zhì)上就是一種很簡單的數(shù)據(jù)結(jié)構(gòu)——隊列袖外,但是光隊列肯定是構(gòu)不成中間件的汁掠,必須要考慮性能略吨、容災(zāi)、可靠性等等因素考阱。
在理解什么是nsq之前翠忠,先來考慮一下,為什么要使用消息中間件
為什么使用消息中間件
簡單來說乞榨,使用消息中間件就是為了解決分布式系統(tǒng)之間消息的傳遞秽之。
假如用戶在網(wǎng)站內(nèi)注冊了一個賬號,需要給其發(fā)短信和郵件吃既,至少要調(diào)用2個其他服務(wù)的接口考榨。
register (...) {
doRegister(...);
//調(diào)用其他服務(wù)接口
sendMsg(...);
sendEmail(...);
}
這樣的做法,顯然不合理鹦倚。
1河质、過度耦合:后面如果注冊之后要處罰其他的動作(比如要在APP內(nèi)也發(fā)一條消息),那就得去改代碼申鱼,在原有的注冊賬號的函數(shù)末尾愤诱,在追加代碼。
2捐友、缺少緩沖:如果注冊賬號時淫半,發(fā)送消息的系統(tǒng)恰好處于非常忙碌或者宕機的狀態(tài),那這時發(fā)送消息就會失敗匣砖,我們需要一個地方科吭,來暫時存放無法被消費的消息
3、執(zhí)行順序:每次用戶注冊完之后先發(fā)送短信猴鲫,再發(fā)送郵件对人,這樣的做法太low。
我們可以使用一個消息中間件拂共,來解決上面的問題牺弄。
我們往注冊中心和其他系統(tǒng)之間引入了一個消息中間件,或者可以先簡單點宜狐,理解為引入一個隊列势告。
當賬號注冊完成后,它只需要往隊列中塞入(push)一條topic為“register”的消息抚恒。接著咱台,我們的消息中間件(隊列)會把這條消息推送給所有訂閱了這個topic的消息的機器,告訴他們俭驮,“創(chuàng)建了一個新的用戶回溺,你們做自己該做的去吧”。
這樣一個簡單的隊列,就做到了:
1遗遵、系統(tǒng)解耦:如果后面有新的動作萍恕,需要在注冊賬號后執(zhí)行,那么只需要讓新的動作自己去訂閱topic為“register”的消息即可
2瓮恭、緩沖:如果消息發(fā)送系統(tǒng)現(xiàn)在很忙雄坪,沒空處理消息,那么只需跟消息中間件說屯蹦,“我很忙维哈,不要再發(fā)消息過來了”,那么消息中間件就不會給它推送消息登澜,或者消息發(fā)送系統(tǒng)出了故障阔挠,消息雖然推送過去了,但是它給處理失敗了脑蠕,那么也只需給消息中間件回復一個“requeue”的命令购撼,消息中間件就會把消息重新放入隊列,進行重試谴仙。
3迂求、并行執(zhí)行消息發(fā)送:郵件發(fā)送系統(tǒng)不需要等到短信發(fā)送完之后再開始發(fā)送郵件了,他只要收到消息晃跺,就可以執(zhí)行自己的操作揩局。
認識nsq
上面使用了一個簡單的隊列來充當消息中間件,在分布式系統(tǒng)中掀虎,這顯然是不可靠的凌盯。
首先,假設(shè)我的短信發(fā)送系統(tǒng)烹玉,部署了三臺實例驰怎,他們都訂閱了topic為“register”的消息,那么一旦有賬號創(chuàng)建二打,這三臺實例就都會收到消息县忌,并且去發(fā)送短信,而其實我只需要發(fā)送一次就ok了继效。
對于這樣的問題症杏,在nsq里涉及到了一個channel的概念。
短信發(fā)送系統(tǒng)的三個實例莲趣,當它們收到消息時鸳慈,要做的事情是一樣的饱溢,并且只需要有有一個實例執(zhí)行喧伞,那么它們就是一個消費者組里面的,要標識為同一個channel,比如說叫“send_msg”的channel潘鲫,而郵件發(fā)送系統(tǒng)翁逞,也要有自己的channel,用來和短信發(fā)送系統(tǒng)作區(qū)分溉仑,比如說叫“send_email”挖函。
當nsq收到消息時,會給每個channel復制一份消息浊竟,然后channel再給對應(yīng)的消費者組怨喘,推送一條消息。消費者組里有多個實例振定,那么要推給誰呢必怜?這就涉及到負載均衡,比如有一個消費者組里有ABC三個實例后频,這次推給了A梳庆,那么下次有可能是推送給B,再下次卑惜,也許就是C …
nsq官網(wǎng)上的一張動圖膏执,就是在解釋這個過程:
圖中,nsq上有一個叫”clicks“的topic露久,”clicks“下面有三條channel更米,其中channel名稱為”metrics“的,有三個實例抱环。消息A來到nsq后壳快,被復制到三條channel,接著镇草,在metrics上的那個A眶痰,被推送到了第二個實例上。接著梯啤,又來了一個叫B的消息竖伯,這一次,B被推送給了第一個實例進行處理因宇。
nsqlookup
我們已經(jīng)知道七婴,nsq收到生產(chǎn)者生產(chǎn)的消息后,需要將消息復制多份察滑,然后推送給對應(yīng)topic和channel的消費者打厘。
那么,nsq怎么知道哪些消費者訂閱了某個topic的消息呢贺辰?
我們需要一個類似于微服務(wù)里頭的注冊中心的模塊户盯,來實現(xiàn)服務(wù)發(fā)現(xiàn)的功能嵌施,這就是nsqlookup.
nsqlookup提供了類似于etcd、zookeeper一樣的kv存儲服務(wù)莽鸭,里面記錄了topic下面都有哪些nsq吗伤。
nsqlookup提供了一個/lookup接口,比如你想知道哪些nsq上面硫眨,有topic為order_created的消息足淆,那么只需要調(diào)一下:
curl 'http://127.0.0.1:4161/lookup?topic=order_created'
nsqlookup就會給你返回對應(yīng)topic的nsq列表:
{"channels":["send_msg"],"producers":[{"remote_address":"127.0.0.1:64402","hostname":"shuruideMacBook-Pro.local","broadcast_address":"127.0.0.1","tcp_port":4150,"http_port":4151,"version":"1.1.0"}]}
接著消費者只需要遍歷返回的json串里的producers列表,把broadcast_address和tcp_port或者http_port拼起來礁阁,就可以拿到要建立連接的url地址巧号。
消費者會和這些nsq,逐個建立連接姥闭。nsq收到對應(yīng)topic的消息后裂逐,就會給和他們建立連接的消費者,推送消息泣栈。
nsq的Java客戶端里卜高,就有這樣的邏輯,里面是遍歷了nsqlookup的列表南片,然后把所有l(wèi)ookup的返回結(jié)構(gòu)掺涛,進行合并。
com.github.brainlag.nsq.lookup.DefaultNSQLookup#lookup
接著和舊的nsq列表比較疼进,進行刪除和新增薪缆,保證本地的nsq列表數(shù)據(jù)是最新的。
com.github.brainlag.nsq.NSQConsumer#connect
這個過程會定期去執(zhí)行伞广,不斷去獲取最新的nsq列表拣帽。
nsq集群
nsq的集群部署非常簡單,官方推薦一個生產(chǎn)者對應(yīng)的部署一個nsqd:
基本的用法是每個nsqd作為一個producer的消息隊列嚼锄,producer不停地向一個nsqd推送message,然后由consumer通過lookupd連接到相應(yīng)的nsqd獲取message减拭。由NSQ文檔可知,producer打到某個nsqd上的message會存在nsqd所在host的內(nèi)存或磁盤中区丑,而不會擴散到其他的nsqd機器上拧粪,這樣若這個nsqd所在主機宕機,則所有在這個Host上的消息就會丟失沧侥。
總結(jié)
消息中間件的應(yīng)用場景
異步處理
如上面提到的用戶注冊的例子:
用戶注冊(50ms)可霎,發(fā)送郵件(50ms)和短信(50ms)
串行:(150ms)用戶注冊==>發(fā)送郵件==>發(fā)送短信
并行(100ms):用戶注冊==>發(fā)送郵件
|==>發(fā)送短信
消息中間件(56ms):用戶注冊(50ms) ==>(6ms)消息中間件==>發(fā)送郵件
|==>發(fā)送短信
應(yīng)用解耦
對一些實時性要求不高的跨系統(tǒng)調(diào)用,可以考慮用消息中間件進行應(yīng)用解耦
流量的削峰
比如宴杀,系統(tǒng)舉行秒殺活動癣朗,熱門商品流量蜂擁而至 。100件商品旺罢,10萬人擠進來怎么辦旷余,10萬秒殺的操作盾致,放入消息隊列。秒殺應(yīng)用處理消息隊列中的10萬個請求中的100個荣暮,其他的打回,通知失敗罩驻。流量峰值控制在消息隊列處穗酥,秒殺應(yīng)用不會瞬間被懟死.
消息通信
可以用來做一些數(shù)據(jù)一致性對比等操作。
nsq的幾個重要組件
nsqlookupd
主要功能是服務(wù)發(fā)現(xiàn)惠遏。每個nsqd啟動時都會向配置中配置的lookupd發(fā)起register請求砾跃,lookupd維護著各各節(jié)點的topic+channel的meta信息
nsqd
nsq的核心,負責消息的存儲與分發(fā)节吮。包括topic和channel的管理抽高、producer和consumer的維護,簡單的說,真正干活的就是這個服務(wù).
nsqadmin
提供一套WEB UI,用來匯集集群的實時統(tǒng)計,提供比較全的集群管理功能和各節(jié)點的狀態(tài)信息.(這里沒有提到透绩,在后面的實際操作可以看到)