ampq协议完整版

原创
2015/12/08 19:33
阅读数 649

AMQP.0-10中文版——概述

第1章 概述

1.1.  本文档的目标

这份文档定义了高级消息队列协议,这个协议使得遵从该协议的客户端应用和消息中间件服务器之间能够互相通信。为了完全实现互操作性,我们还定义了消息中间件服务的标准行为。

我们面对这个领域有经验的技术读者,同时还提供了足够的规范和指南,一个合适的技术工程师可以根据这些文档在任何硬件平台上用各种编程语言来构建遵从该协议的解决方案。

1.2.  专利

AMQP的设计目标之一是它的概念都来自于现有的、无产权阻碍的、广泛推行的标准——比如由互联网工程任务组和万维网颁布的标准。

因此,我们相信仅用众所周知的一些技术就能够实现AMQP服务,比如现有的开源网络程序和电子邮件路由软件或者那些技术专家们所熟悉的技术。

1.3.  摘要

1.3.1.  什么是AMQP

高级消息队列协议使得遵从该规范的客户端应用和消息中间件服务器的全功能互操作成为可能。

1.3.2.  为什么要用AMQP

我们的目标是实现一种在全行业广泛使用的标准消息中间件技术,以便降低企业和系统集成的开销,并且向大众提供工业级的集成服务。

我们的宗旨是通过AMQP,让消息中间件的能力最终被网络本身所具有,并且通过消息中间件的广泛使用发展出一系列有用的应用程序。

1.3.3.  AMQP的范围

为了完全实现消息中间件的互操作性,需要充分定义网络协议和消息代理服务的功能语义。

因此,AMQP定义网络协议(AMQP是协议!)和代理服务如下:

  1. 一套确定的消息交换功能,也就是“高级消息交换协议模型”。AMQP模型包括一套用于路由和存储消息的功能模块,以及一套在这些模块之间交换消息的规则。

  2. 一个网络线级协议(数据传输格式),客户端应用可以通过这个协议与消息代理和它实现的AMQP模型进行交互通信。

可以只实现AMQP协议规范中的的部分语义,但是我们相信明确的描述这些语义有助于理解这个协议。

1.3.4.  高级消息交换协议

1.3.4.1.  AMQP模型

我们需要明确的定义服务器的语义,因为所有服务器实现都应该保持这些语义的一致性,否则就无法进行互操作。

因此AMQP模型描述了一套模块化的组件以及这些组件之间进行连接的标准规则。

在服务器中,三个主要功能模块连接成一个处理链完成预期的功能:

  1. “exchange”接收发布应用程序发送的消息,并根据一定的规则将这些消息路由到“消息队列”。

  2. “message queue”存储消息,直到这些消息被消费者安全处理完为止。

  3. “binding”定义了exchange和message queue之间的关联,提供路由规则。

使用这个模型我们可以很容易的模拟出存储转发队列和主题订阅这些典型的消息中间件概念。

一个AMQP服务器类似于邮件服务器,exchage类似于消息传输代理(email里的概念),message queue类似于邮箱。Binding定义了每一个传输代理中的消息路由表,发布者将消息发给特定的传输代理,然后传输代理将这些消息路由到邮箱中,消费者从这些邮箱中取出消息。

在以前的中间件系统的应用场景中,发布者直接将消息发送给邮箱或者邮件列表。

区别就在于用户可以控制message queue与exchage的连接规则,这可以做很多有趣的事情,比如定义一条规则:“将所有包含这样这样的消息头的消息都复制一份再发送到消息队列中”。

AMQP模型有以下目标:

  1. 支持金融服务领域的语义要求。

  2. 支持金融服务领域所要求的性能要求。

  3. 能够很方便的扩展新的消息路由和队列。

  4. 通过AMQP协议(AMQP和AMQP Protocol的是整体和部分的关系),服务器应用可以通过编程的方式来实现具体的功能语义。

  5. 简单而灵活。

1.3.4.2.  AMQP协议

AMQP协议是一个二进制协议,拥有一些现代特点:多信道、协商式、异步、安全、跨平台、中立、高效。

AMQP通常被划分为三层:


模型层定义了一套命令(按功能分类),客户端应用可以利用这些命令来实现它的业务功能。

会话层负责将命令从客户端应用传递给服务器,再将服务器的应答传递给客户端应用,会话层为这个传递过程提供可靠性、同步机制和错误处理。

传输层提供帧处理、信道复用、错误检测和数据表示。

实现者可以将传输层替换成任意传输协议,只要不改变AMQP协议中与客户端应用程序相关的功能。实现者还可以使用其他高层协议中的会话层。

AMQP模型的设计由以下几个需求所驱动:

  1. 保证遵从AMQP规范的服务器实现之间能够进行互操作。

  2. 为服务质量提供显示控制。

  3. 支持所有消息中间件的功能:消息交换、文件传输、流传输、远程进程调用等。

  4. 兼容已有的消息API规范(比如Sun公司的JMS规范)。

  5. 形成一致和明确的命名。

  6. 通过AMQP协议可以完整的配置服务器线路(TODO:server wiring是啥意思?)。

  7. 使用命令符号可以很容易的映射成应用级别的API。

  8. 明确定义每一个操作只做一件事情。

AMQP传输层的设计由以下几个主要的需求所驱动,这些需求不分先后次序:

  1. 使用能够快速打包解包的二进制编码来保证数据的紧凑性。

  2. 能够处理任意大小的消息。

  3. 允许零拷贝数据传输(比如远程DMA)。

  4. 一个连接支持多个会话。

  5. 保证会话能够从网络错误、服务器失效中恢复。

  6. 为了长期存在,没有隐含的内置限制(TODO:To be long-lived,with no significant in-built limitations)。

  7. 异步传输消息。

  8. 能够很容易的处理新的和变化的需求。

  9. 高版本的AMQP规范能够兼容低版本的规范。

  10. 使用强断言模型来保证应用程序的可修复性。

  11. 保持编程语言的中立性。

  12. 适宜使用代码生成工具生成协议处理模块。

