专栏首页腾讯云TStack专栏从消息队列看OpenStack

从消息队列看OpenStack

点击上方“腾讯云TStack”关注我们

获取最in云端资讯和海量技术干货

本文作者:鹏 飞

专注于OpenStack计算、Python。

热爱大海、雪山。

以往介绍openstack的文章通常都是从各个组件的整体角度来进行介绍,并没有深入的介绍组件内部服务究竟是如何通信的。

本文这次将换一个角度,从消息队列的角度来看openstack。文章将以pike版本中的nova组件为例进行介绍,由于openstack中所有组件内部服务的通信方式都是一致的,因此下面的内容也同样适用于其它组件,如neutron、cinder等。

Nova整体架构

下面这个图只画出了nova组件中最核心的4个服务,即nova-api、nova-conductor、nova-scheduler和nova-compute。服务之间通过消息队列,即图中的mq进行通信(这里的mq几乎默认都是rabbitmq)。

其中api、conductor、scheduler服务都可以配置多进程、多副本以实现服务的高可用和高并发,而compute服务的数量则可能多达上千个。

在nova组件的众多功能中,创建虚拟机功能应该是最能够说明nova组件内部协作的功能了。创建虚拟机时,nova-api服务接收到来自用户的http请求,在进行一些必要的处理之后,通过消息队列将创建流程转交给nova-conductor,之后nova-api会给用户返回响应,而不会等待虚拟机创建完成,虚拟机创建将进入后台运行阶段。

nova-conductor服务从消息队列中收到虚拟机创建请求后,将会进入一个长时间的虚拟机创建流程。首先nova-conductor将会通过消息队列调用nova-scheduler,为虚拟机选择一个可用的计算节点;之后又会通过消息队列将创建请求发送给nova-compute服务。

nova-compute服务在收到虚拟机创建请求后,会执行一系列的虚拟机创建操作,其中还包括更新数据库。但更新数据库并不是由nova-compute自己实现,而是会通过消息队列将更新数据库操作委托给nova-conductor,由nova-conductor代理完成。

以上就是虚拟机创建流程的一个简要说明,从创建流程中可以看到,消息队列对于openstack至关重要。再举一个虚拟机启动的例子,启动虚拟机时nova-api服务将收到来自用户的http请求,之后nova-api将会通过消息队列将虚拟机启动请求发送给虚拟机所在的计算节点,对应计算节点上的nova-compute服务将会收到启动请求,并将指定的虚拟机启动。

但有时候可能会遇到这样的问题,就是通过nova service-list命令看到某个计算节点上的nova-compute服务明明是up的(这表明计算节点上的nova-compute服务是正常运行的,同时还能够正常的上报数据到nova数据库中),但是执行虚拟机启动操作时却没有任何效果,观察nova-compute服务日志找不到任何相关的记录,同时虚拟机卡在启动状态中。对于此类问题,仅仅通过前面的介绍是无法知道根本原因的,必须要进入到消息队列层面才能够明白为什么会发生这类问题。

从MQ来看Nova

注: 在openstack中,默认使用的消息队列是rabbitmq,因此下面的内容全部基于rabbitmq,

关于rabbitmq的基础知识可以在官方文档:https://www.rabbitmq.com/getstarted.html中找到。

打开rabbitmq management页面,在Exchanges标签页下面可以看到很多的rabbitmq exchange,如下图所示(由于篇幅限制,图中只过滤显示了部分exchange)。其中与openstack相关的exchange主要分为3类:

  • 以nova、neutron、openstack等命名的exchange;
  • 以reply开头的exchange;
  • 以fanout结尾的exchange;

下面会依次说明这3类exchange的作用。另外还有一些以amq开头的exchange,这些是rabbitmq默认的exchange,在openstack中不会使用,因此不用关注。

nova exchange

以组件名称命名的exchange是各个组件内部服务之间通信的核心。下面这个图显示了一个controller节点(控制+计算融合节点)和一个单独的compute节点组成的openstack环境中nova exchange的具体内容。

