天天看點

架構設計:系統間通信(19)——MQ:消息協定(上)1、概述2、基本概念3、消息協定

1、概述

從本文開始,我們介紹另一類型的系統間通訊及輸:MQ消息隊列。首先我們将讨論幾種常用消息隊列協定的基本原理和工作方式,包括MQTT、XMPP、Stomp、AMQP、OpenWire等。然後在這個基礎上介紹兩款MQ産品:ActiveMQ和RabbitMQ,它們是現在業務系統中應用廣泛的消息隊列軟體。包括他們的安裝、運作、支援協定、叢集化和調用方式。

當然,在這個過程中我們還會提到其他的消息隊列協定(或者實作),例如微軟JBossMQ、MSMQ、商業化産品WebSphere MQ、Oracle進階隊列(AQ)等。我們還會讨論這些眼花缭亂的協定、軟體、程式庫之間的關系。

随後我們會花一些篇幅,讨論現在新興的消息隊列Kafka和ZeroMQ。它們的應用越來越廣泛,尤其在大資料的采集方面。最後我們将使用消息隊列搭建一個高性能的日志采集系統,作為實戰。

2、基本概念

2-1、消息

首先有三個基本概念在開篇前我們需要進行讨論:消息、消息協定、消息隊列。消息既是資訊的載體 這個描述相信各位讀者都能夠明白。為了讓消息發送者和消息接收者都能夠明白消息所承載的資訊(消息發送者需要知道如何構造消息;消息接收者需要知道如何解析消息),它們就需要按照一種統一的格式描述消息,這種統一的格式稱之為消息協定。是以,有效的消息一定具有某一種格式;而沒有格式的消息是沒有意義的。

而消息從發送者到接收者的方式也有兩種。一種我們可以稱為即時消息通訊,也就是說消息從一端發出後(消息發送者)立即就可以達到另一端(消息接收者),這種方式的具體實作就是我們已經介紹過的RPC(當然單純的http通訊也滿足這個定義);另一種方式稱為延遲消息通訊,即消息從某一端發出後,首先進入一個容器進行臨時存儲,當達到某種條件後,再由這個容器發送給另一端。 這個容器的一種具體實作就是消息隊列。

2-2、知識結構

消息隊列和已經介紹過的RPC相同的是:無論是RPC也好,消息隊列也好他們都建立在網絡IO模型基礎上(我們已經介紹過多種網絡IO模型)。先進的網絡IO模型将賦予MQ協定優異的性能表現(當然,性能也不僅僅取決于網絡IO模型)。

架構設計:系統間通信(19)——MQ:消息協定(上)1、概述2、基本概念3、消息協定

從上圖可以看到,某一種消息通訊軟體(或者叫做程式庫)的實作都建立在“協定”基礎上:RMI程式庫建立在RMI協定上(RMI協定是JAVA規範協定的一部分) ,屬于一種“即時消息通訊”;RabbitMQ和Qpid消息通訊軟體的設計依據是AMQP協定,屬于一種“延遲消息通訊”。

雖然消息協定存在“私有協定”和“開放協定”之分(是否向行業開放消息規範文檔、是否允許某個組織更改協定),雖然某一個軟體(程式庫)不一定隻支援一種協定(例如ActiveMQ實作了多種消息協定),雖然某一種協定也不一定隻有一種軟體(程式庫)實作(例如能夠支援webservice協定的程式庫就有Codehaus XFire、Apache CXF、Jboss RESTEasy等),但是這并不影響“某一種消息通訊軟體(或者叫做程式庫)的實作都建立在“協定”基礎上”的概念,反而是這個基本概念加強了。

3、消息協定

那麼要了解消息隊列,我們就應該從這些支援“延遲消息通訊”的消息協定開始讨論。這個小節我們首先為各位讀者介紹幾種使用的消息協定,他們是XMPP、Stomp和AMQP。為了承接後文我們講解的MQ軟體,這三個協定中我們又着重講解AMQP協定。

3-1、XMPP協定

3-1-1、定義

XMPP is the Extensible Messaging and Presence Protocol, a set of open technologies for instant messaging, presence, multi-party chat, voice and video calls, collaboration, lightweight middleware, content syndication, and generalized routing of XML data.

以上内容引用自XMPP官網,這個定義已經可以清楚表明XMPP協定的用途和特性。XMPP的前身是Jabber,一個開源形式組織制定的網絡即時通信協定。XMPP目前被IETF國際标準組織完成了标準化工作。

