这个指导提供一个AMQP 0-9-1协议的概述,它是RabbitMq支持的一个协议。
什么是AMQP 0-9-1?
AMQP 0-9-1是一个能使客户端和符合条件的消息中间代理通讯的消息协议。
代理和他们的角色
消息代理从发送者接受消息和路由他们想消费者。由于它是一个网络协议,发送者,消费者,代理可以位于不同的机器上。
AMQP0-9-1 模型的简述
AMQP模型遵循下面观点:消息被发送到交换机,它常比作是邮局或邮箱。之后它能分发消息副本到队列使用定义的绑定规则。AMQP代理可以发送消息给订阅队列的消费者,或者消费者可以按需求从队列中拉取消息。
当发送一条消息,发送者可以指定各种各样的消息属性(消息元数据)。一些元数据被通过代理使用,然而,它的其他部分对代理是完全不透明的,只给接受消息的应用使用。
网络是不可靠的和应用可能处理消息会失败,所有AMQP模型有消息确认的概念。当消息发送到消费者时,消费者可通过,一种是自动的或者应用开发者选择处理完后就通知代理。当使用消息确认时,只要代理收到一个对这个消息的通知,它就会完全从队列中删除这条消息(或者一群消息)
在某种情况下,例如,当消息不能路由,消息可能会返回给发送者,或者丢弃掉,如果代理实现了一个扩展,被放入死信队列(dead letter queue)。发送者这可以通过发送消息时使用某一参数来处理这种情况。
队列,交换机和绑定共同称为AMQP实体。
AMQP是一个可编程协议
从某种意义说AMQP0-9-1是一个可编程协议,AMQP实体和路由方案主要由应用自身定义,而不是代理由代理管理。因此,声明队列,交换机,定义它们之间的绑定,订阅队列等等都是为协议操作准备的。
这给应用开发者大量的自由而且需要它们关注潜在的定义冲突。在练习中,定义冲突是少见的尝尝会表明错误的配置。
应用声明它们需要的AMQP0-9-1实体,定义需要的路由方案以及当它们不在使用实体的时候选择删除。
交换机和交换机类型
交换机是一个消息发送目的地的AMQP实体。它能持有消息并路由它到一个或者多个队列。路由算法的使用依靠交换机类型和绑定的规则,AMQP0-9-1代理提供了四中交换机类型:
名称 | 默认预声明名称
-------|-------------------------
直接交换机 | 空串和amq.direct
扇区交换机 | amq.fanout
主题交换机 | amq.topic
头交换机 | amq.match(在RabbitMq中是amq.header)
除了交换机类型,交换机可以声明一些属性,下面是最重要的:
名称
持久化(代理重启交换机存活)
自动删除(当没有队列绑定它的时候交换机删除)
参数(可选,被插件或者指定代理特征使用)
交换机能持久化或者临时的,持久化的交换机重启能存活而临时交换机不能。不是所有场景和使用例子都需要交换机持久化。
默认交换机
默认交换机是一个通过代理生成没名字的预声明的直接交换机。它有一个特殊属性那能使得它非常有用为了简单的应用:每一个队列被创建自动绑定到它用和队列名称相同的路由key。
例如,当你声明一个队列名称为"search-indexing-online", AMQP0-9-1代理能绑定向默认交换机使用search-indexing-online作为路由key。因此,一个消息被发送向默认的交换机用这个路由key "search-indexing-online"路由到队列"search-indexing-online"。换句话说,默认交换机就好像是直接发送消息向队列,即使技术上不是这样的。
直接交换机
直接交换机发送消息向队列基于消息的路由键。一个直接交换机对单播路由消息(虽然它们也可以被使用单播路由)。这是它如何工作的:
一个队列用路由K绑定到交换机
当一条新的消息用路由R到达直接交换机,这个交换机路由这条消息向队列,如果K = R
直接交换机常常被使用在多个工作者之间分发任务(相同应用的实例)用轮询的方式。当是这样的时候,理解它是很重要的,用AMQP 0-9-1,消息在消费者之间是负载均衡的而不是在队列中。
直接交换机如下图所示:
扇出交换机
扇出交换机路由消息到所有绑定它的队列而且路由关键字被忽略。如果一N个队列绑定到扇出交换机,当一个新的消息发送到那个交换机时消息副本被发送到N个队列。扇出交换机对广播路由消息是种理想的选择。
因为扇出交换机发送副本消息到每个绑定它的队列,它的使用案例和下面的非常类似:
大规模的多玩家在线游戏使用它为了排行榜更新或其他的全局事件。
运动新闻网站使用扇出交换机为实时的分发成绩更新向手机客户端。
分布式系统广播各种状态和配置更新。
群组聊天能在参与者之间发布信息使用扇出交换机(但是AMQP没有内置的展现概念,因此XMPP可能是更好的选择)
主题交换机
主题交换机路由消息到一个或者多个队列基于消息路由键和队列绑定到交换机模式的匹配。主题交换机类型常常被使用实现各种各样的发布和订阅模式变种。主题交换机通常被用来做多播消息路由。
主题交换机有一个广泛的使用集。无论什么时候一个问题涉及到多个消费者它们可以有选择性的选择接受哪些消息类型。主题交换机应该被考虑。
使用案例:
发布相关的具体地理位置数据,例如,销售点,多任务的后台处理,处理指定任务集的能力,股票数据更新。
涉及到类别化和标签化的新闻更新。等等
头交换机
一个头交换机被设计为在多个路由属性上它是更容易表示的作为消息头比一个路由键。头交换机会忽略路由属性。相反,为了路由属性会从头属性中持有。一个消息会认为被匹配,如果一个头的值等于在绑定上指定的值。
为了匹配绑定到队列到头交换机肯能会使用多个头。在这种情况下,代理需要从应用开发者那里多一个信息,也就是说,它应该考虑是匹配任何一个头还是它们的所有?这就是靠"x-match"参数去绑定。当"x-match"参数设置为"any"时,只要一个匹配的头值就充足了。或者设置"x-match"为all将会强制多个值匹配。
头交换机可以被看作是直接交换机的一类。因为他们是基于路由值的,他们能使用直接交换机哪里路由key不仅仅是字符串;它还可以是一个整数或者哈希。
队列
在AMQP 0-9-1模型的队列和其他消息的任务队列系统非常相似:他们是存储消息能被应用消费。队列有一些和交换机共享的属性,也有一些额外的属性:
名称
持久化(代理重启队列可以存活)
专用的(只被一个链接使用,当没有消费者订阅时自动删除)
自动删除(当没有消费者使用,队列删除)
参数(可选的,被插件或者指定代理特征使用,例如消息过期时间(message TTL), 队列长度限制等)
在使用队列之前需要声明。如果队列不存在声明队列会使得它被创建。如果这个队列存在而且声明时使用的属性相同,这个声明不会有影响。当这个存在的队列属性和声明不同时,在通道层会发生一个代码为406的异常。
队列名称
应用可以选择一个队列名称或者询问代理产生一个名称。队列名称可以达到UTF-8字符编码的255个字节。AMQP 0-9-1代理能产生一个唯一的队列名称。为了使用这个特征,通过一个空串作为队列名称参数。产生的名称将会返回至客户端声明队列的响应中。
队列名称以amq.开头的被预留在代理内部。尝试声明一个这样的队列名称将会违反这个规则会导致抛出一个通道层的403异常。
队列持久化
持久化队列能保存到硬盘和因此代理重启会存活。不持久化的队列是临时的。不是所有的场景和使用案例都要强制队列持久化。
队列的持久化不会使路由到该队列的消息持久化。如果一个队列宕机了和之后恢复,持久化的队列在代理重启的时候重新声明,然后,只有持久化的消息才会被恢复。
绑定
绑定是交换机使用路由消息向队列的规则。为通知一个交换机E路由消息到队列Q,Q不得不绑定到E。绑定有一个被一些交换机使用的可选路由键属性。路由键的目的是选择一个消息发送到路由到绑定队列的一个交换机,换句话说,路由键扮演一个过滤器。
类似于这样的描述:
队列就像你的目的地在纽约城市
交换机就像肯尼迪机场
绑定就是从肯尼迪到你目的地的路线。有零个或者多种方式到达。
有这个中间层能够使路由场景变成可能,不然直接发送到队列是非常难实现的也消除了应用开发者来回复制的工作。
如果AMQP消息不能路由到任何队列,它会被丢弃掉或者会返回给发送者,依靠发送者在消息属性上的设置。
消费者
存储消息在队列中是无用的除非应用消费了他们。在AMQP 0-9-1模型中,有两种方法可以消费:
有消息发送给他们("push API")
按需抓取消息("pull API")
用"push API" 应用会表明从指定队列消费消息的兴趣。当他们这么做,我们说他们注册了一个消费者,简单说,就是订阅一个队列。它是可能的每个队列有多个消费者或者注册一个专用的消费者(从队列中排除所有其他的消费者当它消费的时候)
每个消费者有一个标识符被称为消费者标签。它能被使用取消订阅消息。这个标签是一个字符串
消息确认
消费者应用--应用会接受和处理消息--可能偶尔失败处理个别消息或者有时宕机。也有可能是网络问题造成。这将会发生一个问题:AMQP代理什么时候从队列中移除消息?AMQP有两个选择:
代理发送一条消息到应用后。
一个应用发送一个回执确认。
前者是被称为自动确认模型,而后者一个明确的确认模式。明确模型可以应用选择什么时候发送确认模型。它能在接受一条消息后,也可以在处理之前存储数据之后,也可以在完全处理消息之后(例如:成功抓取一个网页,处理和存储它之后)
如果消费者宕机没有发送一个确认到AMQP代理将会重新传递它像另一个消费者,或者当是没有提供时,代理将会等到至少有一个消费者注册到相同队列才会尝试重新投递。
拒绝消息
当消费者应用收到一条消息,处理消息可能成功可能失败。一个应用将会向代理表明这消息处理失败而拒绝消息。当拒绝消息时,一个应用会要求一个代理丢弃还是重新入队列。当只有一个消费者在一个队列上,确保你不要创建一个无限消息传递循环通过拒绝和重新排队一个消息从相同的消费者一次又一次的。
否认确认
消息可以使用AMQP的basic.reject方法拒绝。这个方法有一个限制:没有拒绝多条消息的方式。然而如果你使用RabbitMQ,就会有一个解决方法。RabbitMq提供了一个AMQP 0-9-1扩展被称为否认确认或nacks。更多信息,请参考the help page.
预抓取消息
当多个消费者共享一个队列时,它是有用的为了指定每个消费者能被发送多少消息在发送下一次确认之前。这被用来作为一个简单的负载均衡技术和提高吞吐量如果消息倾向于批量发送的话。例如,如果一个生产应用由于它所做的工作每分钟发送消息
注意RabbitMQ值支持通道层面的与抓取数量,不是基于连接和大小的预取。
消息属性和负载
在AMQP模型中消息有属性。一些属性在AMQP 0-9-1说明定义他们是很普遍的和应用开发者不必要精确知道属性名。例如:
Content type(内容类型)
Content encoding(内容编码)
Routing key(路由键)
Delivery mode (persistent or not)(是否持久化)
Message priority(消息优先级)
Message publishing timestamp(消息发布的时间戳)
Expiration period(过期时间)
Publisher application id(发布的应用id)
一些属性是被AMQP代理使用,但是大多数是对接受他们的应用作说明的。一些属性是可选的和称为头。他们类似于HTTP中的头。当消息被发送时消息属性被设置。
AMQP消息也有一个有效负载(他们能持有多少数据),它被消息代理对待为一个不透明的字节数组。这个代理不会去检查和修改负载。对一个消息只包含属性和没有有效负载也是可能的。对数据进行json,Thrift,Protocol buffers和消息包裹的结构化数据序列化微乐发送一条有效负载消息。AMQP的对等体通常使用"content-type"和"content-encoding"域去交流这个信息,这已经是惯例了。
消息能被持久化发送,它会使AMQP代理持久化它们向硬盘。如果服务器重启系统可以确保它收到的消息不会丢失。简单的发送消息向一个持久化的交换机或者被路由到持久化的队列不会使消息持久化:它是依靠消息他自身的持久化模式。发布持久化消息会影响性能。
消息确认
由于网络是不可靠的和应用会失败,它常常需要有处理确认的机制。有时它只需要的当一个消息被接受的时就确认,有时确认意味着被消息被消费者验证和处理,例如,核实是否有强制的数据和持久化数据。
这个解决是很普遍的,因此AMQP 0-9-1有一个内置的消息确认机制机制(被称为acks)那消费者使用它确认消息发送了或处理了。如果一个应用宕掉(当一个连接关闭AMQP代理会知道)。如果一个消息确认被期待,而AMQP代理没收到,消息会重新排列(可能会立即发送到另一个存在的消费者)
有消息确认能帮助开发者构建稳定的软件。
AMQP 0-9-1方法
AMQP 0-9-1 构成了一些方法。方法的操作和面向对象编程语言没有共同点。AMQP方法被分成了类,类是AMQP方法的逻辑分作。
让我们看看交换机的这些类,一组关联的方法在交换机上。它包括如下操作:
exchange.declare
exchange.declare-ok
exchange.delete
exchange.delete-ok
上面的这些操作逻辑方法对:exchange.declare and exchange.declare-ok, exchange.delete and exchange.delete-ok. 这些操作是请求和响应(发送给代理响应对上述的请求)
作为一个例子,客户端要求代理声明一个新的交换机使用exchange.declare方法
作为上面显示的图标,exchange.declare携带几个参数。他们能让客户端指定交换机的名称,类型,持久化标签等等。
如果一个操作成功,代理将会通过exchange.declare-ok方法:
exchange.declare-ok除了通道编码不会携带任何参数。
不是所有的AMQP方法都是相对的,一些(basic.publish 用的最广泛)不会有响应方法和另外(例如basic.get)比一个响应更多。
连接
AMQP连接通常是长期存活的。AMQP是一个应用层协议它使用TCP可靠传送。AMQP连接使用认证和能使用TLS保护。当应用不再需要连接向AMQP代理的时候,它应该优雅关闭AMQP连接而不是突然关闭潜在的TCP连接
通道
一些应用需要多个连接向AMQP代理。然而,同事打开多个TCP连接是不好的因为它会消耗系统资源和配置防火墙也是困难的。AMQP 0-9-1连接是多路复用的通道它是轻量级的连接被单个TCP 连接共享。
为了应用使用多线程处理,为每个线程打开一个通道而不是通道之间共享。
在一个指定的通道的通信和另一个通道通信是完全隔离的。因此每个AMQP方法会携带一个客户端使用的通道编码为了方法是使用的哪个通道。
虚拟主机
为了使单个代理隔离成多个主机环境(用户,交换机,队列等等的群),AMQP包括了一个虚拟主机的概念。他们类似于虚拟机被用于多个流行的Web服务器和提供完全的隔离环境在AMQP下。AMQP客户端会在AMQP连接期间指定他们想要使用什么样的Vhosts.
AMQP的扩展
Custom exchange types(定制交换机类型)让开发者去实现路由方案,箱外的交换机类型没有涉及的情况下,例如基于地理数据的路由。
交换机和队列声明包括额外的属性也是代理能使用的。例如per-queue message TTL(每个消息队列的过期时间),可以通过该方式实现。
特定代理对协议扩展,例如,extensions that RabbitMQ implements
New AMQP 0-9-1 method classes(新的AMQP 0-9-1方法类)介绍
代理能扩展additional plugins(额外的插件),例如RabbitMQ management(RabbitMq管理前端)和Http Api被作为插件实现
这些特征能使AMQP 0-9-1模型更加灵活和可以应用于非常广泛的问题
AMQP 0-9-1客户端的生态体系
有大量的many AMQP 0-9-1 clients为了大量流行的编程语言和平台,他们的一些遵循AMQP术语和仅仅提供AMQP方法的实现。其他的会有额外的特征,方便方法和抽象。有一些客户端是异步的,一些是同步的,一些都支持。一些客户端支持指定供应的扩展(例如,特定的RabbitMq扩展)。
由于AMQP的主要目标是互操作性,所以开发者要理解协议操作而不要限于他们自己指定客户端库的术语。这方法会让开发者使用不同库会更加容易。