专栏首页腾讯云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

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:鹏飞
原始发表时间:2020-12-17
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 从消息队列看OpenStack

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

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

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

    腾小云
  • 从“消息队列”到“服务总线”和“流处理平台”

    队列是一种先进先出的数据结构,特殊之处在于它只允许在队列的前端(front)进行删除操作,而在队列的后端(rear)进行插入操作。

    业余草
  • 【实践】消息队列RabbitMQ从入门安装到精通原理

    从安装环境,配置入门,到HelloWorld实操,各种类型消息传递的演示代码,原理介绍,答疑解惑,面试题,全面介绍RabbitMQ消息队列。 RabbitMQ...

    辉哥
  • 消息队列之Kafka——从架构技术重新理解Kafka

    在Kafka中,客户端和服务器使用一个简单、高性能、支持多语言的 TCP 协议.此协议版本化并且向下兼容老版本, 我们为Kafka提供了Java客户端,也支持许...

    用户5546570
  • 从Linux源码看Socket(TCP)的listen及连接队列

    笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情。 今天笔者就来从Linux源码的角度看下Server端的Socket在...

    呆呆
  • OC-从方法的汇编层看消息转发流程

    CacheLookup Normal,objc_msgSend(sel,imp)

    Wilbur-L
  • C#消息队列(RabbitMQ)零基础从入门到实战演练

    跟着阿笨一起玩NET
  • 如何从0到1设计一个消息队列(Message Queue)

    说起消息队列,早期有“上古”的 ActiveMQ,如今有应用广泛的 RocketMQ、Kafka,到最近推出的 Pulsar,伴随着技术的持续发展,一代又一代的...

    用户1564362
  • 今儿咱说说消息那些事 | 从开发角度看应用架构17

    不是。例如Java应用对应用数据的访问,通过JPA的标准,实现ORM,这种方式就不是消息。

    魏新宇
  • 消息中间件RabbitMQ系列,直连模式,实现利用代码从队列里面取出数据(四)

    之前已经利用代码,将一些数据放到了队列里面,现在我们要实现利用代码从队列里面拿出数据。

    一天不写程序难受
  • 从历史看未来,大规模微服务系统的困境----基于消息的架构的回归

    在大规模分布式系统的架构上,微服务系统是现在很多大型互联网公司的架构方向。 这是一个务实的很好的方向,相对于旧的宏服务来说。 然而,像淘宝这种规模的系统,微服务...

    Linker
  • 高并发系列:架构优化之从BAT实际案例看消息中间件的妙用

    说到Java中的队列应该都不会陌生。其具有通过先进先出,或者双端进出的方式进行数据管理;通过阻塞以达到自动平衡负载的功能。

    Coder的技术之路
  • 昨晚直播错过了?小编给你划重点!(附直播提问中奖名单)

    导语:在大家的期待中,腾讯云TStack首席架构师 贺阮 和 美女产品经理 Kitty 昨晚做客他二哥技术直播间,为大家揭开了腾讯云TStack的神秘面纱。为了...

    腾讯技术工程官方号
  • 用java程序完成从kafka队列读取消息到sparkstreaming再从sparkstreaming里把数据导入mysql中

    有一段时间没好好写博客了,因为一直在做一个比较小型的工程项目,也常常用在企业里,就是将流式数据处理收集,再将这些流式数据进行一些计算以后再保存在mysql上,这...

    gzq大数据
  • OpenStack新手指南:Nova基础知识

    Nova基础知识第二部分 OpenStack 新手指南

    神话_Tyrannosaurus
  • 史上最全全全全的Cell V2干货详解在这!

    本文作者 / 鹏飞师兄 专注于OpenStack计算、Python; 热爱大海、雪山。 Cell V2详解 Cell V2 第一次出现是在 Ocata 版本,...

    腾讯云TStack
  • 昨晚直播错过了?小编给你划重点!

    导语 昨晚大家期待已久 揭秘腾讯云TStack面纱的的直播间里 究竟讲了什么呢? 让小编带你看重点! 文末有昨晚直播提问中奖的同学名单哦! 最近拿奖拿到手软的...

    腾讯云TStack
  • 016.OpenStack及云计算(面试)常见问题

    云计算是一种采用按量付费的模式,基于虚拟化技术,将相应计算资源(如网络、存储等)池化后,提供便捷的、高可用的、高扩展性的、按需的服务(如计算、存储、应用程序和其...

    木二

扫码关注腾讯云开发者

领取腾讯云代金券