AMQP 和 Nova

AMQP 是 OpenStack 云选择的消息传递技术。AMQP 代理,默认使用 Rabbitmq,位于任何两个 Nova 组件之间,并允许它们以松散耦合的方式进行通信。更确切地说,Nova 组件(OpenStack 的计算框架)使用远程过程调用 (RPC,以下简称) 来相互通信;但是,这种范例建立在发布/订阅范例之上,以便可以实现以下好处

  • 客户端和服务器之间的解耦(例如,客户端不需要知道服务器的引用是什么)。

  • 客户端和服务器之间的完全异步(例如,客户端不需要服务器在远程调用时同时运行)。

  • 远程调用的随机平衡(例如,如果更多服务器启动并运行,单向调用会透明地分发到第一个可用服务器)。

Nova 使用直接、扇出和基于主题的交换器。架构如下所示

../_images/rpc-arch.png

Nova 通过提供一个适配器类来实现 RPC(分别称为请求+响应和单向,昵称为 rpc.callrpc.cast),该适配器类负责将消息编组和解组为函数调用。每个 Nova 服务(例如 Compute、Scheduler 等)在初始化时创建两个队列,一个接受路由键为 NODE-TYPE.NODE-ID(例如 compute.hostname)的消息,另一个接受路由键为通用 NODE-TYPE(例如 compute)的消息。前者专门用于 Nova-API 需要将命令重定向到特定节点时,例如 openstack server delete $instance。在这种情况下,只有运行虚拟机的宿主机的计算节点才能删除实例。API 在 RPC 调用是请求/响应时充当消费者,否则仅充当发布者。

Nova RPC 映射

下图显示了消息代理节点(在图中称为 RabbitMQ 节点)的内部结构,当单个实例部署并在 OpenStack 云中共享时。每个 Nova 组件连接到消息代理,并且根据其个性(例如计算节点或网络节点),可以使用队列作为调用者(例如 API 或 Scheduler)或工作者(例如 Compute 或 Network)。调用者和工作者实际上不存在于 Nova 对象模型中,但我们将它们用作抽象以求清晰。调用者是一个通过两种操作发送消息到队列系统的组件:1) rpc.call 和 ii) rpc.cast;工作者是一个从队列系统接收消息并相应地回复 rpc.call 操作的组件。

图 2 显示了以下内部元素

主题发布者

当执行 rpc.callrpc.cast 操作时,会激活主题发布者;此对象被实例化并用于将消息推送到队列系统。每个发布者始终连接到相同的基于主题的交换器;其生命周期限制为消息传递。

直接消费者

如果(且仅当)执行 rpc.call 操作时,会激活直接消费者;此对象被实例化并用于从队列系统接收响应消息。每个消费者通过唯一的独占队列连接到唯一的基于直接的交换器;其生命周期限制为消息传递;交换器和队列标识符由 UUID 生成器确定,并编组到主题发布者发送的消息中(仅 rpc.call 操作)。

主题消费者

主题消费者在实例化工作者时激活,并在其整个生命周期中存在;此对象用于从队列接收消息,并根据工作者角色调用适当的操作。主题消费者通过共享队列或唯一的独占队列连接到相同的基于主题的交换器。每个工作者有两个主题消费者,一个仅在 rpc.cast 操作期间被寻址(并连接到交换机键为 topic 的共享队列),另一个仅在 rpc.call 操作期间被寻址(并连接到交换机键为 topic.host 的唯一队列)。

直接发布者

直接发布者仅在 rpc.call 操作期间激活,并被实例化以返回请求/响应操作所需的消息。该对象连接到基于直接的交换器,其身份由传入消息决定。

主题交换器

交换器是在虚拟主机(RabbitMQ 等提供的多租户机制)的上下文中存在的路由表;其类型(例如主题与直接)决定了路由策略;消息代理节点将为 Nova 中的每个主题只有一个基于主题的交换器。

直接交换器

这是一个在 rpc.call 操作期间创建的路由表;在消息代理节点的生命周期中,有许多这种类型的交换器,每个 rpc.call 调用都有一个。

队列元素

队列是一个消息存储桶。消息保存在队列中,直到消费者(主题或直接消费者)连接到队列并获取它。队列可以是共享的也可以是独占的。路由键为 topic 的队列在具有相同个性的工作者之间共享。

../_images/rpc-rabt.png

RPC 调用

下图显示了 rpc.call 操作期间的消息流

  1. 实例化主题发布者以将消息请求发送到队列系统;在发布操作之前,会立即实例化直接消费者以等待响应消息。

  2. 一旦消息被交换器分发,它就会被路由键(例如“topic.host”)指定的Topic 消费者获取,并传递给负责该任务的工作者。

  3. 一旦任务完成,就会分配一个直接发布者将响应消息发送到队列系统。

  4. 一旦消息被交换器分发,它就会被路由键(例如 msg_id)指定的直接消费者获取,并传递给调用者。

../_images/rpc-flow-1.png

RPC Casts