图中第1部分定义了当前exchange的名称,不同的openstack项目,其exchange的名字不同,但通常和项目的名称一致(cinder默认的exchange名称为openstack,可以通过cinder.conf进行修改;但对于nova和neutron这两个项目,则都是在代码中写死的)。对于nova项目,可以在nova/config.py中找到默认的exchange名称定义。

第2部分指明了nova exchange的type为topic类型(即主题交换机)。rabbitmq支持3种类型的交换机,分别是direct、fanout和topic,关于这三种交换机类型可以从rabbitmq的官方文档中找到详细的说明,在后面的内容中也将简要的说明这几种交换机的特点。

第3部分则展示了当前连接到nova exchange上的所有队列。其中To所在列表示当前连接到nova交换机的所有队列名称,Routing key则指明了nova交换机与指定队列之间的关联关系。当客户端发送消息给nova exchanges时:

  • 如果指定了路由关键字为compute.controller,则消息将被发送到compute.controller队列中;
  • 如果指定了路由关键字为scheduler.controller,则消息将被发送到scheduler.controller队列中;

还可以点击图中的队列名称,进一步查看队列的详细信息,下图是scheduler.controller队列的部分信息,其中包括当前在队列中的消息数量,向队列中添加消息的速率以及消费速率等信息。

红框部分则表明了当前连接到队列的消费者,该消费者来自192.168.60.211节点,对应40054端口的进程。通过命令ss -pn | grep 40054可以看到该进程实际上就是nova-scheduler服务进程。

通过前面观察rabbitmq中的exchange以及队列等信息,我们可以画出生产者、消息队列、消费者之间的简要关系。

图中最左边部分画出了集群中部分nova服务进程,此处这些nova服务进程的作用是作为生产者向rabbitmq发送消息;中间灰色线框部分是rabbitmq;最右边部分也是集群中的nova服务进程,它们和左边的生产者实际上是相同的进程,只不过在此处它们作为消费者接收并处理指定队列中的消息。(nova组件中的服务即是生产者,也是消费者)

以上一章节中提到的虚拟机启动为例,根据这里的消息队列模型再看一下虚拟机的启动流程,按照上图红色部分从左向右。

首先controller节点上的nova-api服务进程收到来自用户的虚拟机启动请求;nova-api查询到虚拟机位于计算节点compute上,因此构造rpc请求消息,将消息发送给nova exchange,并指定routing keycompute.compute;消息将根据路由键被发送到compute.compute队列中;最终绑定并消费该队列的nova-compute服务(计算节点compute上的进程)将获取到消息,并调用相应的函数执行虚拟机开机操作。相关的rpc调用代码可以在nova/compute/rpcapi.py中找到

# 1. nova-compute服务默认的rpc topicRPC_TOPIC = "compute"
@profiler.trace_cls("rpc")class ComputeAPI(object):    def __init__(self):        super(ComputeAPI, self).__init__()        # 2. 所有nova-compute服务都默认将消息发送到compute topic        target = messaging.Target(topic=RPC_TOPIC, version='4.0')        #...
    # 3. 启动虚拟机涉及的rpc调用方法    def start_instance(self, ctxt, instance):        version = '4.0'        # 4. _compute_host(None, instance)将会返回虚拟机所在计算节点主机名,因此最终消息将会发送        # 给compute.<compute_hostname>队列,如果不指定server参数,则消息将被发送给compute队列        cctxt = self.router.client(ctxt).prepare(                server=_compute_host(None, instance), version=version)        # 5. 此处cast表明是异步rpc调用,即只是将消息发送给nova-compute服务,不等待计算节点执行完成        cctxt.cast(ctxt, 'start_instance', instance=instance)

replay exchange

在前面虚拟机启动相关的rpc调用函数中提到cctxt.cast方法是用于异步rpc调用的,即不会等待被调用方执行完成。

在openstack中,还有另外一种rpc调用,即同步rpc调用,对应的方法为cctxt.call,该方法被执行后,将会等待被调用方执行完成。

下面的代码同样来自nova/compute/rpcapi.py文件,该方法用于获取计算节点的运行时间。