XMPP基于XML,用于IM系統的開發。國内比較流行的XMPP伺服器叫做Openfire,它使用MINA作為下層的網絡IO架構(不是MINA2是MINA1);國外用的比較多的XMPP伺服器叫做Tigase,它的官網号稱單節點可以支撐50萬使用者線上,叢集可以支援100萬使用者線上:(http://projects.tigase.org/)

Cluster with over 1mn online users . 500k online users on a single machine

當然如果讀者所在公司需要開發IM系統,除了使用現成的XMPP伺服器以外,還需要實作了XMPP協定的用戶端或者開發包(以便進行擴充開發)。您可以在XMPP官網檢視到XMPP官方推薦的開發包,各種語言的支援基本上都有:http://xmpp.org/software/libraries.html

筆者曾參與過某幾款IM系統的開發(包括自己創業的項目),總的來說XMPP協定本身是不錯的選擇,但是學習起來會耗費相當的時間,并且某些XMPP用戶端、伺服器端或者程式庫并沒有這些開發團隊宣傳的那麼穩定好用。是以如果您的公司需要進行IM系統的開發,那麼創立私有的消息協定也會是一個不錯的選擇。

3-1-2、協定通訊過程示例

為了讓各位讀者對XMPP協定有一個感性認識,這裡我們給出一個XMPP協定處理“IM使用者登入”操作的過程(XMPP的登入方式分為有使用者密碼和無使用者密碼兩種方式,這裡我們介紹無密碼登入方式)。

XMPP協定本身細節比較豐富,這裡我們隻讨論登入操作,如果讀者有興趣可以下載下傳全套的XMPP官方規範文檔進行研究(http://xmpp.org/):

架構設計:系統間通信(19)——MQ:消息協定(上)1、概述2、基本概念3、消息協定

通過上圖可以看到,XMPP協定中的xml片段。這裡出現了幾個XMPP協定中的關鍵資訊,例如:

  • stream标記:通訊流标記,是指XMPP的用戶端或者伺服器端向對方發起的通訊請求(或者響應)。通訊流并不攜帶正真的内容資訊,訓示表明用戶端和伺服器端發生了一次互動。stream的屬性包括:to、from、id、xml:lang、version等。
  • iq标記:iq标記是Info/Query的簡稱(你可以了解成查詢資訊請求),一般是一組的形式出現,由用戶端發起查詢請求,由伺服器端傳回查詢結果。由于查詢請求的類型不一樣,iq标記中可以嵌入的子标記就有很多。例如,可以嵌入bind标記,表明某個使用者和jid的綁定關系;可以嵌入多個item标記,表明查詢得到的這個使用者的好友資訊(如下)。
<iq to='[email protected]/someresource' type='result' id='roster'>  
    <query xmlns='jabber:iq:roster'>  
        <item jid='[email protected]' name='someone1'/>  
        <item jid='[email protected]' name='someone2'/>  
    </query>  
</iq>
           
  • jid标記:jid(JabberID)是XMPP協定中标示,它用來标示XMPP網絡中的各個XMPP實體(實體可以是某一個使用者、某一個伺服器、某一個聊天室),規範格式如下:
  • 還有未出現的message、presence标記:message是實體内容标記,記錄了聊天的真實内容;presence标記表示了XMPP使用者的服務狀态(離線,線上、忙碌等)。示例如下:
<message to="[email protected]/someresource" type="chat"> 
    <body>helloword。。。</body> 
</message> 
           

3-2、Stomp協定

3-2-1、定義

Stomp協定,英文全名Streaming Text Orientated Message Protocol,中文名稱為 ‘流文本定向消息協定’。是一種以純文字為載體的協定(以文本為載體的意思是它的消息格式規範中沒有類似XMPP協定那樣的xml格式要求,你可以将它看作‘半結構化資料’)。目前Stomp協定有兩個版本:V1.1和V1.2。

一個标準的Stomp協定包括以下部分:指令/資訊關鍵字、頭資訊、文本内容。如下圖所示:

架構設計:系統間通信(19)——MQ:消息協定(上)1、概述2、基本概念3、消息協定

以下為一段簡單的協定資訊示例:

CONNECT
accept-version:1.2
someparam1:value1
someparam2:value2

this is conntecon ^@
           

上面的示例中,我們使用了Stomp協定的CONNECT指令,它的意思為連接配接到Stomp代理端,并且攜帶了要求代理端的版本資訊和兩個自定義的K-V資訊(請注意’^@’符号,STOMP協定中用它來表示NULL)。

Stomp協定中有兩個重要的角色:STOMP用戶端與任意STOMP消息代理(Broker)。如下圖所示:

架構設計:系統間通信(19)——MQ:消息協定(上)1、概述2、基本概念3、消息協定

看了上面的示意圖後有的讀者可能會問:為什麼稱為Stomp消息代理,而不稱為Stomp消息服務?因為Stomp Broker隻是負責接受和存儲用戶端發來的消息、隻是按照用戶端要求的路徑轉發消息,隻是管理用戶端連接配接和訂閱:它并不負責根據消息内容做任何業務處理。是以将它稱為消息代理端更貼切。

由于Stomp協定的結構如此簡單,以至于任何了解Stomp協定指令格式的技術人員都可以開發Stomp的代理端或者Stomp的用戶端,并将自己滿足Stomp協定的系統輕松接入另一個同樣滿足Stomp協定的第三方系統(例如activeMQ)。

3-2-2、基本指令/傳回資訊

和介紹XMPP協定的方式類似,為了讓讀者對Stomp協定有進一步的認識,本小節我們介紹Stomp協定的基本指令和代理端傳回的資訊種類,并且列舉一些執行個體進行使用講解。

在Stomp協定中,主要有以下指令/傳回資訊(有的文章中也稱一個完整的資訊為幀)。這些指令/傳回資訊構成了Stomp協定的主體,并能夠支援您的Stomp用戶端和Stomp代理端完成連接配接、發送、訂閱、事務、響應的整個操作過程。這些指令/傳回是:

  • CONNECT/STOMP指令: 用戶端通過使用CONNECT指令,連接配接到Stomp代理端。如果使用STOMP指令,那麼Stomp代理端的版本必須是1.2。
  • CONNECTED資訊:當Stomp代理端收到用戶端發送來的Connect指令并且處理成功後,将向這個用戶端傳回CONNECTED狀态資訊;如果這個過程中出現任何問題,還可能傳回ERROR資訊
  • SEND 發送指令:用戶端使用SEND指令,向某個指定位置(代理端上的一個虛拟路徑)發送内容。這樣在這個路徑上訂閱了消息事件的其它用戶端,将能夠收到這個消息。
  • SUBSCRIBE 訂閱指令:用戶端使用SUBSCRIBE訂閱指令,向Stomp服務代理訂閱某一個虛拟路徑上的監聽。這樣當其它用戶端使用SEND指令發送内容到這個路徑上時,這個用戶端就可以收到這個消息。在使用SUBSCRIBE時,有一個重要的ACK屬性。這個ACK屬性說明了Stomp服務代理端發送給這個用戶端的消息是否需要收到一個ACK指令,才認為這個消息處理成功了。如下所示:
SUBSCRIBE
id:XXXXXXXXX
destination:/test
ack:client

^@
           

以上SUBSCRIBE指令資訊說明,用戶端訂閱的虛拟位置是test。且指令資訊中ack屬性為client,說明當用戶端收到消息時,必須向代理端發送ack指令,代理端才認為這個消息處理成功了(ack的值隻有三種:auto(預設)、client和client-individual)。

  • UNSUBSCRIBE 退訂指令:用戶端使用這個指令,取消對某個路徑上消息事件的監聽。如果用戶端給出的路徑之前就沒有被這個用戶端訂閱,那麼這個指令執行無效。
  • MESSAGE 資訊:當用戶端在某個訂閱的位置收到消息時,這個消息将通過MESSAGE關鍵字進行描述。類似以下資訊就是從代理端拿到的消息描述:
MESSAGE
redelivered:true
message-id:ID:localhost---::-::
destination:/test
timestamp:
expires:
priority:


           
  • BEGIN 開始事務指令: Stomp協定支援事務模式,在這種模式下,使用Send指令從某個用戶端發出的消息,在沒有使用COMMIT正式送出前,這些消息是不會真正發送給Stomp代理端的。BEGIN指令就是用于開啟事務。注意,一個事務中可以有一條消息,也可以有多條消息。
  • COMMIT 送出指令: 當完成事務中的資訊定義後,使用該指令送出事務。隻有使用COMMIT指令後,在某一個事務中的一條或者多條消息才會進入Stomp代理端的隊列(訂閱了事件的其它用戶端才能收到這些消息)。
  • ABORT 取消/終止事務指令:很明顯,這個指令用于取消/終止目前還沒有執行COMMIT指令的事務。
  • ACK 确認指令:當用戶端使用SUBSCRIBE指令進行訂閱時,如果在SUBSCRIBE指令中制定ack屬性為client,那麼這個用戶端在收到某條消息(id為XXXX)後,必須向Stomp代理端發送ACK指令,這樣代理端才會認為消息處理成功了;如果Stomp用戶端在斷開連接配接之前都沒有發送ACK指令,那麼Stomp代理端将在這個用戶端斷開連接配接後,将這條消息發送給其它用戶端。
ACK
id:MESSAGE ID

^@
           

請注意head部分的id屬性,傳遞的id屬性是之前收到的MESSAGE資訊的id标示。

  • NACK 不确認指令:同樣是以上的SUBSCRIBE指令的狀态下,如果這時Stomp用戶端向Stomp代理端發送NACK資訊,證明這條消息在這個用戶端處理失敗。Stomp代理端将會把這條消息發送給另一個用戶端(無論目前的用戶端是否斷開連接配接)。
  • DISCONNECT 斷開指令:這個指令将斷開Stomp用戶端與Stomp代理端的連接配接。
    (接下文)
               

繼續閱讀