今天發現大家對NSQ等組件的集群原理還不了解,所以這遍文章對一些常見組件的集群原理做一個匯總整理。我會不定期更新,增加一些新的組件或修改錯誤。
1 NSQ

NSQ集群比較簡單,主要包含4個部分,一是生產者(圖上沒畫)、二是nsq實例(nsqd)、三是服務發現nsqlookupd、四是消費者(Comsumer)。
這4個部分的工作方式如下:
- 生產者
- 生產者需要指定將消息寫入哪個實例nsqd
- 當一個nsqd實例宕機時,生產者可以選擇將消息寫到其他的實例
- 生產者也可以將同一條消息寫入兩個nsqd(HA)
- nsqd
- 消息不會在nsqd之間傳遞,生產者把消息寫到哪個nsqd就只能在該nsqd消費
- 不同的nsqd可以接收同一個生產者的相同的消息,參考生產者的說明
- nsqd會將自己的服務信息廣播給集群內的nsqlookupd
- 服務發現nsqlookupd
- nsqlookupd與集群內的所有nsqd建立連接,檢測實例的狀態,並接收實例廣播過來的服務注冊
- nsqlookupd接收消費者客戶端的服務發現請求,將對應的實例返回給消費者(這里可以是多個實例)
- 一個集群可以有多個nsqlookupd
- 消費者comsumer
- 消費者可以與nsqd直連,但為了防止單點故障,不應該有這種固定的關系
- 官方推薦走服務發現nsqlookupd,參考生產者的說明,當一個nsqd宕機時,生產者可以將消息寫入其他的nsqd,或者為了HA,生產者可以雙寫
針對上述特性說明,我們可以得出以下結論或支撐系統高可用的方案:
- 當一個nsqd宕機時,這台機上尚未消費且尚未落盤的消息會丟失;集群本身不提供副本和分片
- 鑒於上一條,對於冪等且不可丟失的消息,生產者可以選擇雙寫,一條消息同時寫兩個實例
- 當正在寫的nsqd宕機時,生產者可以選擇寫入其他的nsqd。前提是消費者通過nsqlookupd完成服務發現,及時感知集群的變化;或者消費者可以同時連上集群內所有的Nsqd實例
- 生產者可以做一定的負載均衡,將消息分散生產到不同的nsqd中
2 Elasticsearch
2.1 拓撲結構
ES集群涉及數據遷移、負載均衡和HA等,所以會比NSQ集群更復雜些,下面我們通過一張圖來簡單介紹下:

ES的節點可以通過兩組配置項來決定每個節點類型,即"node.master: true/false"和"node.data: true/false",具體類型如下表:
| node.master | node.data | 是否參與選主 | 是否保存數據 | 是否參與計算 | 對應上圖節點 |
|---|---|---|---|---|---|
| true | true | 是 | 是 | 是 | Node A/B |
| true | false | 是 | 否 | 是 | Node C |
| false | true | 否 | 是 | 是 | Node D |
| false | false | 否 | 否 | 是 | Node E |
特殊的,像Node E這種又不參與選主,又不保存數據的節點,也是有用的,它可以用來處理用戶請求。所有的節點都可以接收並處理用戶請求。具體邏輯見后續章節。
ES集群還支持其他的節點類型,在此不展開討論。
集群內所有節點理論上兩兩相連(網絡異常時允許部分節點之間通訊中斷,保證最終整個集群是拓撲連續的即可)。每個節點都會保存與其相連的其他節點的信息,用於選主和路由。節點之間通過Gossip謠言傳播算法實現數據交換和最終一致性,具體的原理細節這里就不討論了。
2.2 數據分布
ES支持數據分片和分片副本:
- 主分片與副本分片一般分布在不同的節點(HA高可用)
- 不同的主分片可以在同一個節點
- 數據更新只能發生在主分片
- 查詢請求可以在主副分片
- 當主分片丟失時,會自動選擇一個最新的副本分片作為主
分片分布在哪個節點上是不固定的,隨着集群的數據變化和節點的增加刪除,分片會在不同節點之間移動。這個由master主節點來負責協調。具體參見“master主節點”章節。
2.3 數據訪問
集群所有節點均可接收用戶請求。
2.1章節提到每個節點會保存其他節點的信息,所以當一個用戶請求到來時,當前節點負責解析用戶請求,並從本地節點列表中選擇有相關分片的節點(數據分片往往被打散到不同的節點)將請求轉發過去。
對於寫請求:
- Node A收到用戶請求,解析后確定需要寫Node B所保存的主分片,則Node A將請求轉發給Node B
- Node B完成主分片的寫入(注意,只有主分片能寫,副本分片從主分片同步數據)
- Node B將數據同步給集群內其他副本分片,待所有分片均返回后,響應Node A寫入成功
- Node A響應用戶請求
對於讀請求:
- Node A收到用戶請求,解析后確認涉及哪些分片,從本地節點列表中找出有相關分片的節點將請求轉發過去
- 各分片完成查詢,將數據返回給Node A
- Node A將數據運算匯總后,響應用戶請求
2.4 master主節點
主節點是由集群自動選舉出來的。
集群內有且只能有一個master主節點,它負責集群的協調工作,比如新節點加入、分片的轉移等。
根據2.1章節的描述,只有node.master配置為true的節點才有可能成為主節點。
集群選舉
- 選舉由node.master配置為true的節點發起,當某個候選節點發現當前集群缺少master的時候,會主動發起選舉(節點之間會相互通過ping交換信息,包括主節點信息,如果超過半數的節點都連不上主節點時,就認為沒有主節點)
- 選舉的方式是集群所有節點投票,投票規則是每個節點都選擇本地列表中version最高的(如果存在並列最高,則選id最小的那個節點),節點通過join指令投票。
- 被選節點通過收集join的次數,超過集群節點總數的一半+1的節點選自己為主節點時,選舉即成功。
- 否則超時開啟下一輪選舉,直到集群選出master。
關於腦裂
當集群存在兩個或以上的master時,我們稱之為腦裂。這種情況會導致集群的一致性受到威脅。
略有遺憾的是ES存在小概率的腦裂問題。比如當Node A發起選舉,Node X投了一票;但選舉遲遲未成功,接着又有個節點發起了一輪投票,剛好這時Node X發現Node B的版本更高,又投了一票給Node B。於是Node X投出了兩票,條件契合的情況下,Node A/B可能同時認為自己被選上。這種情況可以通過引入選舉周期來解決,同一周期一個節點只投一票,最終選擇周期最新的節點為主。
特別注意:只有兩個節點的集群,一旦通訊異常,master就選不出來了,因為拿不到總節點數/2 + 1的票數
類似的,當集群有超過半數的節點宕機,集群將變的不可用。
2.5 節點發現和退出
當有節點需要加入集群時,通過給它配置discovery.zen.ping.unicast.hosts: [xx.xx.xx.xx, yy.yy.yy.yy]參數來完成自動發現,加入集群。原理是節點會向上述hosts列表發送請求,尋找master,然后join master。該hosts建議配置成所有的主候選節點。
節點需要退出時,先標記待退出狀態,待master將分片全部移走時,才能退出集群。