# 获取计算节点的运行时间def get_host_uptime(self, ctxt, host):    version = '4.0'    # 通过server参数,指定将rpc调用请求发送给哪个队列,相应的计算节点将会收到消息并处理    cctxt = self.router.client(ctxt).prepare(            server=host, version=version)    # 同步rpc调用`cctxt.call`会等待被调用方执行完成并返回结果    return cctxt.call(ctxt, 'get_host_uptime')

将查询主机运行时间的rpc消息发送给指定的计算节点,这一过程与前面一节是完全一样的。不同点在于同步rpc调用与异步rpc调用,同步rpc调用由于需要获取远端方法的执行结果,因此需要有一种方法能够将远端方法的执行结果返回给调用者。

关于这一过程的实现原理可以参考

rabbitmq官方文档: https://www.rabbitmq.com/tutorials/tutorial-six-python.html

用通俗易懂的方式来说就是,同步rpc调用时,客户端在发送给服务端的请求中,还会附加一个队列的名字,该队列用于告诉服务端,在方法执行完成后将执行结果发送到我给你的队列里面。而客户端在发送了rpc调用请求后,则会一直监听用于返回结果的队列,直到有结果返回或者响应超时。(在返回结果时,原来的服务端变成了消息的生产者,客户端变成了消息的消费者。)

在这里提到的用于返回函数执行结果的队列,就是那些以reply开头的队列,后面跟着一个随机生成的uuid。

这些队列不是绑定到nova exchange上的,而是为这些reply队列创建了同名的exchange,这些exchange的类型为direct类型。

并且在服务第一次调用call方法时会生成该队列,之后在服务重启之前会一直使用该队列作为reply队列。至此,同步rpc调用的简要流程可以通过下面这个图简要的表示出来

fanout exchange

以fanout结尾的exchange的作用是对所有相关的服务进行广播,以nova-scheduler服务为例,当有多个nova-scheduler服务进程时,每个nova-scheduler进程都会生成一个队列并绑定到scheduler_fanout exchange上。

在通过这个scheduler_fanout进行消息广播时,所有的nova-scheduler进程都将接收到消息。下图是在一个控制节点上启动了3个nova-scheduler进程时,与scheduler_fanout exchange绑定的队列

使用广播给服务发送消息的方式,在nova中主要用于通知nova-scheduler服务更新缓存信息,比如通知所有的nova-scheduler服务进程更新主机可用域信息。在用户调用nova-api接口修改主机所在可用域的时候,nova-api服务就会通过广播的方式将计算节点的可用域信息广播给所有的nova-scheduler服务进程,使得nova-scheduler服务能够及时的更新内存中缓存的可用域信息,以便于正确的完成虚拟机调度。

下面的代码来自nova/scheduler/rpcapi.py文件,其作用就是通过广播的方式通知所有nova-scheduler服务进程完成内存数据的更新

def update_aggregates(self, ctxt, aggregates):    # 通过fanout参数指定操作为广播操作    cctxt = self.client.prepare(fanout=True, version='4.1')    cctxt.cast(ctxt, 'update_aggregates', aggregates=aggregates)

这里同样使用一个图来简要的表示一下fanout exchange与nova服务之间的关系。下面这个图展示了在有两个控制节点,且每个控制节点上都有两个nova-scheduler进程时的scheduer_fanout exchange及其绑定的队列信息。

从图中可以看到,每个nova-scheduler服务都会有一个队列连接到scheduler_fanout exchange上。因此nova-api在进行广播消息时,每个scheduler_fanout_<uuid>队列里面都将收到消息,所有的nova-scheduler服务进程都能够处理消息。

Nova高可用

nova组件的高可用分为两种,一种是以暴露端口对外提供http调用的服务,比较典型的是nova-api服务,另外还有像placement服务和nova-novncproxy服务;第二种就是像nova-scheduler、nova-conductor这样的服务,这些服务是通过消息队列来接收和处理请求的。

对于nova-api这样通过http对外提供接口的服务,高可用可以借助keepalived+haproxy这样的组合来完成服务的高可用和横向扩展。

