AMQP-0-9-1中文規範

這個殺手不太愣發表於2020-10-19

 

1 概述

1.1 本文件的目標

此文件定義了一個網路協議-高階訊息佇列協議(AMQP), 它使一致的客戶端程式可以與一致的訊息中介軟體伺服器進行通訊.

我們面對的是這個領域有經驗的技術讀者,同時還提供了足夠的規範和指南.技術工程師可以根據這些文件,在任何硬體平臺上使用各種程式語言來構建遵從該協議的解決方案。

1.2 摘要

1.2.1 為什麼使用AMQP?

AMQP在一致性客戶端和訊息中介軟體(也稱為"brokers")之間建立了全功能的互操作.

我們的目標是實現一種在全行業廣泛使用的標準訊息中介軟體技術,以降低企業和系統整合的開銷,並且向大眾提供工業級的整合服務。

我們的宗旨是通過AMQP,讓訊息中介軟體的能力最終被網路本身所具有,並且通過訊息中介軟體的廣泛使用發展出一系列有用的應用程式.

1.2.2 AMQP範圍

為了完全實現訊息中介軟體的互操作性,需要充分定義網路協議和訊息代理服務的功能語義。

因此,AMQP通過如下來定義了網路協議(AMQP是協議!)和服務端服務:

  1. 一套確定的訊息交換功能,也就是“高階訊息交換協議模型”。AMQP模型包括一套用於路由和儲存訊息的功能模組,以及一套在這些模組之間交換訊息的規則。
  2. 一個網路線級協議(資料傳輸格式),AMQP促使客戶端可使用AMQ模型來與伺服器互動.

可以只實現AMQP協議規範中的的部分語義,但是我們相信這些明確的語義有助於理解這個協議。

1.2.3 高階訊息佇列模型(AMQ 模型)

我們需要明確定義伺服器語義,因為所有伺服器實現都應該與這些語義保持一致性,否則就無法進行互操作. 因此AMQ 模型定義了一系列模組化元件和標準規則來進行協作. 有三種型別的元件可以連線伺服器處理鏈來建立預期的功能:

1. "交換器(exchange)" :接收來自發布者應用程式的訊息,並基於任意條件(通常是訊息屬性和內容)將這些訊息路由到訊息佇列(message queues).

2."訊息佇列(message queue)":儲存訊息直到它們可以被消費客戶端應用程式(或多執行緒應用程式)安全處理.

3."繫結(binding)":定義了訊息佇列與交換器之間的關係,並提供了訊息路由條件.

使用這些模型我們可以很容易地模擬經典的儲存轉發佇列和麵向訊息中介軟體的主題訂閱概念. 我們還可以表示更為複雜的概念,例如:基於內容的路由,工作負載分配和按需訊息佇列。

大致上講, AMQP 伺服器類似與郵件伺服器, 每個交換器都扮演了訊息傳送代理,每個訊息佇列都作為郵箱,而繫結則定義了每個傳送代理中的路由表.釋出者傳送訊息給獨立的傳送代理,然後傳送代理再路由訊息到郵箱中.消費者從郵箱中收取訊息. 相比較而言,在AMQP之前的許多中介軟體系統中,釋出者直接傳送訊息到獨立收件箱(在儲存轉發佇列的情況下),或者釋出到郵件列表中 (在主題訂閱的情況下).

區別就在於使用者可以控制訊息佇列和交換器之間的繫結規則,這可以做很多有趣的事情,比如定義一條規則:“將所有包含這樣訊息頭的訊息都複製一份再傳送到訊息佇列中”。

AMQ模型是基於下面的需求來驅動設計的:

1. 支援與主要訊息產品相媲美的語義。.

2. 提供與主要訊息產品相媲美的效能水平.

3. 允許通過應用程式使用伺服器特定語義來程式設計.

4.靈活性,可擴充套件性,簡單性

1.2.4 高階訊息佇列協議(AMQP)

AMQP協議是具有現代特徵的二進位制協議: 它是多通道的, 協商的,非同步的,安全的,便攜的,自然的,高效的。 AMQP通常劃分成兩層:
 
 

功能層( functional layer)定義了一系列命令(分成功能獨立的邏輯類),可為應用程式做有用工作。

傳輸層,將這些方法從應用程式應用搬到伺服器並返回,它同時會處理通道複用,幀同步, 內容編碼,心跳檢測, 以及資料表示和錯誤處理.

在不改變協議可視功能的前提下,可使用任意的傳輸協議來替換傳輸層.也可以將同一個傳輸層用於不同的高階協議.

AMQ模型是基於下面需求驅動的:

 為了保證一致性實現之間的互操作性。.

 提供服務質量的顯式控制.

 一貫的、明確的命名.

 通過協議允許伺服器完全可配置.

 使用命令標記法可輕易地對映到應用程式級別API.

 一目瞭然,每個操作都在做自己的事情。

AMQP傳輸層的設計是由以下需求驅動的(沒有特定順序):

 堅實的,使用二進位制編碼來打包和解包.

 處理任何大小的訊息,沒有明顯限制(實際上有限制).

 在單個連線上攜帶攜帶多個通道.

 長時間生存的,沒有顯著的內建限制。

 允許非同步命令在管道中排隊.

 易於擴充套件,以處理新的更新的需求。

 與未來版本相容。

 可修復,使用一個強大的斷言模型。

 對程式語言保持中立。

 適合程式碼擴充套件過程。

1.2.5 規模化部署
 

AMQP範圍涵蓋不同等級規模,大致如下:

 開發/臨時使用: 1臺伺服器, 1個使用者, 10個訊息佇列,每秒 1個訊息.

 產品應用程式: 2伺服器, 10-100個使用者, 10-50個訊息佇列,每秒10個訊息(36Kmessages/hour).

 部門任務關鍵應用: 4 臺伺服器, 100-500個使用者, 50-100 個訊息佇列, 每秒100個訊息(360K/hour).

區域任務關鍵應用: 16 臺伺服器, 500-2,000 個使用者, 100-500 個訊息佇列和主題,

每秒1000個訊息(3.6M/hour).

 全球任務關鍵應用: 64 臺伺服器, 2K-10K 個使用者, 500-1000 個訊息佇列和主題,

每秒10,000 個訊息(36M/hour).

 市場資料(交易): 200 臺伺服器, 5K 個使用者, 10K 主題, 第秒100K個訊息(360M/hour).

規模越大,訊息傳輸延遲就越重要.例如,市場資料變化相當快. 實現可以依據不同的服務質量和管理能力不區分對待,但必須與本規範相容.

1.2.6 功能範圍

我們要支援多種訊息傳遞架構:

 使用多個writers和一個reader來儲存轉發.

 使用多個writers和多個readers來分散工作負載.

 使用多個writers和多個readers來發布訂閱

 使用多個writers和多個readers來基於內容路由.

 使用多個writers和多個readers來進行佇列檔案傳輸.

 在兩個節點之間進行點對點連線.

 使用多個源和多個readers來分佈市場資料.

1.3 文件組織

文件分成五個章節, 其中大部分設計為可按你的興趣來獨立閱讀:

1. "概述" (本章). 讀本章來了解介紹.

2. "總體架構",這章中我們描述了架構和AMQP總體設計. 本章的目的是幫助系統架構師瞭解AMQP如何工作的.

3. "功能說明", 這章中我們定義了應用程式如何與AMQP一起協同工作. 本章先講述了一個可讀性討論,其次是每個協議命令的詳細規範,以作為實施者參考。在閱讀本章之前,你應該閱讀總體構架.

4. "技術說明",這章中我們定義了AMQP傳輸層是如何工作的. 這章由簡短討論和線路結構的詳細說明組成.你如果想線路級協議是如何工作的,可以閱讀本章.
 

1.4 約定

1.4.1 實現者指導方針

 我們使用了IETF RFC 2119中定義的術語 MUST, MUST NOT, SHOULD, SHOULD NOT, 和 MAY.

 當討論需要AMQP伺服器的特定行為時,我們使用術語"the server".

 當討論需要AMQP客戶端的特定行為時,我們使用術語"the client".

 我們使用術語"the peer" 來代表伺服器或客戶端.

 如果沒有指示,數字值是十進位制的.

 Protocol常量是以大寫名稱表示. AMQP實現應當在定義,原始碼,以及文件中使用這些名稱.

 屬性名稱,方法名稱,以及幀欄位是以小寫名稱來表示. AMQP實現應當在定義,原始碼,以及文件中使用這些名稱.

 AMQP中的字串是區分大小寫的.例如, "amq.Direct"與"amq.direct”是兩個不同的交換器.

1.4.2 版本編號方式

AMQP版本使用兩個或三個數字進行表示 – 主版本號,次版本號以及可選的修訂版本號.為了方便,版本號表示為:major-minor[-revision] 或major.minor[.revision]:

 官方說明中,major, minor, 和revision均支援0到99的數字.

 Major, minor, 和 revision 中100及其以上的數字保留用於內部測試和開發.

 版本號表明了語法和語義互操作性。

 版本 0-9-1 表示 major = 0, minor = 9, revision = 1.

 1.1版本表示為major = 1, minor = 1, revision = 0. AMQP/1.1等價於AMQP/1.1.0或AMQP/1-1-0.
 

1.4.3 技術術語

