AMQP 0-9-1 Model Explained--原文翻译


声明:本文转载自https://my.oschina.net/u/2293359/blog/1542450,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

关于本文

翻译自 RabbitMQ 官网,原文地址 看这里

本文提供AMQP 0-9-1 协议的概述,AMQP 0-9-1 是 RabbitMQ 支持的协议之一。

1. AMQP 0-9-1 和 AMQP 模型高度概括

1.1 什么是 AMQP 0-9-1 ?

AMQP 0-9-1(高级消息队列协议)是一个消息协议,它确保符合协议标准的客户端应用程序与符合协议标准的消息中间件代理之间能够进行通信。

1.2 消息代理及其作用

Messaging brokers(以下称为消息代理)从 publishers(发布消息的应用程序,也被称为 producers,以下称为生产者)接收消息,然后将消息路由到 consumers(处理消息的应用程序,以下称为消费者)。

因为它是一个网络协议,生产者、消费者和消息代理可能都部署在不同的机器上。

1.3 AMQP 0-9-1模型简述

AMQP 0-9-1 模型的整体视图如下:

  • 消息被发送到 exchanges,通常可以把 exchanges 比作邮局或者信箱;
  • exchanges 会将消息的副本根据指定的规则(bindings)分发给 queues;
  • AMQP 消息代理可以将消息投递给订阅了队列的消费者,同时消费者也可以根据需要主动从队列拉取消息。

当发布消息时,生产者可以指定多个消息属性(即消息元数据)。部分消息元数据可以被代理使用,但是其余的元数据对于代理来说完全是不可见的,仅仅只能被接收这条消息的应用程序使用。

网络是不可靠的,应用程序也可能无法处理消息,所以 AMQP 模型有消息确认的概念:当消息确实成功投递到了消费者时,消费者会自动通知或者按照开发人员指定的方式通知到消息代理。在使用了消息确认机制的场景下,代理只有在接收到了指定消息(或消息组)的确认通知后才会将消息从队列中完全移除。

某些情况下,比如有的消息无法路由,消息可能会退回给生产者,或者被丢弃,或者如果代理实现了被称为“dead letter queue”的扩展队列的话,被放置到该队列中。生产者在发布消息时可以使用特定参数选择具体使用哪种处理方式。

Queuesexchanges 以及 bindings 统称为 AMQP 实体,以下分别称为队列交换器绑定规则

1.4 AMQP是一个可编程协议

AMQP 0-9-1 是一个可编程协议,AMQP 实体和路由选择方案都主要由应用程序自身而非由代理管理员来定义。相应地,为声明队列和交换器,定义它们之间的绑定规则,以及订阅队列等协议操作制定了相关规定。

这给了应用程序开发者极大的自由,但同时也要求开发者小心潜在的定义冲突。实际上,定义冲突很少出现,通常来说仅仅是配置错误。

应用程序声明需要的 AMQP 0-9-1 实体,定义必要的路由选择方案,也可以删除不再需要的 AMQP 实体。

2. 交换器和交换器类型

交换器是消息发送端的 AMQP 实体。交换器接收一条消息并将其路由到0个或者多个队列,根据交换器类型和绑定规则的不同,会采用不同的消息路由算法。AMQP 0-9-1 代理提供四种类型的交换器:

| Name | Default pre-declared names | | :--- | :--- | | Direct exchange | (Empty string) and amq.direct | | Fanout exchange | amq.fanout | | Topic exchange | amq.topic | | Headers exchange | amq.match (and amq.headers in RabbitMQ) |

除了类型外,交换器还声明了很多其他属性,其中最重要的是:

  • Name 交换器名
  • Durability 是否持久化,即当代理重启时是否保留该交换器
  • Auto-delete 是否自动删除,即当交换器上没有绑定队列时是否自动删除该交换器
  • Arguments 可选参数,插件和代理特定特性使用

交换器可以是持久化的也可以是临时的。当代理重启时,持久交换器会保留下来,而临时交换器需要在代理重启后重新声明才能继续使用。并非所有的应用场景和使用情况都需要持久化交换器。

2.1 默认交换器(Default Exchange)

