1、概述
從本文開始,我們介紹另一型別的系統間通訊及輸:MQ訊息佇列。首先我們將討論幾種常用訊息佇列協議的基本原理和工作方式,包括MQTT、XMPP、Stomp、AMQP、OpenWire等。然後在這個基礎上介紹兩款MQ產品:ActiveMQ和RabbitMQ,它們是現在業務系統中應用廣泛的訊息佇列軟體。包括他們的安裝、執行、支援協議、叢集化和呼叫方式。
2、基本概念
2-1、訊息
首先有三個基本概念在開篇前我們需要進行討論:訊息、訊息協議、訊息佇列。訊息既是資訊的載體 這個描述相信各位讀者都能夠明白。為了讓訊息傳送者和訊息接收者都能夠明白訊息所承載的資訊(訊息傳送者需要知道如何構造訊息;訊息接收者需要知道如何解析訊息),它們就需要按照一種統一的格式描述訊息,這種統一的格式稱之為訊息協議。所以,有效的訊息一定具有某一種格式;而沒有格式的訊息是沒有意義的。
而訊息從傳送者到接收者的方式也有兩種。一種我們可以稱為即時訊息通訊,也就是說訊息從一端發出後(訊息傳送者)立即就可以達到另一端(訊息接收者),這種方式的具體實現就是我們已經介紹過的RPC(當然單純的http通訊也滿足這個定義);另一種方式稱為延遲訊息通訊,即訊息從某一端發出後,首先進入一個容器進行臨時儲存,當達到某種條件後,再由這個容器傳送給另一端。 這個容器的一種具體實現就是訊息佇列。
2-2、知識結構
訊息佇列和已經介紹過的RPC相同的是:無論是RPC也好,訊息佇列也好他們都建立在網路IO模型基礎上(我們已經介紹過多種網路IO模型)。先進的網路IO模型將賦予MQ協議優異的效能表現(當然,效能也不僅僅取決於網路IO模型)。
從上圖可以看到,某一種訊息通訊軟體(或者叫做程式庫)的實現都建立在“協議”基礎上:RMI程式庫建立在RMI協議上(RMI協議是JAVA規範協議的一部分) ,屬於一種“即時訊息通訊”;RabbitMQ和Qpid訊息通訊軟體的設計依據是AMQP協議,屬於一種“延遲訊息通訊”。
雖然訊息協議存在“私有協議”和“開放協議”之分(是否向行業開放訊息規範文件、是否允許某個組織更改協議),雖然某一個軟體(程式庫)不一定只支援一種協議(例如ActiveMQ實現了多種訊息協議),雖然某一種協議也不一定只有一種軟體(程式庫)實現(例如能夠支援webservice協議的程式庫就有Codehaus XFire、Apache CXF、Jboss RESTEasy等),但是這並不影響“某一種訊息通訊軟體(或者叫做程式庫)的實現都建立在“協議”基礎上”的概念,反而是這個基本概念加強了。
3、訊息協議
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協議中的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實體(實體可以是某一個使用者、某一個伺服器、某一個聊天室),規範格式如下:
jid = [ node "@" ] domain [ "/" resource ]
1
還有未出現的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協議包括以下部分:命令/資訊關鍵字、頭資訊、文字內容。如下圖所示:
以下為一段簡單的協議資訊示例:
CONNECT
accept-version:1.2
someparam1:value1
someparam2:value2
this is conntecon ^@
上面的示例中,我們使用了Stomp協議的CONNECT命令,它的意思為連線到Stomp代理端,並且攜帶了要求代理端的版本資訊和兩個自定義的K-V資訊(請注意’^@’符號,STOMP協議中用它來表示NULL)。
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-34450-1457321490460-4:24:-1:1:1
destination:/test
timestamp:1457331607873
expires:0
priority:4
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代理端的連線。
(接下文)