<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆 - 41  文章 - 7  trackbacks - 0
    <2016年8月>
    31123456
    78910111213
    14151617181920
    21222324252627
    28293031123
    45678910

    常用鏈接

    留言簿

    隨筆分類

    隨筆檔案

    搜索

    •  

    最新評論

    閱讀排行榜

    評論排行榜

    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模塊中的抽象. 這就像允許電子郵件繞過MTA的路由表,直接發送到郵箱中一樣. 這會導致在中間過程中不能插入過濾處理,例如,垃圾郵件檢測.
    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 通過回復碼并關閉通道或連接來處理錯誤.這避免了錯誤出現后的模糊狀態.在連接協商期間,服務器可假設特殊條件是因獲取訪問服務器的敵對嘗試所造成的.對于連接協商中的任何異常,一般處理是暫停該連接 (可能是一個線程)幾秒種時間,然后再關閉網絡連接. 這包括語法錯誤,過大數據,或認證失敗.服務器應該記錄所有這些異常標志或阻止客戶端挑起多個故障.
    posted on 2016-08-12 18:30 胡小軍 閱讀(10848) 評論(0)  編輯  收藏 所屬分類: RabbitMQ
    主站蜘蛛池模板: 国产成人在线观看免费网站| 久久久亚洲精品国产| 九九视频高清视频免费观看| 亚洲AV乱码一区二区三区林ゆな| 免费精品人在线二线三线区别| 高h视频在线免费观看| 亚洲AV无码久久| 午夜视频在线观看免费完整版| 国产无限免费观看黄网站| 亚洲精品午夜视频| 免费一级毛片一级毛片aa| 91精品国产免费久久国语蜜臀| 亚洲av中文无码字幕色不卡| 亚洲AV无码精品无码麻豆| 日韩免费高清视频网站| 免费无码成人AV在线播放不卡| 在线精品自拍亚洲第一区| 久久亚洲精品无码aⅴ大香| 在线看片无码永久免费aⅴ| 免费无码VA一区二区三区| 特级aaaaaaaaa毛片免费视频| 亚洲精品视频免费在线观看| 亚洲毛片网址在线观看中文字幕 | a级亚洲片精品久久久久久久| 免费做爰猛烈吃奶摸视频在线观看| 和老外3p爽粗大免费视频| 亚洲日韩一区二区三区| 亚洲AV日韩AV高潮无码专区| 亚洲精品动漫人成3d在线 | 亚洲精品WWW久久久久久| 啦啦啦中文在线观看电视剧免费版 | 亚洲国产精品无码久久九九大片 | 国产a不卡片精品免费观看| 永久在线免费观看| 9久久免费国产精品特黄| 精品无码专区亚洲| 亚洲国产中文在线视频| 亚洲综合一区二区国产精品| 中文字幕中韩乱码亚洲大片| 又粗又大又长又爽免费视频| 在线成人a毛片免费播放|