使用 ZeroMQ 實現分布式消息系統
“分布式系統是你甚至不知道的一臺計算機上的故障可以使您自己的計算機不可用。”-Leslie Lamport
隨著云計算的普及和可用性,分布式系統架構已很大程度上取代了更多的整體構建。當然,使用面向服務的架構,言外之意,就是你現在必須面對無數的以前從未存 在過的困難,如容錯性,可用性和水平縮放。復雜性的另一層有趣的問題是提供一致性的節點,這本身就是一個未完全研究明白的問題。像 Paxos 和 Raft 算法試圖提供管理數據復制的方案,而其他解決方案提供最終一致性。
構建可擴展的分布式系統,是一個不小的壯舉,但它與構建相似的實時系統比較的話,這算不上什么。分布式體系結構是一個很好理解的問題,事實上,大多數應用 對延遲的容忍度很高。有些系統有明顯的實時通信的需要,但對于開發者來說這是一個有趣的挑戰。在這篇文章中,我探討用 ZeroMQ 來以可擴展的方式處理分布式實時消息傳送,同時也考慮到最終一致性的概念。
智能傳輸層
ZeroMQ 是一個由C++編寫的高性能異步消息庫。它不是一個專用的消息 broker,而是一個可嵌入的并發框架,支持通過多種傳輸的直接和扇出短點。ZeroMQ 實現了許多不同的通訊模式,如通過 TCP 協議請求-應答(request-reply),發布-訂閱(pub-sub)和推送-拖拉(push-pull),PGM(多點廣播),進程內和進程間 的通道。或多或少在設計的時候缺乏UDP協議的支持,因為 ZeroMQ 原本構想能夠提供一個原子消息的 guaranteed-ish 傳輸。這個庫沒有實際的傳輸保證,但它卻是做出了最大的努力。然而,ZeroMQ 所能確保的是,你不會收到一個部分消息,消息會按照順序被接收。這個很重要,因為 UDP 協議的性能在損耗和擁擠的環境中才能真正體現出來。
單單就消息傳遞模式和傳輸(協議)的綜合列表,就讓 ZeroMQ 成為構建分布式應用程序的一個有吸引力的選擇,但它的特殊優勢是其可靠性,可擴展性和高吞吐量。ZeroMQ 和相關技術廣泛地應用于高頻交易應用中,這些應用一般不允許金融數據包丟失1。在2011年,CERN 進行的一項研究,在粒子加速器中比較了 CORBA,Ice, Thrift, ZeroMQ 和其他幾個協議,ZeroMQ 排名最高。
在吞吐量上,ZeroMQ 比 TCP sockets 表現更好,因為它使用一些技術:如智能消息批處理,最小化網絡堆棧遍歷,并禁用 Nagle 算法。 默認情況下(盡可能),消息都是排隊等待訂閱方,并試圖避免訂閱過慢的問題。然而,當這樣做還不能避免的話,ZeroMQ 會采用一種稱為“自殺的蝸牛”的模式。 當一個訂閱方運行緩慢并且不能趕上進入消息的速度,ZeroMQ 就會說服訂閱方來殺死自己。“慢”是由一個可配置的高水位線來決定的。這里的想法是,最好是快速的失敗使問題很快得到解決而不是潛在地允許過期的數據流入 下游。再次考慮高頻交易使用案例。
一個分布式,可伸縮和快速消息架構
ZeroMQ作為一個通訊層使用,它可以成為一個可以信服的案例。讓我們探索更深一點,看看它作為一個消息框架在一個實時系統里是如何被使用的。ZeroMQ可以直觀地使用并提供了很多語言支持,因此,我們將會更多聚焦在架構上和消息傳遞的范例上,而不是實際的代碼。
大約一年前,當我第一次研究ZeroMQ,我建立了一個框架去演示實時消息并且具備文檔同步,它被叫做Zinc。從“文檔”,在這層意義上說,任何有良好結構的和為可變數據考慮的文本文檔,電子表以及畫布(canvas)塊等等。從純學術角度來說,目標是給開發提供一個可建構的豐富框架,應對在分布式環境下的協作體驗。
這個框架實際上有兩種實現,第一個由原生的 ZeroMQ 所支持,另一個是由純 Java 實現 JeroMQ 2所支持。這專未允許使用任何傳輸層而設計的。
Zinc 圍繞幾個核心概念而設計:Endpoints(端點), ChannelListeners(通道監聽者), MessageHandlers(消息處理者) 和 Messages(消息)。在應用程序集群中,端點代表一個單一的節點,提供發送消息到其他端點和從其他端點接收消息的功能。它具有分別傳輸給同類組件和 從其他相同組件接收消息的輸出和輸入通道。
當輸入通道在一個端點被打開時,ChannelListeners (通道監聽者)本質上扮演輸入信息的守護監聽。當消息被接收時,它會被傳遞到一個線程池然后被 MessageHandler (消息處理者)處理。因此如上所述,消息都是根據他們接收的順序被異步處理的。此外,這是在我學習 Go 之前,它成為 Java 的理想代替品,因為它相當適合解決這個問題。
消息是端點間的數據交換,來自我們可以建立的文檔與文檔間片段。文檔是通過應用被結構化定義的數據,而文檔片段是根據粗的或細的需求來確定粒度所表示的部分文檔,或者增量。
Zinc是圍繞著發布-訂閱(publish-subscribe)和推送-拉取(push-pull)的消息模式建立的。一個端點將會作為集群的主機, 其他主機作為客戶端。使用這樣的架構,主機作為發布者而客戶端作為訂閱者。因此,主機發布一條消息,它就會發布到每一個訂閱的客戶端,這類似多播的方式。 相反地,客戶端一直扮演著“推送(push)”端點,而主機是“拉取(pull)”端點。這些客戶端可以把消息推送到主機的消息隊列,主機以先進先出方式 從隊列中拉取。
這種結構允許消息被傳送到整個集群----客戶端修改使其發送給主機,主機發送增量給所有的客戶端。這也就意味著引起變化的客戶端將受到一個“回應”變 量,但是在檢查消息源的時候它將被丟棄,消息源是一個能特殊標識端點的UUID。客戶端然后在必要的情況下負責數據的一致性,或許通過操作轉換或保持一個客戶端能綜合的單一來源的事實。
這種結構的一個優點就是它的規模合理,因為它的可組合性。具體來說,我們可以建構我們的集群客戶提供任意廣度和深度的樹。顯而易見,越從水平和垂直上擴大規模,越容易帶來邊緣結點之間的延遲。再加上最后的一致性,這可能會造成一些應用的問題,但是或許會被接受。
缺點是這種固有的客戶端--服務器模型引入了單點故障的特征。一種解決辦法是當主機有問題時提升其他的結點而且平衡整個樹。
再次,這個框架主要作為我的學術支持和測試驅動 ZeroMQ 的方式,雖然已經存在其他一些有趣的類似的應用。本框架支持通過推送或發布訂閱機制的多播消息的傳遞,下面是一個自主負載均衡的用例。
匹配像 Zoomkeeper, 或其他一些服務發現協議,客戶機將能夠發現主機,主機將作為負載平衡器。一旦客戶機發現了一個主機,它可以請求成為主機的群集的一部分。如果主機接受請 求,客戶端可以發送消息給主機(并且,因此,建立一個集群),同樣,從主機接收消息(和集群休息)。這使客戶機和主機成為提交工作的集群,通過這樣一個均 勻分布的方式進行處理,并且可決定是否將進一步擴展這個樹或使其處理這個工作。客戶機可以自己決定是否參與負載均衡集群,當他們成為可用的,他們大多是有 自主權。客戶可以快速運作,并且向下運作使用,比如像,Docker 容器。
ZeroMQ 在實現可靠、快速和可拓展的分布式消息上是很不錯的,但它在一個單一的機器或幾個局部網絡中,通過跨進程通信使用相同的模式所進行并行計算的同樣有用。它 還表明可以毫不費力地在每臺機器上進行多核操作。ZeroMQ 是一個不可替代的消息代理,但它可以和傳統的面向消息的中間件協調一致的工作。結合協議緩沖區和其他序列化方法,ZeroMQ 可以很容易建立非常高通量信息框架。