這些術語在本文件的上下文中有特殊的意義:

 AMQP 命令架構(AMQP command architecture): 用於在AMQ模型架構上執行操作的線級協議命令.

 AMQ模組架構(AMQ model architecture): 表示關鍵實體和語義的邏輯框架,它必須對相容AMQP實現的伺服器可用, 使得伺服器的狀態可以通過客戶端按本規範中定義的語義來實現.

 Connection: 網路連線,如.一個TCP/IP socket連線.

 Channel: 兩個AMQP節點之間雙向通訊流. 通道是多路複用的,因此單個網路連線可以支撐多個通道

 Client: AMQP連線或通道的發起人. AMQP不是對稱的.客戶端生產和消費訊息,而伺服器端入列和路由訊息.

 Server:接受客戶端連線,實現AMQP訊息佇列和路由功能的過程.也稱為"broker".

 Peer: AMQP連線中任意一方.AMQP連線明確包含兩個節點(一個是client, 一個是server).

 Frame: 一個正式定義的連線資料包。框架總是連線寫和讀資料包-作為連線上的一個單元。

 Protocol class: 用於處理特定型別功能的AMQP命令集合 (也稱為方法).

 Method: 用於在節點之間傳遞特定型別的AMQP命令幀.

 Content: 伺服器和應用程式之間傳送的資料.這個術語是“message”的同義詞。

 Content header:描述內容屬性特定型別幀.

 Content body: 包含原始應用程式資料的特定型別幀.內容體幀完全不透明-伺服器不以任何方式檢查或修改其body內容.

 Message: 與Content同義.

 Exchange: 伺服器中接收來自生產者應用程式的訊息的實體,並可選擇將這些訊息路由到伺服器中的訊息佇列.

 Exchange type: 交換器特定模型的演算法和實現.而"交換器例項"是伺服器中用於接收和路由訊息的實體.

 Message queue: 儲存訊息並將它們轉發給消費者應用程式的命名實體。

 Binding: 用於建立訊息佇列和交換器繫結關係的實現.

 Routing key: 一個虛擬地址,虛擬機器可用它來確定如何路由一個特定訊息.

 Durable: 伺服器資源可在伺服器重啟時恢復.

 Transient: 伺服器資源或訊息會在伺服器重啟後擦除或重置.

 Persistent: 伺服器儲存在可靠的磁碟儲存上的訊息,並且在伺服器重新啟動後不丟失.

 Consumer: 從訊息佇列中請求訊息的客戶端應用程式.

 Producer: 釋出訊息到交換器中的客戶端程式.

 Virtual host: 交換器,訊息佇列以及相關物件的集合. 虛擬主機是共享同一個身份驗證和加密環境的獨立伺服器域。

 Assertion: 一個必須為true且可繼續執行的條件.

 Exception: 一個失敗的斷言,可通過Channel或Connection來關閉.

在AMQP中這些術語沒有特殊意義:

 Topic: 通常是分發訊息的一種手段; AMQP使用一個或多個交換器型別來實現topics.

 Subscription:通常是從topics中接收資料的請求, AMQP以訊息佇列和繫結的方式來實現訂閱.

 Service: 通常與server一個含義. AMQP使用“server”來遵循IETF標準命名。

 Broker: 通常與server一個含義。AMQP使用術語“client”和“server"來遵循IETF標準術語。

 Router: 有時用來描述交換器的動作.交換器也可以作為訊息終點, "router" 在網路領域中有特殊意義,因此AMQP不會使用它.
 

2 總體架構

2.1 AMQ 模組架構

本節將講解了伺服器語法,須標準化來保證AMQP實現之間可互操作的語義。

2.1.1 主要實體

下面的圖顯示了整體AMQ模型:

 
 

我們可以總結一下中介軟體伺服器是什麼:它是一個接受訊息的資料伺服器,並主要做兩件事情,依據條件將訊息路由給不同的消費者,當消費者消費速度不夠快時,它會把訊息快取在記憶體或磁碟上.

在AMQP之前的伺服器中,它們會通過實現了特定型別路由和快取的龐大引擎來完成. AMQ模組使用較小的模組結合更多樣和穩健的方案來實現. 它把這些任務分成了兩個不同角色:

 交換器, 它接受來自生產者的訊息並將它們路由到訊息佇列.

 訊息佇列, 它儲存訊息訊息並把它們轉發給消費者應用程式.

在交換器和訊息佇列之間有一個明顯的介面,稱為繫結(binding),我們隨後會進行講解.

AMQP提供了執行時程式語義,主要有兩方面:

1. 執行時通過該協議可建立任意的交換器和訊息佇列型別的能力(有些是在標準中定義的,但可以新增其他作為伺服器擴充套件)。

2. 執行時通過協議包裝交換器和訊息佇列來建立任何需要的訊息處理系統的能力.

2.1.1.1 訊息佇列(Message Queue)

訊息佇列用於在記憶體或磁碟上儲存訊息, 並將它們依次投遞給一個或多個消費者應用程式.訊息佇列是訊息儲存和分發的實體. 每個訊息佇列是完全獨立的,且是一個相當聰明的物件。

訊息佇列有多個屬性:私有的或共享的, 持久的或臨時的,客戶端命名的或伺服器端命名的等等.

通過選擇希望的屬性,我們可以使用訊息佇列來實現傳統的中介軟體實體,如:

 共享儲存轉發佇列:它可以持有訊息,並以round-robin方式在消費者之間分發訊息.儲存轉發佇列通常是在多個消費者之間是持久化的.

 私有回覆佇列:它可以持有訊息,並把訊息轉發給單個消費者. 回覆佇列通常是臨時的,服務端命名的,且對於某個消費者來說是私有的.

 私有訂閱佇列:它可持有來自不同訂閱源的訊息,並將它們轉發給單個消費者.

訂閱佇列通常是臨時的,伺服器端命名的,並對於某個消費者來說是私有的.

AMQP沒有定義這些類別:這些只是如何使用訊息佇列的例子.建立如持久化,共享訂閱佇列的新實體沒什麼意義.
 

2.1.1.2 交換器(Exchange)

交換器接收來自生產者應用程式的訊息,並將它們按照事先約定的規則路由到訊息佇列中.

這些預先約定的規則或條件稱為繫結. 交換器會與路由引擎匹配.

也就是說,他們會檢查訊息,並使用他們的繫結表來決定如何將這些訊息轉發到訊息佇列或其他交換器中。交換器永遠不會儲存資訊。“交換器”一詞是指一類演算法或演算法例項。

更確切的說,我們談到了交換器型別和交換器例項.

AMQP定義了許多交換器型別,它們覆蓋了常見訊息路由分發的基礎型別. AMQP伺服器提供了這些交換器的預設例項.使用AMQP的應用程式可額外建立它們自己的交換器例項.交換器型別是命名的,這樣建立交換器的應用程式就可以告知伺服器他們使用的交換器型別.

交換器實現也可以是命名的,這樣應用程式可指定如何繫結佇列來發布訊息.

交換器還可以做更多的訊息路由.它們可作為伺服器內的智慧路由代理,按需接受訊息和生產訊息. 交換器概念的目的是定義一套模型或標準,使得可以合理地擴充套件AMQP伺服器,因為可擴充套件性會對互操作產生影響.

2.1.1.3 路由鍵 (Routing Key)

在一般情況下,交換器會檢查訊息的屬性,如,它的header欄位,body內容,並使用這些和其他來源中的資料來決定如何訊息路由。

在大多數簡單情況下,交換器會檢查某個單一的鍵欄位,我們稱之為“路由鍵”。

路由鍵是一個虛擬地址,該虛擬地址可用來決定如何路由訊息。

對於點對點的路由,路由鍵通常是訊息佇列的名稱。

對於主題釋出訂閱路由,路由鍵通常是topic層次結構值。

在更復雜的情況下,路由鍵可以是訊息header欄位和/或訊息內容的組合體。

2.1.1.4 類比電子郵件

如果我們做過一個類似的電子郵件系統,那我們會看到AMQP概念就不再是激進的:

一個AMQP訊息類似於電子郵件;

 訊息佇列就像一個郵箱;

 消費者是一個可讀取和刪除電子郵件的郵件客戶端;

交換器類似於一個MTA (郵件傳輸代理),它會檢查電子郵件,並基於路由鍵和表來決定如何將電子郵件傳送到一個或多個郵箱中;

 路由鍵對應於電子郵件中To:,Cc: ,Bcc: 地址, 不包含服務端資訊(路由完全是AMQP伺服器內部的行為);

 每個交換器例項類似於單獨的MTA過程,用於處理一些電子郵件子域名或特定型別的電子郵件傳輸;

 繫結類似於MTA路由表中的實體.

AMQP的強大來自於建立佇列(郵箱),交換器(MTA過程),和繫結(路由實體)的能力,並可在執行時,將這些連結在一起,這遠遠超出了簡單的"to" 地址到郵箱名稱的對映.

我們不應該把email與AMQP類比得太遠:它們之間有根本上的區別。AMQP面臨的挑戰是如何來儲存和轉發伺服器中的訊息,SMTP(IETF RFC 821)稱其為“自治系統”。而在電子郵件中的挑戰是自治系統之間如何路由訊息。

在一個伺服器內路由和在多個伺服器之間路由,方式是不同的,應該有不同的解決方案.

在多個AMQP伺服器(擁有不同實體)之間路由時,必須明確建立不同的橋樑, 為達到在多個獨立實體之間傳送訊息的目的,一個AMQP伺服器必須作為另一個AMQP伺服器的客戶端.這種工作方式很適合需要使用AMQP的業務型別,因為這些橋樑可以為業務流程,合同義務和安全問題打下基礎.