默认的交换器是直接交换器,由代理预先声明,并且不指定交换器名(声明为空字符串)。每一个队列在创建时都会自动绑定到默认交换器,并以队列名称作为 routing key,这一特性对于简单应用来说尤其有用。

例如,当声明一个名为“search-indexing-online”的队列时,AMQP 0-9-1 消息代理会将其绑定到默认交换器,并将“search-indexing-online”作为 routing key。所以,当一条消息被发送到默认交换器,并且指定 routing key 为“search-indexing-online”时,就会被路由到队列“search-indexing-online”。也就是说,使用默认交换器的场景看起来像是可以不通过交换器,直接将消息投递到队列,当然实际上交换器在消息传递过程中仍是必不可少的。

2.2 直接交换器(Direct Exchange)

直接交换器根据 routing key 将消息投递到队列,适用于消息单播路由的场景(当然组播路由的场景下也可以使用)。工作原理如下:

  • 队列通过 routing key K 绑定到交换器
  • 当指定了 routing key R 的消息到达直接交换器时,如果 K = R,交换器就会将消息路由到 K 对应的队列

直接交换器通常以循环的方式在多个工作组(相同应用的多个实例)之间进行任务分发。在这种使用方式下,理解在 AMQP 0-9-1 协议中,消息是在消费者之间而不是在队列之间进行负载均衡,这一点很重要。

直接交换器可以通过下图进行表示:

2.3 扇出交换器(Fanout Exchange)

扇出交换器会将消息路由到所有与其绑定的队列,不管生产者有没有指定 routing key。如果有 N 个队列绑定到扇出交换器上,当有新消息到达扇出交换器时,消息的副本会被投递给所有这 N 个队列。扇出交换器适用于消息的广播路由。

扇出交换器将消息的副本投递给与它绑定的每一个队列,典型的使用场景:

  • 巨型多人同时在线游戏(MMO)更新排行榜或其他全局事件
  • 体育新闻网站向手机端接近实时地推送比分变更
  • 分布式系统广播多种状态和配置更新
  • 群聊会话中在群成员之间分发群消息(AMQP 没有内置这种场景,所以使用 XMPP 会是一个更好的选择)

扇出交换器可以通过下图进行表示:

2.4 主题交换器(Topic Exchange)

主题交换器基于消息的 routing key 和队列到交换器的绑定模式之间的匹配来把消息路由到一个或者多个队列,通常用于实现各种发布/订阅模式的变更。主题交换器适用于消息的组播路由。

主题交换器有非常广泛的使用场景。任何时候,当遇到多个消费者或者应用程序有选择性的挑选它们需要接收的消息类型时,都可以考虑使用主题交换器。使用示例如下:

  • 发布与特定地理位置相关的数据,比如销售点
  • 由多个工作组处理完成的后台任务,每个工作组能够处理特定的任务集
  • 股票价格更新(或者其他种类的金融数据更新)
  • 包含分类或者标签的新闻更新(例如只更新特定运动或者队伍的新闻)
  • 云端不同种类服务的编排
  • 分布式体系结构或者操作系统软件的构建或打包,每个构建器只能处理一种体系结构或OSzhu

2.5 报头交换器(Headers Exchange)

报头交换器为根据多个属性进行路由而设计,这些属性更容易被表述成消息头,而不是 routing key。报头交换器会忽略 routing key,而从消息报头属性获取值来进行路由。如果报头属性的值与绑定规则定义的值匹配,消息就认为能匹配到报头交换器。

可以通过匹配多个消息头来将队列和报头交换器进行绑定。在这种情况下,消息代理需要从应用程序开发者手中获取额外的信息,即,它应该在有任意一个消息头匹配时就选取消息,还是需要在所有的消息头都匹配时才选取消息。“x-match”绑定参数就是用来指定这个的。当“x-match”参数设置为“any”时,有一个消息头能够匹配就足够,否则,当“x-match”参数设置为“all”时,所有的值都必须匹配。

报头交换器可以被看作是“更加强大的直接交换器”。因为报头交换器根据报头值来路由消息,当 routing key 不一定要是字符串,而可以是像 integer 或者 hash (dictionary) 等值时,报头交换器可以当作直接交换器来使用。

3. 队列(Queues)

AMQP 0-9-1 模型中的队列与其他消息/任务队列系统非常相似:它们保存将被应用程序消费的消息。队列与交换器共享一些属性,但队列也有一些额外属性:

  • 队列名称
  • 持久化(在消息代理重启后仍然保留队列)
  • 唯一性(只被一个连接使用,并且当这个连接关闭时删除队列)
  • 自动删除(已经有多个消费者订阅的队列,在最后一个消费者取消订阅时自动删除)
  • 参数(可选参数,插件和代理特定特性使用,例如 消息TTL,队列长度限制等)

队列在使用之前必须先声明。声明时如果队列不存在,会触发创建队列操作;反之当队列已存在并且队列属性与声明一致时,不会有任何操作;当已经存在的队列属性与声明不一致时,会返回 channel 级的异常,错误码406(PRECONDITION_FAILED)。

3.1 队列名称(Queue Names)

应用程序可以选择自己指定队列名称或者交由消息代理生成队列名称。队列名称最多可以由255字节的UTF-8字符组成。AMQP 0-9-1 消息代理会生成标识应用的唯一队列名称,要使用该特性,在声明队列时,队列名称参数传空字符串,生成的队列名称会与声明返回值一起返回给客户端。

以“amq.”开头的队列名称保留给消息代理内部使用,尝试声明队列名称违反这条规则的队列会返回 channel 级的异常,错误码403(ACCESS_REFUSED)。

3.2 队列持久化(Queue Durability)

持久化的队列保存到了磁盘,所以能在代理重启后仍然保留。不能持久化的队列叫做瞬时队列。并非所有的场景和用例都需要队列持久化。

队列持久化并不会使路由到这个队列的消息也持久化。如果消息代理关闭并重启,持久化队列将在代理启动期间重新声明,然而,只有持久化的消息能被恢复。

4. 绑定规则(Bindings)

绑定是交换器用来(和其他属性一起)路由消息到队列的规则。指定交换器 E 路由消息给队列 Q,即 Q 被绑定到 E。某些交换器类型会使用绑定可能会有的一个可选值 routing key,用于选取发送给交换器的特定消息,并路由到绑定的队列。也就是说,routing key 类似于过滤器。 打一个比方:

  • 如果队列是终点纽约
  • 交换器就是肯尼迪机场
  • 而绑定规则就是从肯尼迪机场到纽约的航线,可以有多种方式到达目的地。

拥有绑定规则这个间接层,能使一些不可能或者很难通过直接发送消息到队列的方式实现的路由场景成为可能,也消除了一定的应用程序开发者的重复工作。

如果 AMQP 消息无法路由给任何队列(例如,消息发布到的交换器没有任何绑定规则),根据生产者发布消息时设置的属性,消息将会被丢弃或者退回给生产者。

5. 消费者(Consumers)

如果应用程序无法消费的话,将消息存储在队列中就没有意义。在 AMQP 0-9-1 模型中,应用程序有两种方式消费消息:

  • 消息被传递给消费者(消息推送 API)
  • 当消费者需要时自己获取消息(消息拉取 API)

使用消息推送API时,应用程序需要先表明希望从哪个队列消费消息,我们称这种行为为向队列注册了一个消费者,或者简单来说就是订阅了队列。同一个队列可以有多个消费者,也可以指定排他消费者(当排他消费者在消费时,会排除所有其他的消费者)。

每个消费者(或者订阅)有一个称为消费者标签的标识符,可以用来退订消息。消费者标签仅仅是字符串。

5.1 消息确认(Message Acknowledgements)

消费者应用(接收和处理消息的应用程序)有时候会出现个别消息处理失败或者应用本身宕机的情况,也有可能因为网络原因导致消费问题。那么,AMQP 消息代理何时才能从队列中移除已发送的消息呢?AMQP 0-9-1 规范提供了两种方式:

  1. 在消息发送给消费者应用后就移除消息(使用basic.deliver或者basic.get-ok方法)
  2. 等待消费者应用返回消息确认才移除消息(使用basic.ack方法)

第一种方式称为自动确认模型,而第二种方式称为明确确认模型。使用明确确认模型时,由消费者应用选择何时发送消息确认。可以在接收到消息后立即发送确认,也可以在完成了消息保存,等待进行消息处理之前发送确认,或者也可以等消息的处理流程全部完成(例如,成功取到一个Web页面,对页面进行处理并将其保存到持久化数据存储中)再发送确认。

如果消费者没有发送消息确认就宕机了,AMQP消息代理就会把未确认的消息再投递给其他的消费者,在没有其他消费者时,消息代理在重新投递该消息之前会持续等待,直到再有消费者注册到这个队列。

5.2 消息拒绝(Rejecting Messages)

当消费者应用接收到一条消息后,对消息的处理可能成功也可能不成功。消费者应用可以通过拒绝消息告知代理消息处理失败。消费者应用可以要求在拒绝消息之后,代理对消息进行丢弃或者重新发送。当队列只有一个消费者时,注意避免对同一个消费者的消息进行反复地拒绝和重发从而使消息投递陷入死循环当中。

5.3 否定确认(Negative Acknowledgements)

可以通过basic.reject AMQP 方法来拒绝消息,但有一个限制:消息拒绝不像消息确认一样可以批量操作,从而同时拒绝多条消息。然而,如果你使用的是 RabbitMQ 的话,可以有一个解决方案,RabbitMQ 提供一个被称为否定确认(nacks)的AMQP 0-9-1扩展,要了解更多,可以参考 rabbitmq-nack

5.4 消息预取(Prefetching Messages)

在多个消费者共享一个队列的场景下,能够规定好在消费者发送下一次消息确认之前代理可以一次性发送给每个消费者的消息数量会很有帮助。消息批量发送可以作为简单的负载均衡技术,并且能提高消息吞吐量。例如,生产者应用因为工作机制的原因每分钟发送多条消息。

注意 RabbitMQ 只支持频道级别的预取数,没有基于连接或者大小的预取。

6. 消息属性和payload

AMQP 模型中的消息具有消息属性。有些消息属性非常通用以至于 AMQP 0-9-1 规范定义之后应用程序开发者都不需要考虑具体的属性名称。例如:

  • Content type
  • Content encoding
  • Routing key
  • Delivery mode(是否需要持久化)
  • Message priority(消息优先级)
  • Message publishing timestamp(消息发送时间戳)
  • Expiration period(过期时长)
  • Publisher application id(生产者应用id)

有些属性由 AMQP 消息代理使用,但是大部分属性会由接收消息的消费者使用。有些属性是可选的,称为报头,类似于 HTTP 中的 X-Headers。消息属性在消息发送时设置。

AMQP 消息具有 payload(消息所携带的数据),AMQP 消息代理将 payload 视为不可见字节数组,不会对 payload 进行检查和修改。消息也可以只包含消息属性而不携带 payload。通常使用像 JSON,Thrift,Protocal Buffers 和 MessagePack 等序列化格式对结构化数据进行序列化以便作为消息的 payload 进行发布。AMQP 节点通常使用“content-type”和“content-encoding”字段来传递 payload 的格式信息,但这样做仅仅只是遵循惯例(而不是协议规定)。

消息发布时可以指定持久化,这样的话 AMQP 消息代理就会把消息持久化到磁盘。当服务器重启时系统会保证接收到的持久化消息不会丢失。仅仅将消息发布到一个持久化的交换器或者消息被路由到一个持久化的队列并不会使消息也持久化:消息持久化只依赖于消息本身的持久化模式。发布持久化消息会影响性能(跟数据存储一样,持久化消息有一定的性能损耗(磁盘读写))。

7. 消息确认

由于网络不可靠,应用程序也会执行失败,有某种处理确认通常是很有必要的。有时,仅仅需要确认消息已经被成功接收;有时需要确认消息已经被消费者验证并处理成功。例如,验证消息已经包含必要数据,持久化到数据存储或者添加索引。

消息确认的场景非常常见,所以 AMQP 0-9-1 提供消息确认(有时被称为acks)的内置功能。消费者可以使用它确认消息的投递和处理。当消费应用宕机(当消费者与代理之间的连接断开时 AMQP 代理可以察觉到)时,如果一条消息正在等待接收消息确认而 AMQP 消息代理没有收到,这条消息会被重新放入队列中(如果这时候存在其他消费者的话,可能立即就被投递给了另一个消费者)。