但本文的主要目的是从MQ来看openstack,因此nova-api这样的服务的高可用并不是本文的重点,这里想要介绍的是nova-scheduler、nova-conductor这些服务的高可用和横向扩展是如何实现的。下面将以nova-scheduler服务为例进行介绍。

nova-scheduler服务是用于虚拟机调度的组件,如果仅在一台主机上进行部署,则很容易出现单点故障。

在实际的部署中,通常会将其部署在3台不同的物理主机上,以实现服务的高可用,同时还能提高虚拟机调度的并发性能(python进程cpu使用率不能超过100%,对于以计算为主的nova-scheduler服务,会严重限制其并发处理性能,必须通过多进程的方式实现高并发)。

在前面介绍nova exchange时提到有一个scheduler队列,该队列会被所有的nova-scheduler服务进程消费,如下图所示

controller01~03节点上的nova-scheduler服务进程都会消费scheduler队列,当有消息被发送到scheduler队列中时,将会由一个进程获取到该消息并进行处理。

当controller02节点上的nova-scheduler服务发送异常时,消息将会由controller01或controller03节点上的nova-scheduler服务消费。

这就是nova-scheduler服务的高可用实现,同时由于有3个nova-scheduler进程在同时消费scheduler队列中的数据,因此消息的处理速度也得到了很大的提升,从而提升了虚拟机调度的并发性能。

下面的代码来自nova/scheduler/rpcapi.py,其功能就是通过rpc同步调用nova-scheduler完成虚拟机的调度

# 1. 通过rpc同步调用nova-scheduler完成虚拟机调度,通常由nova-conductor服务发起调用def select_destinations(self, ctxt, spec_obj, instance_uuids):    version = '4.4'    #...    # 2. 注意这里prepare方法没有指定server参数,因此消息将被发送给scheduler队列。    # 如果指定了server=controller01,则消息将被发送给scheduler.controller01队列,此时    # 虚拟机调度请求消息将只能被controller01节点上的nova-scheduler服务进程获取并处理。    cctxt = self.client.prepare(version=version)    # 3. rpc同步调用,等待虚拟机调度结果    return cctxt.call(ctxt, 'select_destinations', **msg_args)

Nova健康检查

最后介绍一下如何去判断nova服务是否在正常运行。同nova高可用部分一样,这里对nova的健康检查也分为两种情况,一种是提供http接口的服务,这类服务可以简单地通过curl等命令进行服务状态检查,比如通过curl命令访问8774端口,然后检查一下返回的状态码即可知道nova-api服务是否正常运行;另外一种就是nova-scheduler、nova-conductor这样的内部服务。

对于通过消息队列才能访问到的nova-schduler等服务来说,是没有办法通过curl命令检查其健康状态的。要检查这些服务的健康状态,需要发送rpc同步调用请求,如果目的节点上的指定服务能够正常响应,则说明对应节点上的nova服务运行正常。下面以检测计算节点compute01上的nova-compute服务为例进行说明。

根据nova exchange部分的说明可以知道,compute01节点上的nova-compute服务将会消费两个队列,一个是compute队列,另外一个是compute.compute01队列。

由于compute队列会被所有的nova-compute服务消费,所以如果将消息发送给compute队列(即prepare方法不指定server参数),则消息可能被任意一个nova-compute服务进程消费,即使要检测的nova-compute服务已经无法正常功能,检测仍然会成功。

因此在发送消息时,必须指定server参数为目的计算节点compute01,此时消息将被发送到compute.compute01队列中,同时该队列仅被compute01节点上的nova-compute服务消费,如果此时该服务异常,则消息将不会被消费,直到客户端等待响应超时,就可以知道compute01节点上的nova-compute服务出现了异常。

参考资料

1、https://www.rabbitmq.com/getstarted.html

2、https://opendev.org/openstack/openstack-helm/src/branch/master/nova/templates/bin/_health-probe.py.tpl

往期精彩内容回顾

1

S3请求来了,该怎么处理?

2

初探Docker的网络模式

3

OpenStack Policy鉴权大解密!