2.1.2 訊息流(Message Flow)

下面的圖展示了通過AMQ模組伺服器的訊息流:

2.1.2.1 訊息生命週期

一個AMQP訊息由一組屬性和不透明的內容組成。一個新訊息是由生產者應用程式通過使用AMQP client API來建立的.生產者將“內容”附著在訊息中,並對其設定一些訊息“屬性”。生產者使用路由資訊來標記訊息,其表面上類似於地址,但幾乎可以建立任何模式。然後,生產者將訊息傳送到伺服器上的交換器中。

當訊息到達伺服器時,交換器通常會將訊息路由到一級存在於伺服器上的訊息佇列中.如果訊息不能路由,交換器會默默地丟棄或者將其返回給生產者. 生產者可以選擇如何來處理未路由訊息.

單個訊息可存在於多個訊息佇列. 伺服器可以不同方式進行處理,如通過拷貝訊息或通過引用計數器等. 這不影響互操作性。然而,當一個訊息被路由到多個訊息佇列時,它在每個訊息佇列上都是一樣的。沒有獨特的識別符號來區分不同的副本。

當訊息到達訊息佇列時,訊息佇列會通過AMQP,立即嘗試將訊息傳遞給消費者應用程式.如果不行,訊息佇列會儲存訊息(按釋出者要求儲存在記憶體或磁碟中),並等待消費者準備好.如果沒有消費者,訊息佇列通過AMQP將訊息返回給生產者(再次地,如果生產者對此有要求的話).

當訊息佇列把訊息投遞給消費者後,它會從內部緩衝區中刪除訊息.這有可能立即發生,也有可能在消費者應答它已成功處理之後刪除.消費者可選擇如何以及何時來應答訊息.同樣地, 消費者也可以拒絕訊息(一個否定應答).

生產者訊息和消費者應答可以組成事務. 當一個應用程式同時扮演兩種角色時,通常它會做混合工作:傳送訊息和傳送應答,然後提交或回滾事務.

從伺服器投遞訊息給消費者,這個過程不是事務的,它只能通過訊息應答來處理.

2.1.2.2 生產者能看到什麼

通過與電子郵件系統的類比,我們可以看到生產者不能直接向訊息佇列傳送訊息.

如果允許這樣做,將會破壞AMQ模組中的抽象. 這就像允許電子郵件繞過M他的路由表,直接傳送到郵箱中一樣. 這會導致在中間過程中不能插入過濾處理,例如,垃圾郵件檢測.

AMQ 模組也使用了與電子郵件系統一樣的準則:所有訊息都發向單一的某個點:交換器或MTA,然後這個點根據規則和隱藏在傳送者中的資訊來檢查訊息,並將訊息路由到落腳點(對於傳送者來說,資訊仍然是隱藏的).

2.1.2.3 消費者能看到什麼

當我們站在消費者角度來看與電子郵件系統的類比,這就開始崩潰了(break down). Email客戶端是被動的 - 它們可以讀取它們的郵箱,但它們不能對郵箱的收取產生任何影響.而AMQP消費者也可以是被動的,就像email客戶端一樣. 也就是說,我們可以編寫一個程式來希望特定訊息佇列準備好繫結, 並且應用程式脫離訊息佇列來簡單處理訊息(譯者注:不懂).

此外,我們也允許AMQP 客戶端程式執行下面的操作:

 建立或銷燬訊息佇列;

 通過繫結來定義訊息佇列填充的方式;

 選擇不同的交換器,這將完全改變路由語義.

使用協議,這有點像電子郵件系統能做的:

 建立一個新郵箱;

 告訴MTA帶特定header欄位的訊息都可以拷貝到這個郵箱中;

 完全改變電子郵件系統解析地址和其它訊息頭的方式

我們看到AMQP更像是一種語言的連線片而非一個系統.這正是目標的一部分,通過協議來使伺服器行為可程式設計化.
 

2.1.2.4 原子模式

大多數整合架構不需要這個級別的複雜度.就像業餘攝影師一樣,大多數AMQP使用者需要傻瓜式的模式. AMQP通過兩方面來簡化了概念:

 針對訊息生產者的預設交換器;

 基於佇列名稱作為路由鍵來匹配預設繫結.

實際上,給予適當的許可權,預設的繫結讓生產者直接傳送訊息到訊息佇列–它模擬了傳統中介軟體中簡單的“傳送到目的地"的解決方案。

預設繫結不會阻止訊息佇列更復雜方式的使用。然而它使得在不需要了解交換器和繫結如何工作的情況下,就可以使用AMQP.

2.1.3 交換器

2.1.3.1 交換器型別

每種交換器型別都實現了某種路由演算法.這裡有許多標準的交換器型別(將在"功能說明"章節中講解), 但有兩點是很重要的:

 基於路由鍵來路由的direct 交換器型別. 預設交換器是direct交換器.

 基於路由模式來路由的topic 交換器型別.

在啟動時,伺服器將會建立一系列交換器,如direct 和topic交換器.

2.1.3.2 交換器生命週期

每個AMQP 伺服器都預先建立了許多交換器(例項).這些交換器當伺服器啟動時就存在了,不能被銷燬. AMQP 應用程式也可以建立它們自己的交換器.AMQP不會使用像這樣的"create"方法,相反它使用 "declare"方法,其意義是:"如果你不存在就建立,否則繼續".這是合理的:應用程式可以為了私有使用而建立交換器,並在完成工作時進行銷燬. AMQP提供了方法來銷燬交換器,但一般來說,應用程式不會這樣做.在本章我們的例子中,我們假設交換器已在伺服器啟動時建立過了. 我們不會展示宣告交換器的應用程式.
 

2.1.4 訊息佇列

2.1.4.1 訊息佇列屬性

當客戶端程式建立了訊息佇列時,它可以選擇一些重要的屬性:

 name - 如果沒有指定,伺服器會選擇一個名稱,並將其提供給客戶端.一般來說,當應用程式共享訊息佇列時,它們已經對訊息佇列名稱作了事先的約定,當一個應用程式需要出於其自身目的來要求佇列時,它可讓伺服器提供一個名稱.

 exclusive - 如果設定了,佇列將只屬於當前連線,且在連線關閉時刪除.

 durable - 如果設定了, 訊息會進行儲存,並在伺服器重啟時啟用. 當伺服器重啟時,它可能會丟失瞬時訊息.

2.1.4.2 佇列生命週期

這裡主要有兩種訊息佇列生命週期:

 持久化訊息佇列:它們可被多個消費者共享,並可獨立地存在- 即.不管是否有消費者接收它們,它都可以繼續存在收集訊息.

 臨時訊息佇列:對某個消費者是私有的,只能繫結到此消費者.當消費者斷開連線時,訊息佇列將被刪除.

也存在一些變化,如共享訊息佇列會在最後一個消費才斷開連線時刪除訊息佇列.下面的圖展示了臨時訊息佇列建立和刪除的過程:

 
 

2.1.5 繫結

繫結表示的是交換和訊息佇列之間的關係,該關係告訴交換器如何路由訊息。繫結是從客戶端應用程式命令(一個擁有和使用訊息佇列的應用程式)中來綁到交換器上的。我們可以在虛擬碼中表達一個繫結命令,如下所示:

Queue.Bind <queue> TO <exchange> WHERE <condition>

讓我們看一下三種典型使用情況: 共享佇列,私有回覆佇列,釋出訂閱.

2.1.5.1 構造共享佇列

共享佇列是經典的中介軟體"點對點佇列".在AMQP中,我們可使用預設交換器和預設繫結.我們假設訊息佇列稱為"app.svc01". 這裡是建立共享佇列的虛擬碼:

Queue.Declare

queue=app.svc01

在這個共享佇列中,我們有許多消費者.要從共享佇列中消費, 每個消費者可以這樣做:

Basic.Consume

queue=app.svc01

要傳送到共享佇列, 每個生產者都要將訊息釋出到預設交換器:

Basic.Publish

routing-key=app.svc01

2.1.5.2 構建回覆佇列

回覆佇列通常是臨時的,伺服器分配名稱的. 它們通常也是私有的,即只能由單個消費者讀取. 除了這些特殊情況外,回覆佇列使用與標準佇列相同的匹配條件,因此我們也可以使用預設交換器.

下面是建立回覆佇列的虛擬碼, 這裡的S:表示一個伺服器回覆:

Queue.Declare

queue=<empty>

exclusive=TRUE

S:Queue.Declare-Ok

queue=tmp.1

要釋出到回覆佇列,生產者需將訊息傳送到預設交換器中:

Basic.Publish

exchange=<empty>

routing-key=tmp.1

有一個標準訊息屬性-Reply-To, 它專門設定用來攜帶回復佇列的名稱.

2.1.5.3 構建Pub-Sub 訂閱佇列

在經典中介軟體中,術語"訂閱(subscription)" 在概念上是模糊的,它至少涉及了兩個不同的概念: 匹配訊息的條件和用於儲存匹配訊息的臨時佇列. AMQP把這個工作分成了繫結和訊息佇列兩個部分.在AMQP中沒有稱為訂閱的實體.

讓我們來描述pub-sub訂閱:

 為單個消費者(或某些情況下,多個消費者)儲存訊息

 通過一系列匹配主題,訊息欄位或內容的不同繫結方式來從多個源中收集訊息

