本篇文章是由本人阅读NOVA源码过程中的心得、RabbitMQ的官方文档以及网上的一些资料整理总结而成的,也为了方便以后对这部分内容的复习。
NOVA是OpenStack系统的核心模块,主要负责虚拟机实例的生命周期管理、网络管理(前几个版本)、存储卷管理(前几个版本)、用户管理以及其他相关云平台管理功能,在能力上类似于Amazon EC2和Rackspace Cloud Servers。
消息队列(Queue)与数据库(Database)作为Nova总体架构中的两个重要组成部分,二者通过系统内消息传递和信息共享的方式实现任务之间、模块之间、接口之间的异步部署,在系统层面大大简化了复杂任务的调度流程与模式,是整个OpenStack Nova系统的核心功能模块。终端用户(DevOps、Developers和其他OpenStack组件)主要通过Nova API实现与OpenStack系统的互动,同时Nova守护进程之间通过消息队列和数据库来交换信息以执行API请求,完成终端用户的云服务请求。
Nova采用无共享、基于消息的灵活架构,意味着Nova的组件有多种安装方式,可以将每个Nova-Service模块单独安装在一台服务器上,同时也可以根据业务需求将多个模块组合安装在多台服务器上。
1.RabbitMQ
OpenStack Nova系统目前主要采用RabbitMQ作为信息交换中枢。
RabbitMQ是一种处理消息验证、消息转换和消息路由的架构模式,它协调应用程序之间的信息通信,并使得应用程序或者软件模块之间的相互意识最小化,有效实现解耦。
RabbitMQ适合部署在一个拓扑灵活易扩展的规模化系统环境中,有效保证不同模块、不同节点、不同进程之间消息通信的时效性;而且,RabbitMQ特有的集群HA安全保障能力可以实现信息枢纽中心的系统级备份,同时单节点具备消息恢复能力,当系统进程崩溃或者节点宕机时,RabbitMQ正在处理的消息队列不会丢失,待节点重启之后可根据消息队列的状态数据以及信息数据及时恢复通信。
RabbitMQ在功能性、时效性、安全可靠性以及SLA方面的出色能力可有效支持OpenStack云平台系统的规模化部署、弹性扩展、灵活架构以及信息安全的需求。
2.AMQP
AMQP是应用层协议的一个开放标准,为面向消息的中间件而设计,其中RabbitMQ是AMQP协议的一个开源实现,OpenStack Nova各软件模块通过AMQP协议实现信息通信。AMQP协议的设计理念与数据通信网络中的路由协议非常类似,可归纳为基于状态的面向无连接通信系统模式。不同的是,数据通信网络是基于通信链路的状态决定客户端与服务端之间的链接,而AMQP是基于消息队列的状态决定消息生产者与消息消费者之间的链接。对于AMQP来讲,消息队列的状态信息决定通信系统的转发路径,链接两端之间的链路并不是专用且永久的,而是根据消息队列的状态与属性实现信息在RabbitMQ服务器上的存储与转发,正如数据通信网络的IP数据包转发机制,所有的路由器是基于通信链路的状态而形成路由表,IP数据包根据路由表实现报文的本地存储与逐级转发,二者在实现机制上具有异曲同工之妙。
AMQP的目标是实现端到端的信息通信,那么必然涉及两个基本的概念:AMQP实现通信的因素是什么以及AMQP实现通信的实体以及机制是什么。
AMQP是面向消息的一种应用程序之间的通信方法,也就是说,“消息”是AMQP实现通信的基本因素。AMQP有两个核心要素——交换器(Exchange)与队列(Queue)通过消息的绑定与转发机制实现信息通信。其中,交换器是由消费者应用程序创建,并且可与其他应用程序实现共享服务,其功能与数据通信网络中的路由器非常相似,即接收消息之后通过路由表将消息准确且安全的转发至相应的消息队列。一台RabbitMQ服务器或者由多台RabbitMQ服务器组成的集群可以存在多个交换器,每个交换器通过唯一的Exchange ID进行识别。
交换器根据不同的应用程序的需求,在生命周期方面也是灵活可变的,主要分为三种:持久交换器、临时交换器与自动删除交换器。持久交换器是在RabbitMQ服务器中长久存在的,并不会因为系统重启或者应用程序终止而消除,其相关数据长期驻留在硬盘之上;临时交换器驻留在内存中,随着系统的关闭而消失;自动删除交换器随着宿主应用程序的中止而自动消亡,可有效释放服务器资源。
队列也是由消费者应用程序创建,主要用于实现存储与转发交换器发送来的消息,队列同时也具备灵活的生命周期属性配置,可实现队列的持久保存、临时驻留与自动删除。
由以上可以看出,消息、队列和交换器是构成AMQP的三个关键组件,任何一个组件的实效都会导致信息通信的中断,因此鉴于三个关键组件的重要性,系统在创建三个组件的同时会打上“Durable”标签,表明在系统重启之后立即恢复业务功能。
由图中可以看出,交换器接收发送端应用程序的消息,通过设定的路由转发表与绑定规则将消息转发至相匹配的消息队列,消息队列继而将接收到的消息转发至对应的接收端应用程序。数据通信网络通过IP地址形成的路由表实现IP报文的转发,在AMQP环境中的通信机制也非常类似,交换器通过AMQP消息头(Header)中的路由选择关键字(Routing Key)而形成的绑定规则(Binding)来实现消息的转发,也就是说,“绑定”即连接交换机与消息队列的路由表。消息生产者发送的消息中所带有的Routing Key是交换器转发的判断因素,也就是AMQP中的“IP地址”,交换器获取消息之后提取Routing Key触发路由,通过绑定规则将消息转发至相应队列,消息消费者最后从队列中获取消息。AMQP定义三种不同类型的交换器:广播式交换器(Fanout Exchange)、直接式交换器(Direct Exchange)和主题式交换器(Topic Exchange),三种交换器实现的绑定规则也有所不同。
3.Nova中的RabbitMQ应用
3.1RabbitMQ在Nova中的实现
RabbitMQ是OpenStackNova系统的信息中枢,目前Nova中的各个模块通过RabbitMQ服务器以RPC(远程过程调用)的方式实现通信,而且各模块之间形成松耦合关联关系,在扩展性、安全性以及性能方面均体现优势。由前文可知,AMQP的交换器有三种类型:Direct、Fanout和Topic,而且消息队列是由消息消费者根据自身的功能与业务需求而生成。
首先说说三个比较重要的概念:
交换器:
接受消息并且将消息转发给队列。在每个寻你主机的内部,交换器有唯一对应的名字。应用程序在他的权限范围之内可以创建、删除、使用 和共享交换器实例。交换器可以是持久的,临时的或者自动删除的。持久的交换器会一直存在于Server端直到他被显示的删除;临时交换器在服务器关闭时停 止工作;自动删除的交换器在没有应用程序使用它的时候被服务器删除。
队列:
“消息队列”,它是一个具名缓冲区,它代表一组消费者应用程序保存消息。这些应用程序在它们的权限范围内可以创建、使用、共享消息队列。类似于交换器,消息队列也可以是持久的,临时的或者自动删除的。临时消息队列在服务器被关闭时停止工作;自动删除队列在没有应用程序使用它的时候被服务器自动删 除。消息队列将消息保存在内存、硬盘或两者的组合之中。消息队列保存消息,并将消息发给一个或多个客户端,特别的消息队列会跟踪消息的获取情况,消息要出对就必须被获取,这样可以阻止多个客户端同时消费同一条消息的情况发生,同时也可以被用来做单个队列多个消费者之间的负载均衡。
绑定:
可以理解为交换器和消息队列之间的一种关系,绑定之后交换器会知道应该把消息发给那个队列,绑定的关键字称为binding_key。在程序中我们这样使用:
channel.queue_bind(exchange='direct_logs',queue=queue_name,routing_key=binding_key)
Exchange和Queue的绑定可以是多对多的关系,每个发送给Exchange的消息都会有一个叫做routing_key的关键字,交换器要想把消息发送给某个特定的队列,那么该队列与交换器的binding_key必须和消息的routing_key相匹配才OK。
介绍一下RabbitMQ的三种类型的交换器:
广播式交换器类型(fanout)
该类交换器不分析所接收到消息中的Routing Key,默认将消息转发到所有与该交换器绑定的队列中去。广播式交换器转发效率最高,但是安全性较低,消费者应用程序可获取本不属于自己的消息。
广播交换器是最简单的一种类型,就像我们从字面上理解到的一样,它把所有接受到的消息广播到所有它所知道的队列中去,不论消息的关键字是什么,消息都会被路由到和该交换器绑定的队列中去。
它的工作方式如下图所示:
在程序中申明一个广播式交换器的代码如下:
channel.exchange_declare(exchange='fanout',type='fanout')
直接式交换器类型(direct)
该类交换器需要精确匹配Routing Key与BindingKey,如消息的Routing Key = Cloud,那么该条消息只能被转发至Binding Key = Cloud的消息队列中去。直接式交换器的转发效率较高,安全性较好,但是缺乏灵活性,系统配置量较大。
相对广播交换器来说,直接交换器可以给我们带来更多的灵活性。直接交换器的路由算法很简单——一个消息的routing_key完全匹配一个队列的 binding_key,就将这个消息路由到该队列。绑定的关键字将队列和交换器绑定到一起。当消息的routing_key和多个绑定关键字匹配时消息 可能会被发送到多个队列中。
我们通过下图来说明直接交换器的工作方式:
主题式交换器(Topic Exchange)
该类交换器通过消息的Routing Key与Binding Key的模式匹配,将消息转发至所有符合绑定规则的队列中。Binding Key支持通配符,其中“*”匹配一个词组,“#”匹配多个词组(包括零个)。例如,Binding Key=“*.Cloud.#”可转发Routing Key=“OpenStack.Cloud.GD.GZ”、“OpenStack.Cloud.Beijing”以及“OpenStack.Cloud”的消息,但是对于Routing Key=“Cloud.GZ”的消息是无法匹配的。
这里的routing_key可以使用一种类似正则表达式的形式,但是特殊字符只能是“*”和“#”,“*”代表一个单词,“#”代表0个或是多个单词。这样发送过来的消息如果符合某个queue的routing_key定义的规则,那么就会转发给这个queue。
在Nova中主要实现Direct和Topic两种交换器的应用,在系统初始化的过程中,各个模块基于Direct交换器针对每一条系统消息自动生成多个队列注入RabbitMQ服务器中,依据Direct交换器的特性要求,Binding Key=“MSG-ID”的消息队列只会存储与转发Routing Key=“MSG-ID”的消息。同时,各个模块作为消息消费者基于Topic交换器自动生成两个队列注入RabbitMQ服务器中。
Nova各个模块之间基于AMQP消息实现通信,但是真正实现消息调用的应用流程主要是RPC机制。Nova基于RabbitMQ实现两种RPC调用:RPC.CALL和RPC.CAST,其中RPC.CALL基于请求与响应方式,RPC.CAST只是提供单向请求,两种RPC调用方式在Nova中均有不同的应用场景。
Nova的各个模块在逻辑功能是可以划分为两种:Invoker和Worker,其中Invoker模块主要功能是向消息队列中发送系统请求消息,如Nova-API和Nova-Scheduler;Worker模块则从消息队列中获取Invoker模块发送的系统请求消息以及向Invoker模块回复系统响应消息,如Nova-Compute、Nova-Volume和Nova-Network。Invoker通过RPC.CALL和RPC.CAST两个进程发送系统请求消息;Worker从消息队列中接收消息,并对RPC.CALL做出响应。Invoker、Worker与RabbitMQ中不同类型的交换器和队列之间的通信关系如图所示。
Nova根据Invoker和Worker之间的通信关系可逻辑划分为两个交换域:Topic交换域与Direct交换域,两个交换域之间并不是严格割裂,在信息通信的流程上是深度嵌入的关系。Topic交换域中的Topic消息生产者(Nova-API或者Nova-Scheduler)与Topic交换器生成逻辑连接,通过PRC.CALL或者RPC.CAST进程将系统请求消息发往Topic交换器。Topic交换器根据系统请求消息的Routing Key分别送入不同的消息队列进行转发,如果消息的Routing Key=“NODE-TYPE.NODE-ID”,则将被转发至点对点消息队列;如果消息的Routing Key=“NODE-TYPE”,则将被转发至共享消息队列。Topic消息消费者探测到新消息已进入响应队列,立即从队列中接收消息并调用执行系统消息所请求的应用程序。每一个Worker都具有两个Topic消息消费者程序,对应点对点消息队列和共享消息队列,链接点对点消息队列的Topic消息消费者应用程序接收RPC.CALL的远程调用请求,并在执行相关计算任务之后将结果以系统响应消息的方式通过Direct交换器反馈给Direct消息消费者;同时链接共享消息队列的Topic消息消费者应用程序只是接收RPC.CAST的远程调用请求来执行相关的计算任务,并没有响应消息反馈。因此,Direct交换域并不是独立运作,而是受限于Topic交换域中RPC.CALL的远程调用流程与结果,每一个RPC.CALL激活一次Direct消息交换的运作,针对每一条系统响应消息会生成一组相应的消息队列与交换器组合。因此,对于规模化的OpenStack云平台系统来讲,Direct交换域会因大量的消息处理而形成整个系统的性能瓶颈点。
3.2Nova系统RPC.CALL以及RPC.CAST调用流程
由前文可以看出,RPC.CALL是一种双向通信流程,即Worker程序接收消息生产者生成的系统请求消息,消息消费者经过处理之后将系统相应结果反馈给Invoker程序。
例如,一个用户通过外部系统将“启动虚拟机”的需求发送给NOVA-API,此时NOVA-API作为消息生产者,将该消息包装为AMQP信息以RPC.CALL方式通过Topic交换器转发至点对点消息队列,此时,Nova-Compute作为消息消费者,接收该信息并通过底层虚拟化软件执行相应虚拟机的启动进程;待用户虚拟机成功启动之后,Nova-Compute作为消息生产者通过Direct交换器和响应的消息队列将“虚拟机启动成功”响应消息反馈给Nova-API,此时Nova-API作为消息消费者接收该消息并通知用户虚拟机启动成功,一次完整的虚拟机启动的RPC.CALL调用流程结束。其具体流程如图所示:
(3)Worker根据请求消息执行完任务之后,分配一个Direct消息生产者,Direct消息生产者将响应消息发送到Direct交换器。
(4)Direct交换器根据响应消息的Routing Key转发至相应的消息队列,Direct消费者接收并把它传递给Invoker。
RPC.CAST的远程调用流程与RPC.CALL类似,只是缺少了系统消息响应流程。一个Topic消息生产者发送系统请求消息到Topic交换器,Topic交换器根据消息的Routing Key将消息转发至共享消息队列,与共享消息队列相连的所有Topic消费者接收该系统请求消息,并把它传递给响应的Worker进行处理,其调用流程如图所示: