AMQP 0-9-1模型说明
总览
本指南概述了AMQP 0-9-1协议,它是RabbitMQ支持的协议之一。
AMQP 0-9-1和AMQP模型的高级概述
什么是AMQP 0-9-1?
AMQP 0-9-1(高级消息队列协议)是一种消息传递协议,使一致的客户端应用程序可以与一致的消息传递中间件代理进行通信。
经纪人及其作用
消息经纪人从发布者 (发布它们的应用程序,也称为生产者)接收消息,并将其路由到 消费者(处理它们的应用程序)。
由于它是一种网络协议,因此发布者,消费者和经纪人都可以驻留在不同的计算机上。
AMQP 0-9-1模型简介
AMQP 0-9-1模型具有以下的世界观:消息发布到交易所,通常与邮局或邮箱进行比较。然后,交易所使用称为绑定的规则将 消息副本分发到队列中。然后,代理要么将消息传递给订阅了队列的使用者,要么使用者按需从队列中获取/拉取消息。
发布消息时,发布者可以指定各种 消息属性(消息元数据)。这些元数据中的某些可能由代理使用,但是其余部分对代理完全不透明,并且仅由接收消息的应用程序使用。
网络不可靠,应用程序可能无法处理消息,因此AMQP 0-9-1模型具有消息确认的概念 :将消息传递给消费者时,消费者会自动或在应用程序开发人员选择后立即通知代理。这样做。使用消息确认时,代理只有在收到该消息(或消息组)的通知时,才会从队列中完全删除该消息。
在某些情况下,例如,当无法路由消息时,消息可能会返回给发布者,被丢弃,或者(如果代理实施了扩展)会将消息放入所谓的“死信队列”中。发布者通过使用某些参数发布消息来选择如何处理这种情况。
队列,交换和绑定统称为AMQP实体。
AMQP 0-9-1是一个可编程协议
从AMQP 0-9-1实体和路由方案主要由应用程序本身而不是代理管理员定义的意义上来说,AMQP 0-9-1是一种可编程协议。因此,为声明队列和交换,定义它们之间的绑定,订阅队列等等的协议操作作了准备。
这为应用程序开发人员提供了很大的自由度,但同时也要求他们意识到潜在的定义冲突。实际上,定义冲突很少发生,并且经常表示配置错误。
应用程序声明所需的AMQP 0-9-1实体,定义必要的路由方案,并且在不再使用AMQP 0-9-1实体时可以选择删除它们。
交易所和交易所类型
交换是发送消息的AMQP 0-9-1实体。交换接收一条消息并将其路由到零个或多个队列中。使用的路由算法取决于 交换类型和称为绑定的规则。AMQP 0-9-1经纪人提供四种交换类型:
名称 | 预设的预设名称 |
---|---|
直接交换 | (空字符串)和amq.direct |
扇出交换 | amq.fanout |
话题交流 | amq.topic |
标头交换 | amq.match(和RabbitMQ中的amq.header) |
除了交换类型之外,还使用许多属性声明交换,其中最重要的是:
- 名称
- 持久性(在代理重新启动后保留更改)
- 自动删除(当最后一个队列解除绑定时,交换将被删除)
- 参数(可选,由插件和特定于代理的功能使用)
交换可以是持久的或短暂的。持久的交换可在代理重新启动后继续存在,而短暂的交换则不能(临时代理重新联机时必须重新声明)。并非所有方案和用例都要求交换具有持久性。
默认交换
默认交换是经纪人预先声明的不带名称(空字符串)的直接交换。它具有一个特殊的属性,使其对简单的应用程序非常有用:每个创建的队列都使用与队列名称相同的路由键自动绑定到该队列。
例如,当您声明名称为“ search-indexing-online”的队列时,AMQP 0-9-1代理将使用“ search-indexing-online”作为路由键将其绑定到默认交换机(在此示例中上下文,有时称为绑定键)。因此,使用路由关键字“ search-indexing-online”发布到默认交换机的消息将被路由到队列“ search-indexing-online”。换句话说,默认交换使得似乎可以将消息直接传递到队列,即使从技术上讲这不是正在发生的事情。
直接交换
直接交换基于消息路由密钥将消息传递到队列。直接交换是消息单播路由的理想选择(尽管它们也可以用于多播路由)。下面是它的工作原理:
- 队列使用路由密钥K绑定到交换机
- 当具有路由键R的新消息到达直接交换时,如果K = R,则交换会将其路由到队列
直接交换通常用于以轮循方式在多个工作程序(同一应用程序的实例)之间分配任务。这样做时,重要的是要了解,在AMQP 0-9-1中,消息在使用者之间而不是队列之间进行负载均衡。
直接交换可以用以下图形表示:
扇出交换
扇出交换机将消息路由到与其绑定的所有队列,并且路由键将被忽略。如果将N个队列绑定到扇出交换,则将新消息发布到该交换时,会将消息的副本传递到所有N个队列。扇出交换机非常适合消息的广播路由。
因为扇出交换将消息的副本发送到绑定到它的每个队列,所以它的用例非常相似:
- 大型多人在线(MMO)游戏可以将其用于排行榜更新或其他全球性事件
- 体育新闻网站可以使用扇出交换以近乎实时的方式向移动客户端分发得分更新
- 分布式系统可以广播各种状态和配置更新
- 群组聊天可以使用扇出交换在参与者之间分发消息(尽管AMQP没有内置的在线状态概念,因此XMPP可能是更好的选择)
扇出交换可以用以下图形表示:
主题交流
主题根据消息路由键和用于将队列绑定到交换机的模式之间的匹配将消息路由到一个或多个队列。主题交换类型通常用于实现各种发布/订阅模式变体。主题交换通常用于消息的多播路由。
主题交流有非常广泛的用例集。每当问题涉及多个使用者/应用程序,这些使用者/应用程序有选择地选择他们希望接收的消息类型时,应考虑使用主题交换。
示例使用:
- 分发与特定地理位置有关的数据,例如销售点
- 由多个工作人员完成的后台任务处理,每个工作人员都可以处理特定的任务集
- 股票价格更新(以及其他种类的财务数据的更新)
- 涉及分类或标记的新闻更新(例如,仅针对特定运动或团队)
- 云中各种服务的编排
- 分布式体系结构/特定于操作系统的软件构建或打包,其中每个构建器只能处理一个体系结构或OS
标头交换
标头交换旨在用于在多个属性上路由,这些属性比路由键更容易表示为消息标头。标头交换忽略路由键属性。相反,用于路由的属性取自headers属性。如果标头的值等于绑定时指定的值,则认为消息匹配。
可以使用多个标题进行匹配,将队列绑定到标题交换。在这种情况下,代理需要从应用程序开发人员那里获得另一条信息,即,它应该考虑具有任何匹配的标头的消息,还是全部匹配的消息?这就是“ x-match”绑定参数的作用。当“ x-match”参数设置为“ any”时,仅一个匹配的标头值就足够了。或者,将“ x-match”设置为“ all”要求所有值必须匹配。
标头交换可以看作是“类固醇的直接交换”。由于它们基于标头值进行路由,因此可以将它们用作直接交换,而路由密钥不必是字符串。例如,它可以是整数或哈希(字典)。
请注意,以字符串x-开头的标头 将不用于评估匹配项。
队列
队列在AMQP 0-9-1模式非常类似于其他MESSAGE-和任务排队系统队列:它们存储由应用程序使用的消息。队列与交换共享一些属性,但也具有一些其他属性:
- 名称
- 持久(队列将在代理重新启动后幸存)
- 独占(仅由一个连接使用,并且该连接关闭时队列将被删除)
- 自动删除(至少有一个使用方的队列在最后一个使用方退订时被删除)
- 参数(可选;由插件和特定于代理的功能使用,例如消息TTL,队列长度限制等)
必须先声明队列,然后才能使用它。声明队列将导致它创建(如果尚不存在)。如果队列已经存在并且其属性与声明中的相同,则该声明将无效。当现有队列属性与声明中的属性不同时,将引发代码为406(PRECONDITION_FAILED)的通道级异常。
队列名称
应用程序可以选择队列名称,也可以要求代理为它们生成名称。队列名称最多可以包含255个字节的UTF-8字符。AMQP 0-9-1代理可以代表应用程序生成唯一的队列名称。要使用此功能,请传递一个空字符串作为队列名称参数。生成的名称将与队列声明响应一起返回给客户端。
队列名称以“ amq”开头。保留供经纪人内部使用。尝试使用违反此规则的名称声明队列将导致通道级异常,其应答代码为403(ACCESS_REFUSED)。
队列耐久性
持久队列将持久保存在磁盘上,因此在代理重新启动后仍然存在。不持久的队列称为瞬态。并非所有方案和用例都要求队列是持久的。
队列的持久性不会使路由到该队列的消息持久化。如果关闭代理然后 将其恢复,则在代理启动期间将重新声明持久队列,但是,仅持久消息将被恢复。
绑定
绑定是交换使用(其中包括)将消息路由到队列的规则。为了指示交换机E将消息路由到队列Q,必须将Q 绑定到E。绑定可能具有某些交换机类型使用的可选 路由键属性。路由键的目的是选择发布到交换机的某些消息以路由到绑定队列。换句话说,路由键就像一个过滤器。
打个比方:
- 排队就像您在纽约市的目的地
- 交流就像肯尼迪国际机场
- 绑定是从肯尼迪国际机场到目的地的路线。可能有零种或多种方法
有了这一层的间接支持,就可以通过直接发布到队列来实现不可能或很难实现的路由方案,并且还消除了应用程序开发人员必须做的某些重复工作。
如果不能将消息路由到任何队列(例如,由于没有发布该消息的绑定),则该消息将被丢弃或返回给发布者,具体取决于发布者设置的消息属性。
消费者
除非应用程序可以使用它们,否则将消息存储在队列中是没有用的。在AMQP 0-9-1模型中,应用程序可以通过两种方式执行此操作:
- 已将消息传递给他们(“推送API”)
- 根据需要提取消息(“拉API”)
使用“推送API”,应用程序必须表明有兴趣使用特定队列中的消息。当他们这样做时,我们说他们注册了一个使用者, 或者简单地说,订阅了一个队列。每个队列可能有一个以上的使用者,或者注册一个 排他的使用者(在使用队列时将所有其他使用者从队列中排除)。
每个消费者(订阅)都有一个称为 消费者标签的标识符。它可以用于退订消息。消费者标签只是字符串。
留言确认
消费者应用程序(即接收和处理消息的应用程序)有时可能无法处理单个消息,有时甚至会崩溃。网络问题也有可能引起问题。这就提出了一个问题:代理何时应从队列中删除消息?AMQP 0-9-1规范使消费者可以对此进行控制。有两种确认模式:
- 代理将消息发送到应用程序之后(使用basic.deliver或basic.get-ok方法)。
- 应用程序发送回确认之后(使用basic.ack方法)。
前者被称为自动确认模型,而后者被称为显式确认模型。使用显式模型,应用程序选择何时发送确认。可能是在收到消息之后,或者在将消息持久保存到数据存储之前(在处理之前)或在完全处理消息之后(例如,成功获取Web页,将其处理并将其存储到某个持久数据存储中)之后。
如果某个消费者在未发送确认的情况下死亡,则经纪人会将其重新交付给另一位消费者,或者,如果当时没有人可用,则该经纪人将等待,直到至少一个消费者为同一队列注册之后再尝试重新交付。
拒绝讯息
当使用者应用程序接收到一条消息时,对该消息的处理可能会成功也可能不会成功。应用程序可以通过拒绝消息来向代理指示消息处理已失败(或当时无法完成)。拒绝消息时,应用程序可以要求代理放弃或重新排队。当队列中只有一个使用者时,请确保您不会通过反复拒绝和拒绝来自同一使用者的消息来创建无限消息传递循环。
负面确认
使用basic.reject方法拒绝消息。basic.reject有一个限制:无法像确认那样拒绝多个消息。但是,如果您使用RabbitMQ,那么有解决方案。RabbitMQ提供了AMQP 0-9-1扩展,称为否定确认或否定。有关更多信息,请参阅确认 和basic.nack扩展指南。
预取消息
对于多个使用者共享一个队列的情况,在发送下一个确认之前,可以指定每个使用者可以一次发送多少消息是很有用的。如果消息倾向于批量发布,则可以将其用作简单的负载平衡技术或提高吞吐量。例如,如果生产应用程序由于其工作性质而每分钟发送一次消息,
请注意,RabbitMQ仅支持通道级的预取计数,不支持基于连接或大小的预取。
消息属性和有效负载
AMQP 0-9-1模型中的消息具有属性。一些属性非常常见,以至于AMQP 0-9-1规范定义了它们,应用程序开发人员不必考虑确切的属性名称。一些例子是
- 内容类型
- 内容编码
- 路由键
- 投放方式(是否持续)
- 邮件优先级
- 消息发布时间戳
- 有效期
- 发布者应用程序ID
AMQP代理使用某些属性,但大多数属性都可以由接收它们的应用程序进行解释。一些属性是可选的,称为标头。它们类似于HTTP中的X-Header。消息发布时设置消息属性。
消息还具有有效负载(它们携带的数据),AMQP代理将其视为不透明字节数组。代理将不会检查或修改有效负载。消息可能仅包含属性而没有有效载荷。通常使用JSON,Thrift,协议缓冲区和MessagePack等序列化格式来序列化结构化数据,以将其发布为消息有效负载。协议对等方通常使用“内容类型”和“内容编码”字段来传达此信息,但这仅是约定。
消息可能被发布为持久消息,这使代理将它们持久保存到磁盘。如果重新启动服务器,系统将确保接收的持久消息不会丢失。简单地将消息发布到持久性交换或路由到持久性队列的事实并不能使消息持久化:这完全取决于消息本身的持久性模式。将消息持久发布会影响性能(就像数据存储一样,持久性会以一定的性能成本为代价)。
在发布者指南中了解更多信息。
留言确认
由于网络不可靠且应用程序失败,因此通常需要进行某种处理确认。有时仅需要确认已收到消息这一事实。有时,确认表示一条消息已由消费者验证和处理,例如,被验证为具有必需数据并持久保存到数据存储或建立索引。
这种情况是很常见的,所以AMQP 0-9-1具有一个内置的功能,称为消息确认(有时被称为的ack),消费者使用,以确认消息传送和/或处理。如果应用程序崩溃(关闭连接时AMQP代理会注意到此情况),如果预期收到消息确认但AMQP代理未收到该消息,则会对该消息重新排队(并可能立即传递给另一个使用者,如果有的话)存在)。
协议中内置了确认,有助于开发人员构建更强大的软件。
AMQP 0-9-1方法
AMQP 0-9-1被构造为多种 方法。方法是操作(如HTTP方法),与面向对象编程语言中的方法没有任何共同之处。AMQP 0-9-1中的协议方法分为几 类。类只是AMQP方法的逻辑分组。该AMQP 0-9-1参考具有的所有AMQP方法的全部细节。
让我们看一下交换类,这是一组与交换操作有关的方法。它包括以下操作:
- 交换声明
- exchange.declare-ok
- 交换删除
- exchange.delete-ok
(请注意,RabbitMQ站点参考还包括针对Exchange类的RabbitMQ特定扩展,我们将不在本指南中讨论)。
上面的操作形成逻辑对: exchange.declare和 exchange.declare-ok, exchange.delete和 exchange.delete-ok。这些操作是“请求”(由客户发送)和“响应”(由代理响应上述“请求”发送)。
例如,客户要求经纪人使用exchange.declare方法声明新的交换:
如上图所示, exchange.declare带有几个 参数。它们使客户端能够指定交换名称,类型,持久性标记等。
如果操作成功,则代理将使用exchange.declare-ok方法进行响应:
exchange.declare-ok除通道号外不携带任何参数(通道将在本指南后面介绍)。
对于AMQP 0-9-1 队列方法类上的另一个方法对,事件的顺序非常相似:queue.declare和 queue.declare-ok:
并非所有AMQP 0-9-1方法都有对应的方法。一些(basic.publish是使用最广泛的一种)没有相应的“响应”方法,而另一些(例如basic.get)则具有多个可能的“响应”方法。
连接数
AMQP 0-9-1连接通常是长期的。AMQP 0-9-1是使用TCP进行可靠传递的应用程序级别协议。连接使用身份验证,并且可以使用TLS进行保护。当不再需要将应用程序连接到服务器时,它应正常关闭其AMQP 0-9-1连接,而不是突然关闭基础TCP连接。
频道
某些应用程序需要与代理的多个连接。但是,不希望同时打开许多TCP连接,因为这样做会消耗系统资源,并使配置防火墙更加困难。AMQP 0-9-1连接与可被视为“共享单个TCP连接的轻型连接”的通道复用 。
客户端执行的每个协议操作都在通道上发生。特定通道上的通信与另一通道上的通信是完全分开的,因此每种协议方法还带有一个通道ID(又称通道号),该整数是代理和客户端都用来确定该方法用于哪个通道的整数。
通道仅存在于连接的上下文中,而不是单独存在的。关闭连接后,其上的所有通道也将关闭。
对于使用多个线程/进程进行处理的应用程序,很常见的是为每个线程/进程打开一个新通道,而不在它们之间共享通道。
虚拟主机
为了使单个代理可以承载多个隔离的“环境”(用户组,交换,队列等),AMQP 0-9-1包含虚拟主机(vhost)的概念。它们类似于许多流行的Web服务器使用的虚拟主机,并提供AMQP实体所在的完全隔离的环境。协议客户端指定在连接协商期间要使用的虚拟主机。
AMQP是可扩展的
AMQP 0-9-1具有几个扩展点:
- 自定义交换类型使开发人员可以实现路由方案,这些路由方案提供的现成交换类型不能很好地覆盖,例如基于地理数据的路由。
- 交换和队列的声明可以包括代理可以使用的其他属性。例如,RabbitMQ中的每个队列消息TTL是通过这种方式实现的。
- 特定于代理的协议扩展。例如,请参阅 RabbitMQ实现的扩展。
- 可以引入新的AMQP 0-9-1方法类。
- 可以使用其他插件来扩展代理,例如,RabbitMQ管理 前端和HTTP API被实现为插件。
这些功能使AMQP 0-9-1模型更加灵活,适用于各种问题。
AMQP 0-9-1客户生态系统
许多流行的编程语言和平台都有许多AMQP 0-9-1客户端。其中一些紧密遵循AMQP术语,仅提供AMQP方法的实现。其他一些具有附加功能,便捷方法和抽象。有些客户端是异步的(非阻塞),有些是同步的(阻塞),有些则支持这两种模型。一些客户端支持特定于供应商的扩展(例如,特定于RabbitMQ的扩展)。
因为AMQP的主要目标之一是互操作性,所以对于开发人员来说,理解协议操作而不是将自己局限于特定客户端库的术语是一个好主意。与使用不同库的开发人员进行通信的这种方式将大大简化。
获取帮助并提供反馈
如果您对本指南的内容或与RabbitMQ有关的任何其他主题有疑问,请随时在RabbitMQ邮件列表中询问。
帮助我们改善文档<3
如果您想对该网站做出贡献,可以在GitHub上找到其来源。只需分叉存储库并提交拉取请求。谢谢!