訂閱佇列與命名佇列或回覆佇列之間的關鍵區別是訂閱佇列名稱與路由目的無關,路由是通過抽象匹配條件來完成的,而不是1對1路由鍵欄位匹配來完成的.

我們以常見的主題樹pub-sub模型進行講解並對其實現. 我們需要一個能夠在主題樹上匹配的交換器型別. 在AMQP中,這是"topic" 交換器型別. topic交換器會匹配類似"STOCK.USD.*"萬用字元, 如像"STOCK.USD.NYSE"這樣的路由鍵.

我們不能使用預設的交換器或繫結,因為它們不會做topic風格的路由. 因此我們必明確地建立一個繫結.以下是建立和繫結pub-sub訂閱佇列的虛擬碼:

Queue.Declare

queue=<empty>

exclusive=TRUE

S:Queue.Declare-Ok

queue=tmp.2

Queue.Bind

queue=tmp.2

TO exchange=amq.topic

WHERE routing-key=STOCK.USD.*

要從訂閱佇列中消費訊息,消費者需要這樣做:

Basic.Consume

queue=tmp.2

當釋出訊息時,生產者可以這樣做:

Basic.Publish

exchange=amq.topic

routing-key=STOCK.USD.ACME

topic交換器會使用其它繫結表處理傳入的路由鍵("STOCK.USD.ACME"),並會找到一個匹配項tmp.2.然後它會把訊息路由到那個訂閱佇列上.

2.2 AMQP 命令架構

本章節解釋了應用程式如何與伺服器對話.

2.2.1 協議命令 (類&方法)

中介軟體是複雜的,我們在設計協議結構的挑戰是要馴服其複雜性。

我們的方法是基於類來建立傳統API模型,這個類中包含方法,並定義了方法明確應該做什麼.

這會導致大量的命令集合,但一個命令應該相對容易理解.

AMQP命令組合在類中.每個類都覆蓋了一個特定功能領域.有此類是可選的 -每個節點都實現了需要支援的類.

有兩種不同方法對話:

 同步請求-響應,在其中一個節點傳送請求,另一個節點傳送回復.

同步請求和響應適用於效能不是關鍵的地方.

 非同步通知, 在其中,一個節點傳送訊息但不希望得到回覆.非同步方法適用於效能是至關重要的地方.

為使處理方法簡單,我們為每個非同步請求定義了不同的回覆. 也就是說,沒有哪個方法可作為兩個不同請求的回覆.這意味著一個節點,傳送一個同步請求後,可以接受和處理傳入的方法,直到得到一個有效的同步答覆. 這使得AMQP與更加傳統的RPC協議是有區別的.

方法可以形式上定義為同步請求,同步回覆(針對特定請求),或者是非同步的. 最後,每個方法都形貌地定義為客戶端(即. 伺服器到客戶端),或服務端(客戶端到伺服器).

2.2.2 對映AMQP到中介軟體API

我們已經設計AMQP是可對映到中介軟體的API.這種對映有一些是智慧的(不是所有方法, 也不是所有引數對應用程式來說都是有意義的),也有一些是機械的(給定一些規則,所有方法都可以在無人工干預的情況下對映).

這麼做的優勢是對於那些瞭解AMQP語義(本章描述的類)的開發者會在它們使用的環境中找到相同的語義.

例如,下面是Queue.Declare 方法示例:

Queue.Declare

queue=my.queue

auto-delete=TRUE

exclusive=FALSE

這會轉換為線路級幀(wire-level frame):

或者更高階的API:

對映為非同步方法的虛擬碼邏輯是:



對映為同步方法的虛擬碼邏輯是:


 

值得一提的是,對於大部分應用程式, 中介軟體可以完全隱藏在技術層面中, 而且實際API使用的影響會小於中介軟體的健壯性和能力性.

2.2.3 無確認

一個聊天式協議(chatty protocol)是很慢的. 如果在這些情況中,效能是很嚴重的,我們會使用非同步.

一般我們從一個節點傳送訊息到另一個節點. 我們會使傳送方法儘可能地快,而不用等待確認.在必要時,我們可以以較高階別來實現視窗和限制,如以消費者水平.

我們免除了確認,因為我們為所有操作使用了斷言模型. 要麼它們成功,要麼就是我們有關閉通道或連線的異常.

AMQP中可以沒有確認.成功是寂靜的,而失敗是喧鬧的.當應用程式明確需要追蹤成功和失敗時,它們應該使用事務.

2.2.4 Connection類

AMQP是一個連線協議. 連線設計為長期的,且可運載多個通道. 連線生命週期是這樣的:

 client開啟與伺服器的TCP/IP連線併傳送一個協議頭(protocol header).這只是client傳送的資料,而不是作為方法格式的資料.

 server使用其協議版本和其它屬性,包括它支援安全機制列表(Start方法)進行響應.

 client選擇一種安全機制(Start-Ok).

 server開始認證過程, 它使用SASL的質詢-響應模型(challenge-response model). 它向客戶端傳送一個質詢(Secure).

 client向server傳送一個認證響應(Secure-Ok). 例如,對於使用"plain"機制,響應會包含登入使用者名稱和密碼.

server 重複質詢(Secure) 或轉到協商,傳送一系列引數,如最大幀大小(Tune).

 client接受或降低這些引數(Tune-Ok).

 client 正式開啟連線並選擇一個虛擬主機(Open).

 伺服器確認虛擬主機是一個有效的選擇 (Open-Ok).

 客戶端現在使用希望的連線.

 一個節點(client 或 server) 結束連線(Close).

 另一個節點對連線結束握手(Close-Ok).

 server 和 client關閉它們的套接字連線.

沒有為不完全開啟的連線上的錯誤進行握手. 根據成功協議頭協商(後面有詳細定義),在傳送或收到Open 或Open-Ok之前,如果一個節點檢測到錯誤,這個節點必須關閉socket,而不需要傳送任何進一步的資料。

2.2.5 Channel 類

AMQP是一個多通道協議. 通道提供了一種方式來將一個重量級TCP/IP連線分成多個輕量級連線.

這使得協議對於防火牆更加友好,因為埠使用是可預測的. 這也意味著傳輸調整和網路服務質量可以得到更好的利用.

通道是獨立的,它們可以同時執行不同的功能,可用頻寬會在當前活動之間共享.

這是令人期待的,我們鼓勵多執行緒客戶端應用程式經常使用"每個通道一個執行緒"程式設計模型.

然而,從單個client開啟一個或多個AMQP servers連線也是完全可以接受的.

通道生命週期如下:

1. client開啟一個新通道(Open).

2. server確認新通道準備就緒(Open-Ok).

3. client和server按預期來使用通道.

4. 一個節點(client或server) 關閉了通道(Close).

5. 另一個節點對通道關閉進行握手(Close-Ok).

2.2.6 Exchange 類

交換器類讓應用程式來管理伺服器上的交換器。這個類可以讓應用程式指令碼自己佈線(而不是依賴於一些配置介面)。注:大多數應用程式不需要這個級別的複雜度,傳統的中介軟體是不太可能能夠支援這種語義。

交換器生命週期如下:

1. client 請求server確保交換器是否存在(Declare). client可細化到,"如果交換器不存在則進行建立",或 "如果交換器不存在,警告我,不需要建立".

2. client釋出訊息到交換器.

3. client可選擇刪除交換器(Delete).

2.2.7 Queue 類

queue類可讓應用程式來管理伺服器上的訊息佇列. 在幾乎所有消費訊息的應用程式中,這是基本步驟,至少要驗證期望的訊息佇列是否實際存在.

持久化訊息佇列的生命週期相當簡單:

1. client斷言訊息佇列存在(Declare, 使用"passive"引數).

2. server確認訊息佇列存在(Declare-Ok).

3. client從訊息佇列中讀取訊息。

臨時訊息佇列的生命週期更加有趣:

1. client建立訊息佇列(Declare,不提供佇列名稱,伺服器會分配一個名稱). server 確認(Declare-Ok).

2. client 在訊息佇列上啟動一個消費者. 消費者的精確功能是由Basic類定義的。

3. client 取消消費者, 要麼是顯示取消,要麼是通過關閉通道/連線隱式取消的

4. 當最後一個消費者從訊息佇列中消失的時候,在過了禮貌性超時後,server會刪除訊息佇列.

AMQP 像訊息佇列一樣為主題訂閱實現了分發機制. 這使結構更多有趣,訂閱可以在合作訂閱應用程式池中進行負載均衡.

訂閱生命週期涉及到額外的繫結階段:

1. client 建立訊息佇列(Declare),server進行確認(Declare-Ok).

2. client 繫結訊息佇列到一個topic交換器 (Bind),server進行確認(Bind-Ok).

3. client像前面的例子來使用訊息佇列.

2.2.8 Basic 類

Basic 類實現本規範中描述的訊息功能.它支援如下主要語義:

 從client傳送訊息給server, 非同步發生(Publish)

 啟動和停止消費者(Consume, Cancel)

 從server傳送訊息給client, 非同步發生(Deliver, Return)

 應答訊息(Ack, Reject)

 同步從訊息佇列中取訊息 (Get).

2.2.9 Transaction 類

AMQP 支援兩種型別的事務:

1. 自動事務: 每個釋出的訊息和應答都處理為獨立事務.

2. Server 本地事務, 伺服器會快取釋出的訊息和應答,並會根據需要由client來提交它們.

Transaction 類(“tx”) 使應用程式可訪問第二種型別,即伺服器事務。這個類的語義是:

1. 應用程式要求在每個通道中都有事務(Select).

2. 應用程式做一些工作(Publish, Ack).

3. 應用程式提交或回滾工作(Commit, Roll-back).

4. 應用程式做一些工作,迴圈往復。

事務能覆蓋釋出內容和應答,但不能覆蓋投遞(deliveries). 因此回滾不能導致訊息重新入隊或者重新投遞, 客戶端有權在事務中確認這些訊息。

2.3 AMQP 傳輸架構

這個章節解釋了命令是如何對映到線路協議的.

2.3.1 一般描述

AMQP是二進位制協議. 資訊被組織成各種型別的幀(frames). Frames可以攜帶協議方法和其它資訊.所有 幀(frames)都有同樣的格式: 幀頭(frame header),幀負載(frame payload)和幀尾(frame end).幀負載( frame payload)的格式依賴於幀型別(frame type).

我們假設有一個可靠的面向流的網路傳輸層(TCP/IP或相當的).

在單個套接字連線中,可以存在多個獨立控制執行緒,它們被稱為通道.

每個幀都使用通道編號來編號.通過交織它們的幀,不同的通道共享連線。對於任何給定的通道,幀執行在一個嚴格的序列,這樣可以用來驅動一個協議解析器(通常是一個狀態機).

我們使用一組小的資料型別,如位,整數,字串和欄位表來構造幀。幀欄位是緊密包裝的,不會使得它們緩慢或解析複雜。從協議規範中生成框架層是相對簡單的。

線路級格式的設計是可擴充套件性,一般可以用於任意的高層協議(不只是AMQP)。我們假設AMQP將來會擴充套件、改進,隨時間的推移線路級格式仍然會得到支援。

2.3.2 資料型別

AMQP資料型別用於方法幀中,它們是:

 Integers ( 1到8個位元組),用來表示大小,數量,範圍等等. Integers通常是無符號的,在幀中可能是未對齊的.

 Bits,用來表示開/關值.位被包裝成位元組。

 短字串(short string),用來儲存短的文字屬性.短字串限制為255個位元組,可以在無緩衝區溢位的情況下進行解析.

 長字串(long string),用來儲存二進位制資料塊.

 欄位表(Field tables),用來儲存名稱-值對(name-value pairs). 欄位值型別可以是字串,整數等等.

2.3.3 協議協商

AMQP client 和server 可對協議進行協商.這就是說當client連線時,server可處理client能接受或修改的操作.當兩個點對結果達成一致時, 連線會繼續前行.協商是一種有用的技術,因為它讓我們可以斷言假設和前提條件。在AMQP,我們協商協議的下面方面:

 實現協議和版本. server 可在同一個埠上儲存多個協議.

 加密引數和兩者之間的認證.這是功能層的一部分,以前解釋過。

 最大幀大小,通道數量,以及其它操作限制.

達成一致的限制可能會使兩者重新分配關鍵快取區以避免死鎖.每個傳入的幀要麼服從達成的限制(這是安全的),或者超過它們(在這種情況下,另一方必須斷開連線).這非常符合"它要麼工作,要麼就完全不工作"的AMQP哲學.

兩個節點達成一致的最低限度為:

 伺服器必須告訴客戶端它提出了什麼限制。

 客戶端進行響應,並可能減少其連線的限制。

2.3.4 限制幀

TCP/IP是一個流協議,即沒有限制幀的內建機制. 現有協議可以幾種不同的方式解決這個問題:

 每個連線中只傳送單個幀.這很簡單,但很慢.

 在流中新增幀定界符.這很簡單,但解析較慢.

 計算幀的大小, 並在每個幀的前面傳送大小。這是簡單和快速,和我們的選擇.

2.3.5 幀細節

所有的幀都由一個頭(header,7個位元組),任意大小的負載(payload),和一個檢測錯誤的幀結束(frame-end)位元組組成:

要讀取一個幀,我們必須:

1. 讀取header,檢查幀型別(frame type)和通道(channel).

2. 根據幀型別,我們讀取負載並進行處理.

3. 讀取幀結束位元組.

在實際實現中,如果效能很關鍵的話,我們應該使用讀前緩衝(read-ahead buffering)”或“收集讀取(gathering reads)”,以避免為了讀一個幀而做三次獨立的系統呼叫。

2.3.5.1 方法幀

方法幀可以攜帶高階協議命令(我們稱之為方法(methods)).一個方法幀攜帶一個命令. 方法幀負載有下面的格式:


 

要處理一個方法幀,我們必須:

1. 讀取方法幀負載.

2. 將其拆包成結構. 方法通常有相同的結構,因此我們可以快速對方法進行拆包.

3. 檢查在當前上下文中是否允許出現方法.

4. 檢查方法引數是否有效.

5.執行方法.

方法主體(bodies) 由AMQP資料欄位(位,整數, 字串和字串表組成)構成. 編組程式碼直接從協議規範中生成,因此是非常快速地.

2.3.5.2 內容幀

內容是我們通常AMQP伺服器在客戶端與客戶端之間傳送和應用資料. 粗略地說,內容是由一組屬性加上一個二進位制資料部分組成的。它所允許的屬性集合由Basic類定義,而這些屬性的形式為內容頭幀(content header frame)。其資料可以是任何大小,也有可能被分解成幾個(或多個)塊,每一個都有內容體幀(content body frame)。

看一個特定通道的幀,當它們線上路上傳輸時,我們可能會看到下面這樣的東西:

某些方法(如Basic.Publish, Basic.Deliver等等.)通常情況下定義為傳輸內容.

當一個節點傳送像這樣的方法幀時,它總是會遵循一個內容頭幀(conent header frame)和零個或多個內容體幀(content body frame)的形式.

一個內容頭幀有下面的格式:

某些方法(如Basic.Publish, Basic.Deliver等等.)通常情況下定義為傳輸內容.

當一個節點傳送像這樣的方法幀時,它總是會遵循一個內容頭幀(conent header frame)和零個或多個內容體幀(content body frame)的形式.

一個內容頭幀有下面的格式:

我們將內容體放置在不同的幀中(並不包含在方法中),因此AMQP可支援零拷貝技術,這樣其內容就不需要編組或編碼. 我們將內容屬性安放在它們自己的幀中,以便收件人可以有選擇地丟棄他們不想處理的內容。

2.3.5.3 心跳幀

心跳是一種設計用來撤銷(undo)TCP/IP功能的技術,也就是說在長時間超時後,它有能力通過關閉broker物理連線來進行恢復.在某些情景下,我們需要快速知道節點連線是否斷開了,或者是由於什麼原因不能響應了.因為心跳可以在較低水平上進行,我們在傳輸層次上按節點交換的特定幀型別來處理,而不是按類方法.

2.3.6 錯誤處理

AMQP使用異常來處理錯誤.任何操作錯誤(未找到訊息佇列,訪問許可權不足)都會導致一個通道異常. 任何結構化的錯誤(無效引數,壞序列的方法.)都會導致一個連線異常.異常會關閉通道或連線,同時也會向客戶端應用返回響應碼和響應文字.我們使用了類似於HTTP等協議和其它大多數協議中的三位回覆程式碼和文字回覆文字方案.

2.3.7 關閉通道和連線

連線或通道,對於客戶端來說,當其傳送Open時則被認為是“開啟”的,對於伺服器端來說,當其傳送Open-Ok時則被認為是開啟的。基於這一點,一個希望關閉通道或連線的對等體也必須使用握手協議來這樣做。

可出於任何原因,可能會正常地或異常地關閉一個通道或連線-因此必須仔細小心。

對於突然或意外關閉,並不能得到快速探測,因此當發生異常時,我們可能會丟失錯誤回覆程式碼。

正確的設計是對於所有關閉必須進行握手,使我們關閉後對方知道相應的情況。

當一個節點決定關閉一個通道或連線時,它傳送一個Close方法。接收節點必須使用Close-Ok來響應Close,然後雙方可以關閉他們的通道或連線。請注意,如果節點忽略了關閉,當兩個節點同時傳送Close時,可能會發生死鎖。

2.4 AMQP Client 架構

可直接從應用程式中讀寫AMQP幀,但這是相當糟糕的設計.

即使是最簡單的對話方塊也比較複雜(比如同HTTP比較),應用程式開發者沒必要為了向訊息佇列傳送訊息, 而來理解二進位制這樣的東西. 推薦的AMQP client架構須由下面的多個抽象層組成:

1. 幀層. 此層接受AMQP協議方法,並按某種語言格式(結構,類等等) 來序列化成線路級幀.幀層可以根據AMQP規範機械產生(這是在一個協議的建模語言,專為AMQP定義了XML實現).

2. 連線管理層. 此層用於讀寫AMQP幀,並管理所有連線,會話邏輯.在此層中,我們可以封裝開啟連線和會話,錯誤處理,內容傳輸和接收的全部邏輯. 此層的大部分都可通過AMQP規範來生成.例如,規範定義了哪些方法可以攜帶內容, 因為邏輯傳送方法和可選的傳送內容可以機械的生成.

3. API 層. 此層暴露了應用程式工作的特定API. API層可能會反映一些現有的標準,或暴露高層AMQP的方法,或對本節前面介紹的內容做一個對映。AMQP方法設計為使這些對映簡單有用。API層本身可能是由多個層組成的,如.構建於AMQP方法API之上的高階API.