听说长得好看的人都点了赞和在看!

本文分享自微信公众号 - 腾讯云TStack(gh_035269c8aa5f),作者:鹏飞

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-12-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 从消息队列看OpenStack

    以往介绍openstack的文章通常都是从各个组件的整体角度来进行介绍,并没有深入的介绍组件内部服务究竟是如何通信的。本文这次将换一个角度,从消息队列的角度来看...

    jiang
  • 从演进式角度看消息队列

    ? 导语 | 市面上有非常多的消息中间件,rabbitMQ、kafka、rocketMQ、pulsar、 redis等等,多得令人眼花缭乱。它们到底有什么异同...

    腾小云
  • 「消息队列」看过来!

    当我试图用一则通俗的比喻来说明这个概念的时候,我想到一个有意思的比喻:如果把队列抽象成一个集合体,那么消息队列也就是一堆消息的集合。按照这个思路我想到了「杂志」...

    我没有三颗心脏
  • 消息队列(一) MySQL实现消息队列

    消息队列(一)MySQL实现消息队列 (原创内容,转载请注明来源,谢谢) 一、概述 消息队列(MessageQueue,通常简称MQ)是一种进程间通信或同一进...

    用户1327360
  • 消息队列-腾讯云消息队列 CKafka

    腾讯云消息队列 CKafka,分布式、高吞吐量、高可扩展性的消息服务,100%兼容开源 Apache Kafka 0.9 0.10

    用户3570397
  • 消息队列

    radaren
  • 消息队列

    发送者将消息发送给消息队列之后,不需要同步等待消息接收者处理完毕,而是立即返回进行其它操作。消息接收者从消息队列中订阅消息之后异步处理。

    全栈程序员站长
  • 消息队列

    一般来说,消息队列是一种异步的服务间通信方式,是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。

    用户4464623
  • 【消息队列 MQ 专栏】消息队列之 Kafka

    Kafka 最早是由 LinkedIn 公司开发一种分布式的基于发布/订阅的消息系统,之后成为 Apache 的顶级项目。主要特点如下:

    芋道源码
  • 【消息队列 MQ 专栏】消息队列之 ActiveMQ

    ActiveMQ 是由 Apache 出品的一款开源消息中间件,旨在为应用程序提供高效、可扩展、稳定、安全的企业级消息通信。它的设计目标是提供标准的、面向消息的...

    芋道源码
  • 【消息队列 MQ 专栏】消息队列之 RocketMQ

    RocketMQ 是阿里巴巴在2012年开源的分布式消息中间件,目前已经捐赠给 Apache 软件基金会,并于2017年9月25日成为 Apache 的顶级项目...

    芋道源码
  • 消息队列探秘-RabbitMQ消息队列介绍

    RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现。AMQP 的出现其实也是应了广大人民群众的需求,...

    高广超
  • 消息队列及常见消息队列介绍

    消息队列是分布式系统中重要的组件,在很多生产环境如商品抢购等需要控制并发量的场景下都需要用到。最近组内需要做流水server的选型升级,这里对消息队列及常见的消...

    曾令武
  • 消息队列探秘 – RabbitMQ 消息队列介绍

    RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现。AMQP 的出现其实也是应了广大人民群众的需求,...

    java思维导图
  • 消息队列_RabbitMQ

    神秘的寇先森
  • Rabbitmq---消息队列

    有了消息队列,每一次连接不管是生成消息还是消费消息,都有各自的逻辑与其他逻辑无关--通信解耦

    Java高级架构
  • FreeRTOS 消息队列

    上面这几中方式中, 除了消息通知, 其他几种实现都是基于消息队列。消息队列作为主要的通信方式, 支持在任务间, 任务和中断间传递消息内容。 这一章介绍 Fre...

    orientlu
  • 消息队列kafka

    在流式计算中,Kafka一般用来缓存数据,Storm通过消费Kafka的数据进行计算。

    超蛋lhy
  • 消息队列rabbitmq/kafka

    消息(Message)是指在应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象。

    超蛋lhy

扫码关注云+社区

领取腾讯云代金券