在协议层内置消息确认功能帮助开发者构建更健壮的应用。

8. AMQP 0-9-1 方法

AMQP 0-9-1 被构造成许多方法。方法是指操作(类似 HTTP 方法),与面向对象语言中的方法没有半毛线关系。AMQP 方法按照类分组,类只是 AMQP 方法的逻辑分组。AMQP 0-9-1 参考书有全部 AMQP 方法的详情。

我们来看一下 exchange 分类,与交换器操作相关的方法分组。包括如下操作:

  • exchange.declare
  • exchange.declare-ok
  • exchange.delete
  • exchange.delete-ok

(注意 RabbitMQ 网站参数书也会包含具有RabbitMQ特定扩展的交换器类,本文中不进行讨论)。

上述操作组成逻辑对:exchange.declare-exchange.declare-okexchange.delete-exchange.delete-ok。这些操作是请求(由客户端发送)以及响应(由消息代理发送作为前面请求的响应)。

看一个示例,客户端请求消息代理声明一个新的交换器,使用exchange.declare方法:

如上图所示,exchange.declare包含几个参数,这样客户端就可以指定交换器的名称,类型,持久化标记等属性。

如果操作成功,消息代理会发送响应,使用exchange.declare-ok方法:

exchange.declare-ok除了频道号(频道的概念将在本文稍后解释)外不包含任何参数。

事件的先后顺序与 AMQP 队列分类下的另一个方法对非常类似:queue.declare-queue.declare-ok

并非所有对 AMQP 方法都是成对的。某些方法(如使用范围最广的basic.publish方法)没有与之对应的响应方法,而某些方法(比如basic.get)可以不止有一个响应。

9. 连接(Connections)

AMQP 连接通常是长连接。AMQP 属于应用层协议,使用 TCP 进行可靠传输。AMQP 连接使用认证并能通过 TLS(SSL)进行保护。当应用程序不再需要与 AMQP 消息代理保持连接时,应该优雅地关闭连接,而不是突然关闭基础 TCP 连接。

10. 频道(Channels)

某些应用程序需要与 AMQP 消息代理保持多个连接。然而,同时建立多个 TCP 连接的方式是不可取的,因为这样做消耗多余的系统资源同时也增加了配置防火墙的难度。AMQP 0-9-1 连接可以通过频道进行多路复用,可以把频道理解为“共享一个 TCP 连接的多个轻量级连接”。

对于使用多线程或者多处理器进行处理的应用程序来说,为每一个线程或者处理器新建一个频道,并且各个频道之间不进行共享的使用方式是很常见的。

特定频道的通信与其他频道的通信是完全隔离的,所以每一个 AMQP 方法需要包含客户端使用的频道号,以便标识方法操作的是哪个频道(例如,根据频道再确定需要调用哪个事件处理器)。

11. 虚拟主机(Virtual Hosts)

为了使单个消息代理能够支持多个独立的“环境”(用户分组、交换器以及队列等),AMQP 包括虚拟主机(vhosts)的概念。与许多流行 Web 服务器使用的虚拟主机类似,AMQP 虚拟主机提供 AMQP 实体生存的完全隔离的环境。AMQP 客户端在 AMQP 连接建立时指定需要连接的虚拟机。

12. AMQP 是可扩展的

AMQP 0-9-1 有几个扩展点:

这些功能使 AMQP 0-9-1 模型更加灵活,适用于非常广泛的问题。

13. AMQP 0-9-1 客户端生态系统

许多流行的编程语言和平台都有 AMQP 0-9-1 客户端。有些客户端严格遵循 AMQP 术语,仅仅提供 AMQP 方法的实现,其他一些客户端提供附加功能,以及便捷的方法和抽象;有些客户端是异步(非阻塞)的,有些是同步(阻塞)的,有些同时支持异步和同步;有些客户端支持供应商特定扩展(例如,RabbitMQ特定扩展)。

因为互用性是 AMQP 的主要目标之一,对于开发者来说,理解协议操作,不局限于特定客户端库的术语是非常重要的。这样的话,开发者通过使用不同的客户端库来进行通信将变得非常容易。

本文发表于2017年09月24日 14:37
(c)注:本文转载自https://my.oschina.net/u/2293359/blog/1542450,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2030 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1