此外,通常還會有一些I / O層,這此可以是非常簡單的(同步套接字讀取和寫入)或複雜的(完全非同步多執行緒I / O)。此圖顯示了整體推薦的架構:

 

在本文件中,當我們說"client API"的時候,我們指的則是應用程式下的所有層(i/o,幀,連線按理和API層).我們通常將客戶端API和應用程式分開說, 在這裡,應用程式會使用客戶端API來同中介軟體伺服器進行對話.

3 功能說明

3.1 Server 功能說明

3.1.1 訊息和內容

訊息是中介軟體路由和佇列系統處理的原子單元。訊息可攜帶一份內容,它包括一個內容頭,一組屬性,和一個內容體,和持有一個不透明的二進位制資料塊。

一個訊息可以對應到許多不同應用程式的實體:

 一個應用程式級訊息

 一個傳輸檔案

 一個資料流幀等等.

訊息可以持久化.一個持久化訊息可以安全地儲存在磁碟上,即使是在嚴重的網路故障,伺服器崩潰、溢位等情況下也可確保投遞.訊息也可以有優先順序.高優先順序訊息會在等待同一個訊息佇列時,在低優先順序訊息之前傳送. 當訊息必須被丟棄以確保伺服器質量水平,將會優先丟棄低優先順序訊息.

伺服器不能修改接收到並將傳遞給消費者應用程式的訊息內容體. 伺服器可在內容頭中新增額外資訊,但不能刪除或修改現有資訊.

3.1.2 虛擬主機(Virtual Hosts)

虛擬主機是伺服器內的資料分割槽, 它為在共享基礎設施上的管理帶來了方便.

一個虛擬主機包括其名稱空間,一組交換器,訊息佇列以及所有相關物件. 每個連線必須關聯一個單個虛擬主機.

在認證後,客戶端可在Connection.Open方法中選擇虛擬主機. 這意味著,伺服器上的認證方案可在此伺服器上的所有虛擬主機上共享. 然而,對於每個虛擬主機來說,也可以獨特的認證方案. 對於每個虛擬主機需要不同的身份驗證方案的管理員應該使用單獨的伺服器。

連線中的所有通道都在同一個虛擬主機上工作.在同一個連線中,沒有與不同虛擬主機通訊的方式, 也沒有在不斷開連線重新開始的情況下,切換到其它虛擬主機的可能性.

該協議沒有提供用於建立或配置虛擬主機的機制-這在伺服器內是一個不確定的方式,是完全依賴於實現的。

3.1.3 交換器

交換器是一個虛擬主機內的訊息路由代理。交換器例項(我們通常稱之為“交換器”)接受訊息和路由資訊-主要是一個路由鍵-或者將訊息傳遞到訊息佇列,或到內部服務。交換器是基於每個虛擬主機命名的。

應用程式可以在許可權範圍內自由地建立、共享、使用和銷燬交換器例項.交換器可能是持久的、臨時的或自動刪除的。持久化的交換器會持續到他們被刪除,臨時的交換器會持續到伺服器關閉。自動刪除的交換器直到他們不再使用。伺服器提供了一組特定的交換器型別。每個交換器型別都實現了一個特定的匹配和演算法,如下一節中定義的。AMQP只要求少量的交換器型別,並推薦了一些。此外,每個伺服器實現可以新增自己的交換型別。

交換器可以將單個訊息併發地路由到的訊息佇列中。這將建立一個獨立訊息的多個例項。

3.1.3.1 Direct交換器型別

direct 交換器按如下方式來工作:

1. 訊息佇列使用路由鍵K來繫結交換器.

2. 釋出者使用路由鍵R來向交換器傳送訊息.

3. 在K=R時,訊息會傳遞到訊息佇列中.

server必須實現direct交換器,並且在每個虛擬主機中必須預定義兩個direct交換器: 一個名為 amq.direct, 另一個無公共名稱(為Publish方法的預設交換器).

注意,訊息佇列可以使用任何有效的路由鍵值進行繫結,但通常訊息佇列使用它們自己的名稱作路由鍵來繫結.

事實上,所有訊息佇列必須能使用其自身佇列名稱作路由鍵自動繫結無名稱的交換器上.

3.1.3.2 Fanout 交換器型別

fanout交換器型別按如下方式來工作:

1. 訊息佇列不使用引數來繫結交換器.

2. 釋出者向交換器傳送訊息.

3. 訊息無條件傳遞給訊息佇列。

fanout 交換器是微不足道的設計與實現.此交換器型別和預宣告的交換器稱為amq.fanout,它是強制的.

3.1.3.3 Topic交換器型別

topic交換器型別按如下方式來工作:

1. 訊息佇列使用路由模式P來繫結到交換器.

2. 釋出者使用路由鍵R來向交換器傳送訊息.

3. 當R匹配P時,訊息將被傳遞到訊息佇列.

用於topic交換器的路由鍵必須由0個或多個由點號

用於topic交換器的路由鍵必須由點分隔的零或多個單片語成.每個單詞必須包含字母A-Z和a-z 以及數字0-9.

路由模式與路由鍵遵循相同的規則,* 用於匹配單個單詞,# 用於匹配0個或多個單詞.因此路由模式*.stock.# 會匹配路由鍵usd.stock 和eur.stock.db 但不匹配stock.nasdaq.

對於topic交換器我們建議的設計是保持所有已知路由鍵的集合,當釋出者使用了新的路由鍵時,才更新此集合. 通過給定一個路由鍵來確實所有繫結是可能的,因此可為訊息快速找到訊息佇列. 此交換器型別是可選的.

server應該實現topic交換器型別,在這種情況下,server 必須在每個虛擬主機中預先定義至少一個 topic交換器,其名稱為amq.topic.

3.1.3.4 Headers交換器型別

headers交換器型別按如下方式進行工作:

1. 訊息佇列使用包含匹配繫結和帶有預設值的header參數列來繫結交換器.在這種交換器型別中,不使用路由鍵.

2.釋出者向交換器傳送訊息,這些訊息的headers屬性中包含名稱-值對的表.

3.如果訊息頭屬性與佇列繫結的引數相匹配,則訊息傳遞給佇列。

匹配演算法是由參數列中的名稱值對這樣的特殊繫結引數來控制的. 這個引數的名稱是'x-match'.

它可以接受兩種值, 以表示表格中其它的名稱值對將如何來進行匹配:

 'all' 則表明所有其它的名稱值對必須與路由訊息的頭屬性相匹配(即.AND匹配)

 'any' 則表明只要訊息頭屬性中的任何一個欄位匹配參數列中的欄位,則訊息就應該被路由(即. OR匹配).

繫結引數中的欄位必須與訊息欄位中的欄位相匹配,這些情況包括:如果繫結引數中的欄位沒有值且在訊息頭中存在相同名稱的欄位,或者繫結引數中的欄位有值,且訊息屬性中存在同樣的欄位且有相同的值。

任何以'x-'而不是'x-match'開頭的欄位為將來保留使用並會被忽略.

server應該實現headers交換器型別, 且server必須在每個虛擬主機中預先宣告至少一個headers交換器,且名稱為amq.match.

3.1.3.5 System交換器型別

system交換器型別按如下方式進行工作:

1. 釋出者使用路由鍵S來向交換器傳送訊息.

2. system交換器將其傳遞給系統服務S.

系統服務以"amq."開頭,為AMQP保留使用. 在伺服器環境中,所有其它名稱可自由使用. 此交換器型別是可選的.

3.1.3.6 實現定義的交換器型別

所有非規範交換器型別必須以"x-"開頭. 不以"x-"開頭的交換器作為將來AMQP標準保留使用.

3.1.4 訊息佇列

訊息佇列是一個名為FIFO的緩衝區且為一組消費者應用程式儲存訊息.

在其許可權範圍內,應用程式可以自由地建立、共享、使用和銷燬訊息佇列.

注意,在一個佇列中可能存在多個讀者,或存在客戶端事務,或存在使用了優先順序欄位,或存在使用了訊息選擇器,或特定實現了投遞優化的佇列可能不會真正地展現出FIFO特性. 唯一可以確保FIFO的方式是隻有一個消費者連上了佇列.在那些情況下,佇列可描述為弱-FIFO.

訊息佇列可能是持久化的或自動刪除的.持久化訊息佇列會持續到它們刪除時為止. 臨時訊息佇列可持續到伺服器關閉時為止.自動刪除訊息佇列可持續到它們不再使用時為止.

Message佇列可將訊息儲存在記憶體,磁碟,或兩者的組合中.訊息佇列是基於虛擬主機來命名的.

訊息佇列儲存資訊,並可在一個或多個消費客戶端之間進行分發.路由到訊息佇列中的訊息不能再發給多個客戶端,除非在失敗或拒絕後進行重發.

單個訊息佇列可在同個時間可獨立地持有不同型別的內容.也就是,如果Basic和檔案內容都發給了同一個訊息佇列,這些將會作為請求獨立地分發給消費應用程式.

3.1.5 繫結

繫結是訊息佇列和交換器之間的關係.繫結特有的路由引數將告訴交換器那些佇列應該得到訊息. 應用程式可根據需要來驅動訊息流向它們的訊息佇列. 繫結的壽命依賴於定義它們的訊息佇列 - 當訊息佇列被銷燬時,其繫結也會被銷燬.Queue.Bind 方法的特定語義將依賴於交換器型別.

3.1.6 消費者