1.3.5.  功能范围

我们支持各种消息交换的体系结构:

  1. 存储转发(多个消息发送者,单个消息接收者)。

  2. 分布式事务(多个消息发送者,多个消息接收者)。

  3. 发布订阅(多个消息发送者,多个消息接收者)。

  4. 基于内容的路由(多个消息发送者,多个消息接收者)。

  5. 文件传输队列(多个消息发送者,多个消息接收者)。

  6. 点对点连接(单个消息发送者,单个消息接收者)。

1.4.  本文档的结构

本文档分成两个部分:

  1. “概念”部分将对AMQP的概念做一个简单的介绍,描述AMQP怎么工作,以及AMQP的用途。

  2. “标准”部分将对AMQP的模型层、会话层的每个组成部分做精确的定义,还将定义AMQP在网络上传输的二进制消息结构。

  3. 我们用IETF RFC2119中的术语定义:必须、不必、应该、不应该和可以(详见http://www.ietf.org/rfc/rfc2119.txt)。

  4. 当我们讨论遵从AMQP规范的服务器的具体行为时,我们使用术语“服务器”来表示这些服务器。

  5. 当我们讨论遵从AMQP规范的客户端应用的具体行为时,我们使用术语“客户端”来表示这些客户端应用。

  6. 我们使用“端点”来表示“服务器或者客户端”。

  7. 除非另有说明,所有数字都是十进制的。

  8. 协议中的常量都用大写字母的名字来表示。AMQP的实现如果需要在代码或者文档中定义和使用这些常量,必须用这些名字来表示。

  9. 属性名、命令或者控制参数,以及帧字段都用小写字母的名字来表示。AMQP的实现必须在代码或者文档中与之保持一致。

1.5.  约定

1.5.1.  定义

1.5.2.  版本号

AMQP版本用两个版本号表示——主版本号和次版本号。我们约定版本由主版本号后面加小数点再加上次版本号组成(比如1-3表示主版本号为1,次版本号为3)。

  1. 主版本号和次版本号可以用0到255之内的所有值。

  2. 主版本号保持不变,次版本号递增。当AMQP工作组提升主版本号时,次版本号将被设置为0。因此,有可能出现这样的版本序列:1-2,1-3,1-4,2-0,2-1……

  3. 一旦本协议发布之后(主版本号大于1),应尽量防止次版本号递增到9。不过在发布之前(版本0-x),由于会对本协议进行频繁的修订,可以不遵守这条约定。

  4. 一旦本协议发布之后(主版本号大于1),同一个主版本不同次版本的实现必须向后兼容。而在发布之前,这些次版本的实现不需要兼容。

  5. 大于或者等于99的主版本号用于测试和开发目的。

1.5.3.  技术术语

  1. AMQP模型(AMQP Model):一个由关键实体和语义表示的逻辑框架,遵从AMQP规范的服务器必须提供这些实体和语义。为了实现本规范中定义的语义,客户端可以发送命令来控制AMQP服务器。

  2. 连接(Connection):一个网络连接,比如TCP/IP套接字连接。

  3. 会话(Session):端点之间的命名对话。在一个会话上下文中,保证“恰好传递一次”。

  4. 信道(Channel):多路复用连接中的一条独立的双向数据流通道。为会话提供物理传输介质。

  5. 客户端(Client):AMQP连接或者会话的发起者。AMQP是非对称的,客户端生产和消费消息,服务器存储和路由这些消息。

  6. 服务器(Server):接受客户端连接,实现AMQP消息队列和路由功能的进程。也称为“消息代理”。

  7. 端点(Peer):AMQP对话的任意一方。一个AMQP连接包括两个端点(一个是客户端,一个是服务器)。

  8. 搭档(Partner):当描述两个端点之间的交互过程时,使用术语“搭档”来表示“另一个”端点的简记法。比如我们定义端点A和端点B,当它们进行通信时,端点B是端点A的搭档,端点A是端点B的搭档。

  9. 片段集(Assembly):段的有序集合,形成一个逻辑工作单元。

  10. 段(Segment):帧的有序集合,形成片段集中一个完整子单元。

  11. 帧(Frame):AMQP传输的一个原子单元。一个帧是一个段中的任意分片。

  12. 控制(Control):单向指令,AMQP规范假设这些指令的传输是不可靠的。

  13. 命令(Command):需要确认的指令,AMQP规范规定这些指令的传输是可靠的。

  14. 异常(Exception):在执行一个或者多个命令时可能发生的错误状态。

  15. 类(Class):一批用来描述某种特定功能的AMQP命令或者控制。

  16. 消息头(Header):描述消息数据属性的一种特殊段。

  17. 消息体(Body):包含应用程序数据的一种特殊段。消息体段对于服务器来说完全不透明——服务器不能查看或者修改消息体。

  18. 消息内容(Content):包含在消息体段中的的消息数据。

  19. 交换器(Exchange):服务器中的实体,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

  20. 交换器类型(Exchange Type):基于不同路由语义的交换器类。

  21. 消息队列(Message Queue):一个命名实体,用来保存消息直到发送给消费者。

  22. 绑定器(Binding):消息队列和交换器之间的关联。

  23. 绑定器关键字(Binding Key):绑定的名称。一些交换器类型可能使用这个名称作为定义绑定器路由行为的模式。

  24. 路由关键字(Routing Key):一个消息头,交换器可以用这个消息头决定如何路由某条消息。

  25. 持久存储(Durable):一种服务器资源,当服务器重启时,保存的消息数据不会丢失。

  26. 临时存储(Transient):一种服务器资源,当服务器重启时,保存的消息数据会丢失。

  27. 持久化(Persistent):服务器将消息保存在可靠磁盘存储中,当服务器重启时,消息不会丢失。

  28. 非持久化(Non-Persistent):服务器将消息保存在内存中,当服务器重启时,消息可能丢失。

  29. 消费者(Consumer):一个从消息队列中请求消息的客户端应用程序。

  30. 生产者(Producer):一个向交换器发布消息的客户端应用程序。

  31. 虚拟主机(Virtual Host):一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。客户端应用程序在登录到服务器之后,可以选择一个虚拟主机。

下面这些术语在AMQP规范的上下文中没有特别的意义:

  1. 主题:通常指发布消息;AMQP规范用一种或多种交换器来实现主题。

  2. 服务:通常等同于服务器。AMQP规范使用“服务器”这个术语来兼容IETF的标准术语,并且明确了协议中每个部分的角色(两方也可能是AMQP服务)。

  3. 消息代理:等同于服务器。AMQP规范使用术语“客户端”和“服务器”来兼容IETF的标准术语


第2章 AMQP模型

2.1.  AMQP模型简介

本节说明为了保证AMQP实现之间的互操作性而必须标准化的功能语义。

下面的图展示了AMQP模型的功能组件:


我们可以将中间件服务器概要的定义为:它是一个数据服务器,它接受消息并且对这些消息做两个主要的处理;它按照任意规则将这些消息路由给消费者,当消费者来不及接受这些消息时,它会将消息存储在内存或者硬盘中

以前的一些消息中间件服务器把路由和存储这两个任务都交给一个整体式的处理引擎去处理,AMQP模型将一些较小的模块化的插件来组合成更加多元化和健壮的处理引擎。AMQP将这些任务划分成两个不同的角色:

在交换器和消息队列之间有一个明确的接口组件,名为“绑定器”,稍后我们会提到这个组件。AMQP的价值来自于以下三个主要特性:

能够创建任意类型的交换器和消息队列(一些类型在标准中已经定义,用户还可以添加一些自定义的类型作为服务器的扩展)。 能够结合交换器和消息队列创建出满足任何需求的消息处理系统。 能够通过协议完全控制AMQP模型中的组件。

事实上,AMQP提供了动态编程的功能语义。

2.1.1.  消息队列

消息队列将消息存储在内存或者硬盘中,并按顺序把这些消息传递给一个或者多个消费者应用程序。消息队列是消息存储和分发的实体。每一个消息队列都完全独立。

消息队列有多个属性:私有队列或共享队列,持久存储队列或临时存储队列,永久队列或临时队列。通过选择期望的属性,我们可以用消息队列来实现传统的中间件实体,比如:

标准的存储转发队列,存储消息,并根据轮询调度的算法向多个订阅者分发消息。 临时回复队列,存储消息,并将消息转发给单个订阅者。回复队列通常是临时的,它对单个订阅者而言是私有的。 订阅队列,存储来自于不同消息源的消息,并将消息转发给单个订阅者。订阅队列通常是临时的,它对单个订阅者而言是私有的。(别跟主题弄混淆了)

这几种队列类型并没有在AMQP规范中定义,它们只是展示消息队列如何使用的例子。创建其他类型的队列实体也很简单,比如持久、共享订阅的队列。

2.1.2.  交换器

交换器接受从生产者应用程序发送的消息,并根据事先定义的规则将这些消息路由给消息队列。这些规则叫“绑定器”。交换器是匹配和路由消息的实体,也就是说,它检查消息并使用绑定器中的路由表,决定如何将消息转发给消息队列。交换器不存储消息

术语“交换器”本质上即是一类算法,也是这样一个算法的实例。更准确的说,我们称之为“交换类型”和“交换实例”。

AMQP规范定义了一些标准交换器类型,它们涵盖了消息传输过程中基本的消息路由类型。AMQP服务器将提供这些交换器的默认实例,使用AMQP的应用程序还可以添加一些自定义的交换器实例。交换器类型都有各自的名称,服务器可以根据这些名称创建出对应的交换器。交换器实例同样也有名称,应用程序可以在代码中使用这个名称来指明应用程序如何与队列绑定和发布消息。

交换器的概念是为了定义一个模型,AMQP服务器可以利用这个模型来扩展消息路由行为。

2.1.3.  绑定关键字

一般情况下,交换器会检查某条消息的消息属性、消息头字段、消息体内容,以及从源端接收到的类似数据,然后决定如何路由这条消息。

在大多数简单的情况下,交换器只检查一个关键字段,我们称之为“绑定关键字”。绑定关键字是一个虚拟地址,交换器用这个地址来决定如何路由消息。

对于点对点路由,绑定关键字是消息队列的名称。

对于主题发布订阅路由,绑定关键字是主题阶层值(TODO:topic hierarchy value,对规范还不熟悉,不知道这是什么意思)。

在更复杂的情况下,绑定关键字可能会基于消息头字段和/或消息体内容。

2.1.4.  AMQP与电子邮件的比较

如果我们和电子邮件系统做一个比较,我们会发现AMQP中的概念早已存在:

AMQP消息类似电子邮件。 消息队列类似于邮箱。 消费者类似于获取和删除电子邮件的电子邮件客户端。 交换器类似于检查邮件并决定如何路由给邮箱的电子邮件传输代理, 路由关键字对应于电子邮件中的“To:or Cc:or Bcc:”地址,不包含服务器信息(路由动作完全是在AMQP服务器内部完成的)。 每一个交换器实例都类似于单独的电子邮件传输代理进程,处理一些电子邮件子域或者是一些特殊的电子邮件通信(TODO:handling some email sub-domain, or particular type of email traffic)。 绑定器类似于邮件传输代理中路由表的一个条目。

AMQP的强大之处在于我们可以动态的添加队列(邮箱)、交换器(电子邮件传输代理)、绑定器(路由项),还可以通过不同的方式将这些组件串联在一起,这样做远比简单的将目的地址映射成邮箱名称要灵活得多。

我们不必对电子邮件和AMQP做深入比较,因为它们有本质区别。AMQP需要应付的场景是在服务器中路由和存储消息。(TODO:if only for banal reasons such as maintaining transparent performance)服务器内部路由消息和在服务器之间路由消息需要面对截然不同的问题,也有着截然不同的解决方法。

为了在AMQP服务器之间路由消息,用户必须明确建立连接桥——为了在这些分离的实体间传递消息,其中一个AMQP服务器将扮演另一个服务器的客户端。这种工作方式是为了迎合使用AMQP的企业的需要,因为创建这些连接桥可能需要考虑业务流程、合同契约和安全问题。这个模型也让AMQP“垃圾消息”难以在网络上传播。

2.1.5.  消息流程

下图展示了消息在AMQP服务器中的处理流程:


2.1.5.1.  消息生命周期

一条AMQP消息由一组消息头属性加上一个非透明的消息体组成。

生产者应用程序首先通过客户端API创建一条消息,将应用数据设置在消息体中,也许还会对某些消息属性赋值,然后为消息设置路由标签——这些标签类似于一个地址,也可能是任意结构的标识,最后将消息发送给服务器中的某个交换器。

当消息到达服务器时,交换器将这些消息路由给一组同样存在于服务器中的消息队列。如果消息无法路由,交换器可能会根据生产者的要求,直接丢弃或者拒绝这些消息,或者将它们路由给其他交换器

一条消息可以存在于多个消息队列,AMQP服务器实现可能会用不同的技术来应对这样的场景:复制消息、引用计数等,使用不同的技术并不会影响互操作性,然而,当消息路由到多个消息队列时,这条消息对于每一个消息队列而言都是相同的,没有唯一性标识来表明这条消息产生于不同的复制过程。

当消息到达某个消息队列时,消息队列立即尝试将它通过AMQP协议传递给客户端应用程序。如果消息无法传递,消息队列会保存这条消息,并且等待消费者的订阅。

可被送达的消息将会从内部缓冲区被删除。删除动作可能会立即发生,或者在订阅者成功处理且明确接受消息之后发生。订阅者决定在什么时候如何接受消息,也可以将消息放回到队列中,或者在无法处理消息的时候拒绝它。

生产者发送消息和订阅者接受消息都是事务性处理过程,当一个应用程序扮演这两个角色时——应用程序经常这样干——先发送和接受消息,然后再提交或者回滚事务。

2.1.5.2.  生产者视角

通过与电子邮件系统的比较,我们可以看到生产者并非直接将消息发送到消息队列中。直接将消息发送到消息队列会破坏AMQP模型内在的抽象性(TODO:the abstraction in the AMQP Model),就好象是允许电子邮件不经过电子邮件传输代理而直接被投递到邮箱中,这会导致管理员在生产者和邮箱之间难以插入邮件过滤器、处理程序、垃圾邮件检测程序等。

AMQP模型采用与电子邮件系统相同的原理:所有消息被发送到交换器,交换器根据规则信息检查消息,然后将它们路由给其他组件,交换器的这两个动作对于用户来说都是透明的。

2.1.5.3.  消费者视角

当我们以消费者的视角来审视AMQP时,我们会发现一些它与电子邮件的不同之处。电子邮件客户端都被动接受邮件——它们读取邮箱中的电子邮件,但是它们无法决定邮箱应该存放哪些电子邮件。AMQP客户端也可以像电子邮件客户端一样被动的接受消息,也就是说,我们可以写一个应用程序,这个应用程序被动的接收某个消息队列中的消息。

然而,我们也允许AMQP客户端执行以下三个操作:

就像是我们拥有这样的邮件系统:

我们可以看到AMQP更像是一种编程语言,它能够定义AMQP模型中的各个组件如何连接和交互。这也是我们的目标之一——通过协议实现系统行为的可编程化。

2.1.5.4.  默认流程

大多数集成系统不需要这么精确的定义消息流程。就像针对业余摄影爱好者一样,大多数AMQP用户需要“全自动”模式。AMQP使用两个简单的概念来实现这一点:

事实上,默认的绑定器使得生产者直接将消息发送到消息队列中,它模拟了传统消息中间件中最简单的“发送到目的地”消息分发机制。

2.2.  虚拟主机

虚拟主机由它自己的名字空间和一组交换器、消息队列和所有关联的对象组成。每个连接必须和某个虚拟主机关联。

客户端在认证之后选择虚拟主机,这要求服务器上所有虚拟主机共享服务器的授权策略,对每一个虚拟主机而言,授权策略可以是唯一的。

同一连接中所有信道都和同一个虚拟主机通信。同一个连接既不可能与不同的虚拟主机同时通信,也不可能不重建连接就直接切换连接到另一个虚拟主机。

协议没有提供创建或者配置虚拟主机的机制,这些机制完全由服务器自定义。

2.3.  交换器

交换器是虚拟主机中的路由代理。交换器实例(俗称“一个交换器”)接受消息和路由信息(路由关键字),然后将消息发送给消息队列或者是一些AMQP服务器厂商扩展的内部服务。对于每个虚拟主机内部,交换器有独一无二的名字。

应用程序在其权限范围之内可以自由的创建、共享、使用和销毁交换器实例。

交换器可以是持久的、临时的或者自动删除的。持久交换器会一直存在于服务器,直到它被显式删除;临时交换器会工作到服务器被关闭时为止;而一旦没有应用程序使用自动删除交换器,它就会被服务器自动删除掉。

服务器提供了一组特定的交换器类型,每一个交换器类型都实现了一种特定的匹配和路由算法,我们将在下一节介绍这些算法。AMQP要求服务器实现至少实现一小部分交换器类型,并且建议实现更多的类型以满足应用的需要,每一个服务器实现还可以实现其他自定义交换器类型。

交换器能够并行的将一条消息路由到多个消息队列,这将创建多个被独立消费的消息实例。

2.3.1.  交换器类型

每一个交换器类型都实现了一种特定的路由算法。后面将会介绍一些标准交换器类型,但是有两种类型特别重要:

请注意:

2.3.1.1.  直接式交换器类型 (Direct Exchange)

直接式交换器类型提供了这样的消息路由机制:通过精确匹配消息的路由关键字,将消息路由到零个或者多个队列中,绑定关键字用来将队列和交换器绑定到一起。这让我们可以构建经典的点对点队列消息传输模型,不过和任何已定义的交换器类型一样,当消息的路由关键字与多个绑定关键字匹配时,消息可能会被发送到多个队列中。

直接式交换器的工作方式如下:

服务器必须实现直接式交换器类型,必须在每一个虚拟队列中事先声明至少两个直接式交换器:一个名为“amq.direct”,一个没有公共名称——作为向其他服务器传输消息的默认交换器(无名交换器)。值得注意的是,消息队列可以使用任意合法的绑定关键字,但是通常会使用它们自己的名称作为绑定关键字

特别强调的是,所有使用各自消息队列名称作为绑定关键字的消息队列必须自动和无名交换器绑定在一起

2.3.1.2.  广播式交换器类型(Fanout Exchange)

广播式交换器类型提供了这样的路由机制:不论消息的路由关键字是什么,这条消息都会被路由到所有与该交换器绑定的队列中。

广播式交换器类型的工作方式如下:

不使用任何参数将消息队列与交换器绑定在一起。 发布者(直接式交换器类型描述中的producer变成了publisher,已经隐含了二种交换器类型的区别)向交换器发送一条消息。 消息被无条件的传递到所有和这个交换器绑定的消息队列中。

服务器必须实现扇出式交换器类型,必须在每一个虚拟队列中事先声明至少一个扇出式交换器:名为“amq.fanout”。

2.3.1.3.  主题式交换器类型(Topic Exchange)

主题式交换器类型提供了这样的路由机制:通过消息的路由关键字和绑定关键字的模式匹配,将消息路由到被绑定的队列中。这种路由器类型可以被用来支持经典的发布/订阅消息传输模型——使用主题名字空间作为消息寻址模式,将消息传递给那些部分或者全部匹配主题模式的多个消费者。

主题交换器类型的工作方式如下:

绑定关键字用零个或多个标记构成,每一个标记之间用“.”字符分隔。绑定关键字必须用这种形式明确说明,并支持通配符:“*”匹配一个词组,“#”零个或多个词组。

因此绑定关键字“*.stock.#”匹配路由关键字“usd.stock”和“eur.stock.db”,但是不匹配“stock.nasdaq”。

这种交换器类型是可选的。

服务器应该(不是必须)实现主题式交换器类型,在这种情况下,服务器必须事先在每一个虚拟主机中定义至少一个主题式交换器:名为“amq.topic”

2.3.1.4.  消息头式交换器类型

消息头式交换器类型提供了复杂的、多重部分表达式路由,它的路由机制基于AMQP消息头属性。

消息头式交换器的工作方式如下:

消息队列根据一个参数列表与交换器绑定,列表中的参数包括被匹配的消息头字段和一些可选的字段值。 发布者向交换器发送一条消息,这条消息的消息头属性包含了名值列表。 如果消息头中的名值列表与参数列表匹配,消息将被传递给对应的消息队列。

匹配算法由参数列表中的某个特殊的名值对所控制,这个参数的名称是“x-match”,它能取一到两个值,用来决定如何匹配其他名值:

如果符合以下两种条件之一,绑定参数中的一个字段与消息头中的一个字段匹配:(1)绑定参数的某个字段没有值,且消息头有这个属性字段;(2)绑定参数的某个字段有值,且消息头中有这个属性字段并具有相同的值。

除了‘x-match’,以“x-”开头的字段都作为保留字段,目前这些字段会被服务器直接忽略。

服务器应该(不是必须)实现消息头式交换器类型,在这种情况下,服务器必须事先在每一个虚拟主机中定义至少一个消息头式交换器:名为“amq.match”

2.3.1.5.  系统交换器类型

系统交换器的工作方式如下:

AMQP规范以“amq.”开头的系统服务名称,这些名称作为保留名称,其他所有名称都可以由服务器实现任意使用。这个交换器类型是可选实现的。

2.3.1.6.  自定义交换类型

所有没有在规范中定义的交换器类型的名称必须以“x-”开头。不以“x-”开头的交换器类型作为保留类型,被AMQP标准在将来使用。

2.3.2.  交换器生命周期

每一个AMQP服务器都会事先创建一些交换器(更准确一点,“交换器实例”)。这些交换器会一直存在与服务器中,它们不能被销毁

AMQP应用程序也能创建它们自己的交换器,AMQP并不使用“create”之类的命令,它使用“declare”命令,这意味着“如果不存在就创建,否则继续”。应用程序可以创建一个私有的交换器,并且当它们的工作完成时删除这些交换器。AMQP提供了一个命令来删除交换器,但是一般应用程序不会这样做。

在本章的例子中,我们假设所有交换器都在服务器启动时被创建出来。我们不会展示应用程序如何声明交换器。

2.4.  消息队列

消息队列是一个具名缓冲区,它们代表一组消费者应用程序保存消息。应用程序在其权限范围之内可以自由的创建、共享、使用和消费消息队列。

消息队列提供了有限制的先进先出保证。服务器会将从某一个生产者发出的同等优先级的消息按照它们进入队列的顺序传递给某个消费者,万一某些消息不能被消费者处理,它们可能会被打乱顺序重新传递。

消息队列可以是持久的、临时的或者自动删除的。临时消息队列会工作到服务器被关闭时为止;而一旦没有应用程序使用自动删除消息队列,它就会被服务器自动删除掉。只要用户(客户端)拥有相应的权限,任何队列都可以被显式删除。

消息队列将消息保存在内存、硬盘,或者这两种介质的组合之中。

消息队列限定在虚拟主机范围之中。

队列名必须包含1到255个字符。队列名首字符限定为字母a-z或者A-Z,数字0-9,或者下划线‘_’;接下来的其他字符必须是合法的UTF-8字符。

消息队列保存消息,并将消息分发给一个或多个订阅客户端。

消息队列会跟踪消息的获取情况,消息要出队就必须被获取(acquire和consume是两个动作,先执行acquire,相当于对消息加锁)。这阻止了多个客户端同时获取和消费同一条消息,也可以被用来做单个队列多个消费者之间的负载均衡。

如果消息被客户端释放或者最初被发送的消息没有被获取,一个队列中的消息可能会被发送给多个客户端。为了允许客户端能够安全的浏览队列中的内容,消息队列可能会将未被获取的消息分发给客户端。

2.4.1.  消息队列属性

当客户端应用程序创建消息队列时,它可以为这个消息队列选择几个重要的属性:

名称——当应用程序共享一个消息队列,它们预先协商好的消息队列名。 持久的——如果指定这个属性,消息队列会在服务器重启之后仍然能够继续工作。不过在重启之后,消息队列会丢失非持久消息。 自动删除的——如果指定这个属性,当所有客户端不再使用这个队列时,服务器会立即或者不久之后把这个队列删除掉。 

2.4.2.  消息队列生命周期

下面是两种主要的消息队列生命周期:

持久消息队列,在多个订阅者之间共享,独立存在——也就是说,不管有没有订阅者接收消息,它们都会持续存在并收集消息。临时消息队列,私有的,只有某个订阅者可以使用,当订阅者退出连接后,消息队列会被删除掉。

还有些其他类型的消息队列生命周期,比如共享消息队列,当最后一个订阅者退出连接后,消息队列会被删除掉。

下图展示了临时消息队列的创建和删除过程:


2.5.  绑定器

绑定器表示消息队列和交换器之间的关联关系(对于实现者和用户而言,binding应该是一个实体,而不是抽象的relationship)。绑定器指定了路由参数,这些参数告诉交换器哪些消息可以发送给队列。

应用程序根据需要创建和销毁绑定器,驱动消息流入消息队列。绑定器的生存期由使用它的消息队列和交换器决定,当消息队列或者交换器被销毁,绑定器也会被销毁。

客户端应用程序使用命令构造绑定器,用来绑定它正在使用的消息队列和交换器。我们可以下面这段伪代码来表示一个绑定命令:

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

Exchange.Bind的特殊语义取决于交换器类型。

让我们来看看三个典型的用例:共享队列、私有回复队列和发布订阅队列。

2.5.1.  构造共享队列

共享队列是典型中间件中的点对点队列。在AMQP中,我们可以使用默认的交换器和默认的绑定器,假设我们的消息队列名为“app.svc01”,我们下面的伪代码来创建共享队列:

Queue.Delcare

queue=app.svc01

exclusive=FALSE

可能会有多个消费者使用这个共享队列,假设每个消费者都用下面的伪代码消费队列中的消息:

Message.Subscribe

queue=app.svc01

为了向共享队列发布消息,每个生产者用下面的伪代码向交换器发送消息。

Message.Transfer

routing_key=app.svc01

2.5.2.  构造回复队列

回复队列通常属于临时队列,它们也通常属于私有队列,也就是说它们只被某一个订阅者使用。除了这些特点,回复队列使用和标准队列相同的匹配规则,因此我们同样可以使用默认交换器。为了避免不同的客户端使用的临时队列的名字冲突,建议客户端在队列名中包含一个全局唯一标识符(在RFC-4122中定义)或者其他全局唯一标识符。

客户端使用下面的伪代码创建一个回复队列:

Queue.Declare

queue=tmp.550e8400-e29b-41d4-a716-446655440000

exclusive=TRUE

auto_delete=TRUE

为了向回复队列发布消息,生产者用下面的伪代码向默认交换器发送消息:

Message.Transfer

routing_key=tmp.550e8400-e29b-41d4-a716-446655440000

2.5.3.  构造发布订阅队列

在经典消息中间件中,单词“订阅”的意义有点模糊,它至少涉及到两个不同的概念:一组匹配消息的规则和存储匹配消息的临时队列。AMQP将这些工作划分开,由绑定器和消息队列共同完成。

通过匹配主题、消息字段或者消息内容等不同方式,发布订阅队列从多个不同的消息源收集消息。发布订阅队列和有名或回复队列之间的关键区别在于:发布订阅队列的名称并不用于路由,它采用抽象的匹配规则完成路由操作,而不是通过路由关键字与队列名一对一匹配的方式。

让我们来看看什么是发布订阅主题树以及如何实现它。我们需要一个交换器类型能够在主题树上做匹配操作。在AMQP规范中,这种类型是主题交换器类型。主题交换器用类似于“STOCK.USD.*”的通配符扩展绑定关键字来匹配类似于“STOCK.USD.*”的路由关键字。

我们不能使用默认交换器或绑定器,因为它们不支持主题式路由。因此我们显式创建一个绑定器。下面这段伪代码创建和绑定一个发布订阅队列:

Queue.Declare

queue=tmp.2

auto_delete=TRUE

Exchange.Bind

exchange=amq.topic

TO

queue=tmp.2

WHERErouting_key=STOCK.USD.*

一旦绑定器被创建,消息将从交换器路由到消息队列,然而,消费者必须从队列中消费消息:

Message.Subscribe

queue=tmp.2

为了向发布订阅队列发布消息,生产者用下面的伪代码发送消息

Message.Transfer

exchange=amq.topic

routing_key=STOCK.USD.IBM

主题式交换器使用它的绑定列表来匹配消息中的路由关键字(“STOCK.USD.IBM”),一旦匹配成功,它将消息路由到订阅队列中。

2.6.  消息

消息是路由和入队原子单位。消息由消息头和消息体组成,其中消息头包含一组明确定义的属性,消息体是一段对于队列而言不透明的二进制数据。

消息可以是持久的——就算是遇到网络故障、服务器崩溃和服务器过载等一系列问题,服务器也会将持久消息安全的保存在硬盘中,并且保证这些消息会被传输给客户端。

消息可以有优先级,在同一个消息队列中的高优先级消息可能会比低优先级的消息更早被发送给客户端。当消息必须被丢弃时,服务器会先丢弃低优先级消息。

服务器不会更改消息体,但是可能会在转发消息给消费者应用程序之前更改某些消息头属性。

2.6.1.  流控制

根据接收方的可用资源,流控制可以被用来匹配消息的传输速率。接收者可能是从客户端接收消息的AMQP服务器,也可能是从服务器消息队列中接收消息的客户端。这两种情况都采用同样的流控制机制。流控制一般采用信用度的概念来表示发送者还能发送多少消息或者多少字节数据,当发送消息或者数据后,信用度降低,当接收者释放被占用的资源后,信用度提高。预取缓冲区可以被接收者用来减少延迟。

2.6.2.  传输响应

当消息被接收时,消息接收者向发送者发回一个信号。当客户端向服务器发送一条消息时,服务器向客户端发回一个接受消息,表明服务器已经成功将消息路由到队列中。当服务器向客户端发送一条消息时,客户端向服务器发回一个接受消息,表明客户端已经成功处理了该消息,并通知服务器从队列中删除该消息。AMQP规范支持两个不同的接受模式:

显式,接收应用程序必须为其接收到的每条消息或者每批消息向发送者发回一条接受消息。 None(TODO:在AMQP中,有accept-mode和acquire-mode,它们之间互相影响),认为消息被发送出去时就已经被接受了。

2.7.  订阅

我们使用单词“订阅”来表示实体:控制某个客户端应用程序如何接收队列中的消息。这和发布订阅消息传输中的订阅者概念并不冲突。当客户端“启动一个订阅”,它在服务器上创建一个订阅实体。当客户端“取消一个订阅”,它在服务器上销毁一个订阅实体。

订阅属于单独的客户端会话,并导致消息队列将消息异步的发送给客户端。

2.8.  事务

AMQP规范定义了两种截然不同的事务模型,单阶段提交事务模型(称为tx)和两阶段提交事务模型(称为dtx)。标准的单阶段提交事务模式的作用域为单个会话,客户端可以选择会话是否是事务性的(使用tx.select命令),一旦选择事务性会话,会话会一直保持事务性直到它被销毁。

一旦选择事务性会话,由客户端发出的所有指示会话传输和接受消息的命令只会在发出tx.commit命令之后生效。其他改变服务器状态的命令并非事务性命令,因此也无法回滚,比如声明队列和交换器的命令就是非事务性命令。

如果AMQP客户端在一个事务中向服务器发布一条消息(使用message.transfer命令),然后消息会被交换器路由到队列中,直到这个事务完成之后才会从队列中被传递给其他端点。服务器在消息被发送的时候(而非事务被提交的时候)就决定消息是否能被路由到队列中。服务器拒绝接受某条消息不会导致事务回滚,也不会等到commit命令生效的时候才执行,而是在消息被发送到服务器上的时候就立即执行。

事务回滚对服务器所造成的影响是:客户端发出的发布和接受的命令都会被丢弃。需要注意的是获取消息的命令并非事务性操作,因此在事务作用域中的客户端仍然可以获取消息。这意味着事务回滚之后,客户端仍然持有所有在事务中服务器传递给它的消息(无论客户端是否已接受)。如果客户端希望服务器能够重新分发这些消息,它必须发出对应消息的释放命令。(事务回滚时,消息的状态是acquired,见2.6.2节,此时如果希望服务器能够重新分发这些消息,必须发出message.release的命令)

2.9.  分布式事务

分布式事务支持X-Open XA架构。

dtx类被用来限定和协调事务。dtx.start和dtx.end命令限定在会话中一个AMQP事务的范围。事务的协调和恢复功能由其他dtx类中的命令提供。

OMG OTS和JTS/JTA模型依赖于“资源管理器客户端”实例,这个实例提供了操作全局事务中服务器资源的XA接口的实现。这些资源管理客户端实例由Rmid唯一标识,这个标识贯穿c/c++中的xa_switch或者java中的XAResource的使用过程。

如下图所描述的,一个资源管理器使用XA接口来限定事务的范围和协调事务的结果。当资源管理器客户端使用dtx.start和dtx.end命令参与到会话中的某个事务时,应用程序才能以事务性的方式发送和接收消息。资源管理器客户端使用dtx的相关命令来协调事务结果和恢复AMQP服务器的操作。图中的协调会话就是为了这个目的。


2.9.1.  分布式事务场景

下图展示了应用程序以事务方式从队列Q1消费消息的场景(通过事务管理器TM使用事务T1)。根据消费到的消息,应用程序使用数据库管理系统更新数据库表Tb,然后以事务方式向Q2发送一条消息。



3.1 Session定义
Session 是AMQP对等端之间的命名的交互,session 名局限于一个认证机构(authentication principal),并且session名字由应用层程序来决定。在参与交互的对端之一,或者两者上可能保存session的状态。发布一个消息,创建一个队列,或者选择一个事务模式的命令必须发生在一个session的context之内。
Session是AMQP其他部分的基础。

Session 可以被看做:
AMQP内建的刚好一次递交操作的context
网络协议映射和model层之间的接口
诸于队列,交换器,订阅这些实体的生命周期的一个范围域
命令标识符的一个范围域

3.1.1 session 生命期
Session 不是显式地被创建或者被摧毁,在某种意义上,session 一直存在。对等端必须试着去附着(attach)到它的对端的session上,而不是去创建一个session。然后,这个附着请求的接收者会去查看它是否正持有该session的状态。

对端双方在附着到一个session上,它们必须保留session的相关状态。当session分离了(不管是通过显式的分离请求,还是对等端之间的网络连接断开),那么session的状态还会被保留一段时间,这个时间是由先前对等端双方协定好的。

3.1.2 命令传输
AMQP模型的交互是通过在对等端之间发送”命令”。这些命令高效地在session上发送。当一个命令从model层到session时,它被授予一个标识符。这些标识符能够用来把命令和其结果关联起来,或者在异步AMQP命令流上执行同步。

3.1.3 session 作为一个层次
Session 充当了网络协议映射层和model层之间的接口。尤其是,当对等端双方都保留session的状态时,它可以被用来作为一个机制,保证命令刚好递交一次。
Session state 至少由如下组成:
一个replay 缓存区,存放对等端还没有确认收到的整个命令,或者命令的一部分
一个幂等的栅栏 – 一组命令标识符,对等端知道这些命令已经接收到了,但是不能确定对方是不是会重发。

因为session名是由应用层程序指定的,除了这里详细说明的状态之外,可能还有其他更多的状态。这些额外的状态可能(例如)在session状态过期是,会被用来执行恢复。然而,在这章,当我们谈到session状态,我们将只会指session层持有的那些状态。

3.2 session 的功能
Session 层提供了几个关键的服务,model层构建在这些服务之上:
命令顺序化标识
将要执行命令的确认
命令完成时发通知
网络失败的重放,恢复
对端失败后,状态协调

3.2.1 顺序化的标识
对等端发出的每个命令必须被标识,为了在整个系统内,能保证命令刚好一次执行。
Session层使用一个顺序编号的机制来标识每个命令,使得命令在一个session内是唯一的。

标识这个概念,使得可以异步地把命令和起结果关联起来。Model层可以看到命令的标识符。当一个命令的结果返回时,可以用命令标识符来关联到是哪个命令的执行结果。

3.2.2 确认
为了使对等端能够安全地丢弃一个给定命令的状态,它必须接收到一个该命令将会被执行的保证。更精确地说,发送者必须接收到一个确认该命令已经被执行,或者它的递交被保留到期望的时间长度。

实际上,你可能会通过一个消息传输系统发送两种类型的消息:持久消息和瞬间消息。对于瞬时消息,一般来说,消息传输系统和应用程序之间的协定是,消息可能在消息传输系统丢失瞬时状态时,消息可能会被丢失。对于一个持久消息,消息传输系统必须保证消息被保存在最持久的的存储中。

Session 层处理消息确认的发送和接收。这使得session层可以管理它需要持有的状态,能够在对等端临时失败时,或者传输失败时,通过这些状态能够进行恢复。

确认可以批量或者不确定地延迟。尤其是如果一个对等端不需要紧急的确认通知,确认可以被忽略掉,因为它仅仅是意味着一个命令的完成。

3.2.3 命令完成
从确认概念独立出来的是命令完成概念。为了同步和确保不同session间的完全有序化的目的,因此很有必要在一个命令执行完成时通知到对等端。

在对等端没有请求紧急完成通知的地方,比如通知可以延迟或者可以批量发送,这适用于一命令范围。这减小了网络的流量。

例如,如果3个队列按顺序声明,如命令1到3。服务器可能会像如下这下通知命令完成

注意,命令的接收方将会发送整个已经完成的命令集(这里不明白啥意思,比较难翻)

3.2.4 重放和恢复
一般地,AMQP系统应该能够处理临时的网络失败,或者AMQP 服务器cluster 中的单点失败。为了能够从这些失败中幸存,session必须用来重放在失败点接收有疑问的命令。Session 层提供必要的工具来标识有疑问的命令集,并且在无重复递交风险的情况下重放这些命令。

展开阅读全文
打赏
1
3 收藏
分享
加载中
更多评论
打赏
0 评论
3 收藏
1
分享
返回顶部
顶部