下图显示了 rpc.cast 操作期间的消息流

  1. 实例化主题发布者以将消息请求发送到队列系统。

  2. 一旦消息被交换器分发,它就会被路由键(例如“topic”)指定的Topic 消费者获取,并传递给负责该任务的工作者。

../_images/rpc-flow-2.png

AMQP Broker 负载

在任何给定时间,运行 RabbitMQ 等的的消息代理节点的负载是以下参数的函数

API 调用吞吐量

OpenStack 云中提供的 API 调用数量(更确切地说,rpc.call 操作)决定了与之关联的直接交换器、相关队列和直接消费者的数量。

工作者数量

在具有相同个性的工作者之间有一个共享队列;但是,有与工作者数量一样多的独占队列;工作者数量也决定了主题交换器中的路由键数量,所有工作者共享该交换器。

下图显示了 Nova 组件在测试环境中启动后 RabbitMQ 节点的状态。Nova 组件创建的交换器和队列是

  • 交换器

    1. nova (主题交换器)

  • 队列

    1. compute.phantomphantom 是主机名)

    2. compute

    3. network.phantomphantom 是主机名)

    4. network

    5. scheduler.phantomphantom 是主机名)

    6. scheduler

../_images/rpc-state.png

RabbitMQ 注意事项

Nova 使用 Kombu 连接到 RabbitMQ 环境。Kombu 是一个 Python 库,反过来使用 AMQPLib,一个实现编写时标准 AMQP 0.8 的库。在使用 Kombu 时,调用者和工作者需要以下参数才能实例化连接到 RabbitMQ 服务器的连接对象(请注意,以下大部分材料也包含在 Kombu 文档中;此处已进行了总结和修订以求清晰)

hostname

AMQP 服务器的主机名。

userid

用于向服务器进行身份验证的有效用户名。

password

用于向服务器进行身份验证的密码。

virtual_host

要使用的虚拟主机名称。此虚拟主机必须存在于服务器上,并且用户必须可以访问它。默认值为“/”。

port

AMQP 服务器的端口。默认值为 5672 (amqp)。

以下参数是默认值

insist

坚持连接到服务器。在具有多个负载共享服务器的配置中,Insist 选项告诉服务器客户端坚持连接到指定的服务器。默认值为 False。

connect_timeout

客户端放弃连接到服务器之前的秒数。默认情况下没有超时。

ssl

使用 SSL 连接到服务器。默认值为 False。

更确切地说,消费者需要以下参数

connection

上述连接对象。

queue

队列名称。

exchange

队列绑定的交换器名称。

routing_key

路由键的解释取决于 exchange_type 属性的值。

直接交换器

如果消息的路由键与队列的 routing_key 属性相同,则将消息转发到队列。

扇出交换器

即使绑定没有键,消息也会转发到绑定到交换器的队列。

主题交换器

如果消息的路由键与路由键根据原始模式匹配方案匹配,则将消息转发到队列。消息路由键由点 (.,如域名) 分隔的单词组成,并且有两个特殊字符可用;星号 (*) 和井号 (#)。星号匹配任何单词,井号匹配零个或多个单词。例如 .stock.# 匹配路由键 usd.stockeur.stock.db 但不匹配 stock.nasdaq

durable

此标志确定交换器和队列的持久性;持久交换器和队列在 RabbitMQ 服务器重新启动时保持活动状态。非持久交换器/队列(瞬态交换器/队列)在服务器重新启动时被清除。值得注意的是,AMQP 规定持久队列不能绑定到瞬态交换器。默认值为 True。

auto_delete

如果设置,则在所有队列完成使用它后,将删除交换器。默认值为 False。

exclusive

独占队列(例如非共享队列)只能从当前连接进行消费。当 exclusive 开启时,这也意味着 auto_delete。默认值为 False。

exchange_type

AMQP 定义了几个默认交换器类型(路由算法),涵盖了大多数常见的消息传递用例。

auto_ack

消息接收后自动处理确认。默认情况下,auto_ack 设置为 False,并且接收者需要手动处理确认。

no_ack

它禁用服务器端的确认。这与 auto_ack 不同,因为确认完全关闭。此功能提高了性能,但以可靠性为代价。如果客户端在传递它们到应用程序之前死亡,消息可能会丢失。

auto_declare

如果此值为 True 并且设置了交换器名称,则交换器将在实例化时自动声明。默认情况下,自动声明为开启。

发布者指定消费者的 معظم 参数(例如,他们不指定队列名称),但他们也可以指定以下参数

delivery_mode

用于消息的默认传递模式。该值为一个整数。RabbitMQ 支持以下传递模式

1 (瞬态)

消息是瞬态的。这意味着它仅存储在内存中,如果服务器死亡或重新启动,则会丢失。

2 (持久)

消息是持久的。这意味着消息存储在内存中和磁盘上,因此如果服务器死亡或重新启动,则会保留。

默认值为 2(持久化)。在发送操作期间,发布者可以覆盖消息的传递模式,以便例如可以向持久队列发送瞬态消息。