我們使用術語"consumer"來表示應用程式和控制客戶端程式來接收訊息佇列中的實體.當客戶端啟動一個消費者,它就在伺服器中建立了一個消費實體 .當客戶端退出一個消費者時,它就銷燬了一個伺服器中的消費者實體. 屬於單個客戶端通道的消費者可非同步地將訊息傳送到佇列中.

3.1.7 服務質量

服務質量控制了訊息傳送的速度. 服務質量依賴於被分發的內容型別.一般的服務質量,在客戶端應答訊息前,會使用預提取的概念來指定傳送多少個訊息或多少個位元組的數量. 目標是提前傳送訊息資料,以減少延遲。

3.1.8 確認/應答

應答是從客戶端程式發出的正式訊號,用以表示訊息佇列中的訊息已經得到成功處理. 有兩種應答模型:

1. 自動地(Automatic), 在這種情況下,只要訊息投遞到了應用程式,伺服器就會立即從訊息佇列中刪除訊息(通過 Deliver 或 Get-Ok 方法).

2. 明確地(Explicit),在這種情況下,客戶端程式必須對每個訊息發磅一個Ack方法以表示訊息被處理了.客戶端層可以不同方式來實現明確應答,如.只要收到了訊息或當應用程式表示訊息已經處理了.

這些區別不會影響AMQP或互操作性.

3.1.9 流控制(Flow Control)

流控制是一個用來中止節點訊息流的緊急過程. 它在客戶端和伺服器端都按同樣方式工作,且都是由Channel.Flow命令實現的. 流控制是唯一可以阻止一個過度生產釋出者的機制.如果它使用訊息確認(這通常意味著使用事務), 消費者則可以使用更優雅的預取機制視窗。

3.1.10 命名約定

這些約定規範了AMQP實體命名. 伺服器和客戶端必須遵守這些約定:

 使用者定義的交換器型別前輟必須是"x-"

 標準交換器例項前輟是"amq."

 標準系統服務前輟是"amq."

 標準訊息佇列前輟是"amq."

 所有其他的交換器、系統服務和訊息佇列名稱都在應用程式空間中。

3.2 AMQP 命令說明(Classes & Methods)

3.2.1 解釋性註釋

出於互操作原因,AMQP方法可以定義特定的最小值(如每訊息佇列的消費者數量)。這些極小值被定義在每個類的描述中。

遵從AMQP的實現應該為這些欄位實現合理值, 最小值只用在最小能力的平臺上.

語法使用這樣的標記法:

 'S:' 指示從伺服器傳送到客戶端的資料或方法;

 'C:' 指示從客戶端傳送到伺服器的資料或方法;

 +term or +(...) 表示式表示1個或多個例項;

 *term or *(...) 表示式表示0個或多個例項.

我們定義的方法是:

 一個同步請求("syn request").傳送節點應該等待特定的回覆方法,但可以非同步實現此方法;

一個同步回覆("syn reply for XYZ");

 一個非同步請求或答覆 ("async").

3.2.2 類和方法細節

這部分是由生成的檔案amqp-xml-spec.odt提供。

4 技術說明

4.1 IANA分配的埠號

IANA為標準AMQP的TCP和UDP分配了5672埠。UDP埠被保留用於將來的組播實現。

4.2 AMQP 執行緒級格式

4.2.1 正式協議語法

我們為AMQP提供了一個完整語法(這只是AMQP提供的參考,跳到下一節,你會發現不同的幀型別和格式):

 
 

我們使用了IETF RFC 2234中定義的增強BNF語法. 總體而言,

 規則的名稱僅僅是名稱本身。

 終端是由一個或多個數字字元指定的,這些字元的基本解釋為“d”或“x”。

 通過列出一系列規則名稱,一個規則可以定義一個簡單的,有序的字串的值.

 其他數值的範圍可以簡潔指定,使用破折號(“-”)來表示替代值的範圍。

 在圓括號中的元素被視為單個元素,其內容是嚴格有序的。

 由/分隔的元素是可替代值.

 元素之間的操作符 "*"表示重複.完整格式為: "<a>*<b>element",這裡<a>的<b>是可選的十進位制值, 表示只能出現大於<a>而小於<b>的元素.

 規則形式: "<n>element" 等價於<n>*<n>element.

 方括號中的元素是可選元素.

4.2.2 協議頭

client必須通常傳送一個協議頭開始新連線.它是8位元組序列:

協議頭由大寫字母"AMQP",其後跟常量%d0組成:

1. 協議主版本號, 按照章節1.4.2中描述的使用.

2. 協議次版本號, 按照章節1.4.2中描述的使用.

3. 協議修訂版本, 按照章節1.4.2中描述的使用.

該協議協商模型與現有HTTP協議相容,使用常量文字字串來發起連線, 並使用防火牆來檢測協議的開始以決定應用什麼規則.

client和服務通過以下方式來達成協議版本一致:

 client開啟一個到AMQP伺服器的新socket連線,併傳送協議頭.

 server可接受或拒絕協議頭.如果它拒絕了協議頭,它將會輸出一個有效的協議頭到socket,然後再關閉socket.

 否則它會同意(leaves)socket開啟,並相應地實現協議.

示例:

實現者指導方針:

 server可接受非AMQP協議,如HTTP.

 如果server無法識別socket資料中的前5個位元組,或者它不支援client請求的協議版本,它必須輸出一個有效的協議頭到socket,然後再關閉socket (必須確保client應用程式能收到資料) ,最後再關閉socket連線.伺服器可以列印診斷資訊以輔助除錯。

 client可使用伺服器支援的最高版本來進行檢測,如果收到了伺服器發回的這種資訊,就可使用較低版本來進行重連

 實現了多版本AMQ的Clients和servers都應該使用8位元組的協議頭來標識協議.

4.2.3 通用幀格式

所有幀都以7個位元組的頭開始,其中包括一個type欄位 ,一個channel欄位和一個size欄位:

AMQP 定義瞭如下的幀型別:

 Type = 1, "METHOD": 方法幀

 Type = 2, "HEADER": 內容頭幀

 Type = 3, "BODY": 內容體幀.

 Type = 4, "HEARTBEAT": 心跳幀.

通道編號為0的代表全域性連線中的所有幀,1-65535代表特定通道的幀.

size欄位是負載的大小,不包括結束幀位元組. 由於AMQP假設是一個可靠的連線協議,我們使用結束幀來檢測錯誤客戶端和伺服器實現引起的錯誤.

實現者指導方針:

 結束幀必須是十六進位制值%xCE.

 如果一個節點收到了未定義型別的幀,它必須將其視為致命的協議錯誤,並關閉連線,而不進一步地傳送任何資料

 當一個節點讀取到幀時,在解碼幀前,它必須檢查結束幀是否是有效的. 如果結束幀無效,它必須將其視為致使的協議錯誤,並關閉連線,而不進一步地傳送任何資料. 它應該記錄相關問題的日誌資訊,這樣就可以伺服器或客戶端幀程式碼實現中表示錯誤.

 節點傳送的幀大小不能超過約定的大小. 節點收到超過大小的幀時,必須發出一個回覆碼為501(幀錯誤)的連線異常訊號.

 對於所有心跳幀,方法幀,連線類的頭和體,通道編號必須為0. 節點收到非0通道編號的這些幀必須使用回覆碼503(無效命令)來發出異常訊號.

4.2.4 方法負載

方法幀的體包括一個不可變的資料欄位列表,稱為"arguments".所有方法體都以型別和方法的識別符號開始:

實現者指導方針:

 class-id 和 method-id是由AMQP類和方法定義的常量.

 arguments 是特定於每個方法中的一組AMQP欄位.

 Class id 中%x00.01-%xEF.FF範圍內的值被AMQP標準類保留使用.

 Class id 中%xF0.00-%xFF.FF (%d61440-%d65535) 範圍內的值可用於非標準擴充套件類實現.

4.2.5 AMQP 資料欄位

AMQP有兩種級別的資料欄位:用於方法引數的原生資料欄位, 以及用於多個應用之間傳遞資料的欄位表. 欄位表是原生資料欄位的超集.

4.2.5.1 Integers

AMQP定義了這些原生整數型別:

 無符號位元組(8 bits).

 無稱號短整形(16 bits).

 無符號長整形(32 bits).

 無符號長長整形(64 bits).

整形和字串長度總是無符號的,且按網路位元組順序儲存. 當存在兩個高低系統時(如.兩個Intel CPUS),我們不會對它們的互動嘗試優化.

實現方針:

 實現不能假設幀內的整形編碼在記憶體邊界中是對齊的.

4.2.5.2 Bits

AMQP定義了一個原生位欄位型別. 位累積成整個位元組. 當在幀中兩個或更多位相鄰時,它們會被包裝成一個或多個位元組,且在每個位元組中以低位開始.

沒有要求在一個幀中的所有位必須是連續的,但這通常是做,以儘量減少幀尺寸。

4.2.5.3 Strings

AMQP 字串是可變長度,由一個整數長度後跟零個或多個位元組資料表示. AMQP定義了兩種原生字串型別:

 短字串(Short strings),以8位無稱號整形長度後跟0個或多個位元組資料儲存. 短字串可攜帶最多255位元組的UTF-8資料, 但不能包含二進位制零位元組.

 長字串(Long strings), 以32位無稱號整形長度後跟0個或多個位元組資料儲存. 長字串可包含任意資料.

4.2.5.4 時間戳(Timestamps)

