第2章 AMQP模型

原创
2015/12/08 19:29
阅读数 414

第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发送一条消息。



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