時間戳是以精度為1秒的64位POSIX time_t 格式儲存的.使用64伴可以避免31位和32位相關的time_t值概括問題(wraparound issues).

4.2.5.5 欄位表

欄位表是包含名稱-值對的長字串. 名稱-值對編碼為:以短字串定義名稱,位元組定義值型別和值. 有效的表欄位型別是原生整形,位,字串,時間戳型別的擴充套件. 多位元組整形欄位通常是按網路位元組順序儲存的.

指導方針:

 欄位名稱必須以字母開頭,其後可跟'$,'#',數字,下劃線,最大長度為128個字元.

 server應該驗證欄位名稱,如果收到了無效的欄位名稱,它應該使用回覆碼503(語法錯誤)來發出異常訊號.

 十進位制值不用於支援浮點值,它是固定的業務值,如貨幣匯率和金額。其位元組編碼代表了位置編號,其後跟著一個無符號的長整數.“十進位制”是無符號的.

 重複欄位是非法的。對於一個包含重複欄位的表,其行為是未定義的。

4.2.6 內容幀

某些特定的方法(Publish, Deliver, etc.) 會攜帶內容.請參考 "Functional Specifications" 來了解每種方法的說明,以及它們是否是攜帶內容的方法.

內容由1個或多個幀組成:

1. 只有一個內容頭幀能提供內容屬性.

2. 可選的, 可以有1個或多個內容體幀.

特定通道上的內容幀是嚴格有序的. 也就是說,它們可以和其它通道的幀混合,但同一個通道內兩個幀是不可能混合或重疊, 也不可能出現單個內容上的內容幀與相同通道上的方法幀相混合.

注意,任何非內容幀都會明確地標識內容的結束. 儘管可從內容頭中知道內容的大小,但也允許傳送者在不關閉通道的情況下中止內容傳送.

實現者指導方針:

 收到不完整或錯誤格式內容的節點必須使用回覆碼500(非希望幀)丟擲一個連線異常. 這包括缺少內容頭,內容頭中錯誤的class IDs,缺少內容體幀等等.

4.2.6.1 內容頭

內容頭負載有下面的格式:

實現者指導方針:

 class-id必須與方法幀class id匹配. 節點必須對無效的class-id使用501回覆碼(幀錯誤)丟擲一個連線異常.

 weight欄位未使用且必須是0.

 body大小是一個64位值,它定義了內容體的總大小,也就是後面內容體幀的body大小的總和. 0表示無內容體幀.

 property flags是位陣列,它表示每個屬性的存在性. 位是從最高到最低進行排序的,位15代表第一個屬性.

 property flags可指定多於16屬性.如果最後位(0)被設定了,這表明其後有進一步的屬性標誌欄位。根據需要,這裡有許多屬性標誌欄位。

 屬性值是特定類的AMQP資料欄位.

 位屬性僅由它們各自的屬性標誌(0或1)表示,並且在屬性列表中不存在。

 內容幀中的通道編碼不能為0.在內容幀中收到0通道編號的節點必須使用504回覆碼(通道錯誤)來發出異常訊號

4.2.6.2 內容體

內容體負載是是不透明的二進位制塊,其後跟著一個結束幀位元組:

內容體可以根據需要分成多個幀.幀負載的最大大小可在連線時,由兩端進行協商.

實現者指導方針:

 節點必須要能將分成多個幀的內容體作為單一集合進行儲存處理,要麼分成更小的幀重新傳輸,要麼 連線成單個塊分發給應用程式.

4.2.7 心跳幀

心跳幀告訴收件人發件人仍然是活的. 在連線時,心跳幀的速率和時間都可以調整.

實現者指導方針:

 心跳幀的通道編號必須為0. 收到無效心跳幀的節點需使用501回覆碼(幀錯誤)來丟擲異常.

 如果節點不支援心跳,它必須在不發出錯誤或失敗訊號的情況下丟棄心跳幀.

 client收到Connection.Tune方法後,必須要開始傳送心跳, 並在收到Connection.Open後,必須要開始監控.server在收到Connection.Tune-Ok後,需要開始傳送和監控心跳.

 節點應該盡最大努力按固定頻率來傳送心跳. 心跳可在任何時候傳送. 任何傳送位元組都可作為心跳的有效替代,因此當超過固定頻率還沒有傳送非AMQP心跳時,必須傳送心跳.如果節點在兩個心跳間隔或更長時間內,未探測到傳入的心跳,它可在不遵循Connection.Close/Close-Ok握手的情況下,關閉連線,並記錄錯誤資訊.

 心跳應該具有持續性,除非socket連線已經被關閉, 包括在Connection.Close/Close-Ok 握手期間或之後的時間.

4.3 通道複用

AMQP 允許節點建立多個獨立的控制執行緒.每個通道都可作為共享單個socket的虛擬連線:

實現者指導方針:

 AMQP節點可支援多個通道.在連線協商期間,可定義最大通道數目,節點可協商這個數值為1.

 每個節點都應該以公平的方式平衡所有開啟通道的流量. 這種平衡可以每幀為基礎,也可以以每個通道上的總交通流量為基礎. 節點不應該允許一個非常繁忙的通道讓一個不太繁忙的通道餓死.

4.4 可見性保證

伺服器必須確保客戶端對伺服器狀態的觀察是一致的。

下面的示例說明了在這種情況下,客戶端的觀察方法:

 Client 1 和 Client 2 連上了同一個虛擬主機

 Client 1 宣告瞭一個佇列

 Client 1 收到了Declare.Ok回覆 (觀察”的一個例子)

 Client 1 將其告知了Client 2

 Client 2 對同一個佇列做了被動宣告

可見性必須保證Client 2能看到佇列(在沒有刪除的情況下)

4.5 通道關閉

當發生以下事件時,server會考慮通道已經關閉了:

1. 節點關閉了通道或其父連線使用了Close/Close-Ok握手.

2. 節點在通道或父連線上丟擲了異常.

3.節點未使用 Close/Close-Ok握手關閉了父連線socket.

當伺服器關閉通道時,通道上任何未應答的訊息將標記為重新分發.

當伺服器關閉連線時,它會刪除連線所擁有的自動刪除資訊.

4.6 內容同步

在某些情況下,同步請求響應方法會對同一個通道上的非同步內容傳遞產生影響,包括:

 Basic.Consume 和 Basic.Cancel 方法, 這些會啟動和停止訊息佇列中的訊息流.

 Basic.Recover 方法,它會要求伺服器重新分發訊息到通道.

 Queue.Bind, Queue.Unbind, 和Queue.Purge 方法, 它會影響訊息進入訊息佇列.

實現者指導方針:

 請求-響應效果在response方法之前必須不可見,但在之後必須可見.

4.7 內容排序保證

流經通道的方法順序是穩定的:方法按傳送時的順序接收. 這是由AMQP使用的TCP/IP傳輸所保證的.

此外,伺服器也會按一種穩定的方式來處理內容.尤其是,經過伺服器中單個路徑的內容會保持順序.

對於設定了優先順序並經過單個路徑內容,我們定義了一個內容處理路徑-由一個傳入通道,一個交換器,一個佇列和一個傳出通道組成.

實現者指導方針:

 server必須保持流經單個內容處理路徑上的順序性,除非在Basic.Deliver或Basic.Get-Ok方法上設定了redelivered欄位,可根據條件規則來設定欄位.

4.8 錯誤處理

4.8.1 異常

使用標準的異常程式設計模型, AMQP不會發出成功訊號,只在失敗時才發出訊號. AMQP定義了兩種異常級別:

1. 通道異常.指那些關閉通道引起的錯誤.通道異常通常是因為軟錯誤引起的,這些錯誤並不影響應用程式的其它部分.

2. 連線異常. 這些關閉socket連線的異常通常是因為硬錯誤造成的,如程式錯誤,錯誤配置或其他需要干預的情況。

4.8.2 回覆程式碼格式

AMQP 回覆程式碼按照 IETF RFC 2821的回覆程式碼的嚴重程度和理論進行定義.

4.9 限制

AMQP規範為將來的AMQP擴充套件或同種線路級格式使用瞭如下限制:

 每個連線上的通道數量: 16位通道數量.

 協議類數量: 16位class id.

 每個協議內的方法數量: 16位 method id.

AMQP規範對於資料做了如下限制:

 短字串的最大長度為: 255位元組.

 長字串或欄位表的最大長度: 32位大小.

 幀負載的最大大小: 32位大小

 內容的最大長度: 64位大小.

伺服器或客戶端也可以對資源施加自己的限制,如併發連線的數量、每個通道的消費者數量、佇列的數量等。這些不影響互操作性,因此並沒有指定。

4.10 安全

4.10.1 目標和原則

為了防止緩衝區溢位,我們在所有地方都使用特定長度的緩衝區. 當讀取資料時,所有資料都可以使用允許的最大長度來進行驗證.無效的資料可以被明確地處理,通過關閉通道或連線。

4.10.2 拒絕服務攻擊

AMQP 通過回覆碼並關閉通道或連線來處理錯誤.這避免了錯誤出現後的模糊狀態.在連線協商期間,伺服器可假設特殊條件是因獲取訪問伺服器的敵對嘗試所造成的.對於連線協商中的任何異常,一般處理是暫停該連線 (可能是一個執行緒)幾秒種時間,然後再關閉網路連線. 這包括語法錯誤,過大資料,或認證失敗.伺服器應該記錄所有這些異常標誌或阻止客戶端挑起多個故障.

相關文章