Nova中的RPC

Openstack遵循这样的设计原则:项目之间用RESTful API通信;项目内部不同服务之间必须经过消息总线。
#远程过程调用(RPC,Remote Procedure Call)
通过远程过程调用,一个服务可以调用其他远程服务进程的方法,并且有两种调用方式:call和cast。通过call调用,远程方法会被同步执行,调用者会被阻塞知道结果返回;通过cast的方式调用,远程方法会被异步执行,结果并不会立即返回,调用者也不会被阻塞,但是调用者需要利用其他方式查询这次远程调用的结果。
#怎样理解阻塞非阻塞与同步异步的区别(https://www.zhihu.com/question/19732473)

同步与异步

同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,*被调用者 *通过状态、通知来通知调用者,或通过回调函数处理这个调用。典型的异步编程模型比如Node.js举个通俗的例子:你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。还是上面的例子,你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

引用说明:
作者:严肃
链接:https://www.zhihu.com/question/19732473/answer/20851256
来源:知乎
#消息队列
Openstack所支持的消息总线类型中大部分都是基于AMQP(高级消息队列协议)
##消息队列相关架构
###AMQP架构
AMQP是一个异步消息传递所使用的开放的应用层协议,主要包括消息的导向、队列、路由、可靠性和安全性。通过定义消息在网络上传输时的字节流格式,不同的具体AMQP实现之间可以进行互操作,AMQP的架构如图1-1

AMQP架构
图1-1

AMQP可以看成三个组件,发布者、中间件、订阅者。

  • 发布者:消息发布方
  • 中间件:包括消息的路由、交换(Exchange实现),消息的存储(queue中存储)
  • 订阅者:消息接收方
    简单来说,发布者把消息发送给exchange,Exchange将详细存储到queue中,订阅者从queue中获取消息。下面细分一下。

AMQP模型的四个重要角色:

  • Exchange:根据 Routing key 转发消息到对应的 Message Queue 中
    • direct: binding key 和 routing key 必须完全一致,不支持通配符
    • topic: 同direct类型,但支持通配符,“*” 匹配一个单子,“#” 匹配一个零个或者多个单字,单字之间由"."分隔
    • fanout: 忽略binding key和routing key,消息会被传递到所有绑定的队列上
  • Routing key:用于 Exchange 判断哪些消息需要发送对应的 Message Queue
  • Publisher:消息发送者,将消息发送的 Exchange 并指明 Routing Key,以便 Message Queue 可以正确的收到消息
  • Consumer:消息接受者,从 Message Queue 获取消息

消息发布者 Publisher 将 Message 发送给 Exchange 并且说明 Routing Key。Exchange 负责根据 Message 的 Routing Key 进行路由,将 Message 正确地转发给相应的 Message Queue。监听在 Message Queue 上的 Consumer 将会从 Queue 中读取消息。
Routing Key 是 Exchange 转发信息的依据,因此每个消息都有一个 Routing Key 表明可以接受消息的目的地址,而每个 Message Queue 都可以通过将自己想要接收的 Routing Key 告诉 Exchange 进行 binding,这样 Exchange 就可以将消息正确地转发给相应的 Message Queue。

基于AMQP实现RPC如图1-2:

在这里插入图片描述
图1-2

  • Exchange:消息交换,客户端发送一个请求消息给Exchange,指定routing key为“op_queue”,同时指明一个消息队列名来获取响应,图中为“res_queue”。
  • Exchange把此消息转发到消息队列op_queue。
  • 消息队列op_queue把消息退给服务端,服务端执行此RPC调用所对应的任务。执行结束后,服务端把响应结果发送给消息队列,指明routing key为“res_queue”。
  • Exchange把此消息转发到消息队列res_queue。
  • 客户端从消息队列res_queue中获取相应。

RPC.cast请求

如图2-1(参考https://docs.openstack.org/developer/nova/rpc.html ):

  • 请求发起者的消息被实例化之后发往消息队列系统
  • 一旦消息被发送Exchange,Exchange会根据routing key发送给指定的消费者读取,并传递给请求处理者

这里写图片描述
图2-1

源码分析

Nova RPC

Nova中各服务间的通信是基于AMQP实现的RPC机制,其中nova-compute、nova-conductor和nova-scheduler在启动时都会注册一个RPC Server,而nova-api因为Nova内部没有服务去调用他提供的接口,故无需注册。
以nova-compute为例:

# nova/compute/rpcapi.py
class ComputeAPI(object):
  def __init__(self):
    super(ComputeAPI, self).__init__()
    target = messaging.Target(topic=CONF.compute_topic, version='4.0')
    version_cap = self.VERSION_ALIASES.get(CONF.upgrade_levels.compute,
                                           CONF.upgrade_levels.compute)
    serializer = objects_base.NovaObjectSerializer()
    self.client = self.get_client(target, version_cap, serializer)
    #self.client会在下面live_migration方法中用到

    def get_client(self, target, version_cap, serializer):
    #__init__方法中有调用get_client方法,这个方法是获取目标机的RPC client信息
    return rpc.get_client(target,
                          version_cap=version_cap,
                          serializer=serializer)

  def live_migration(self, ctxt, instance, dest, block_migration, host,
                   migration, migrate_data=None):
    #nova-compute对nova其他服务提供的远程可调用RPC接口,这儿以live_migration方法实现。
    args = {'migration': migration}
    version = '4.2'
    if not self.client.can_send_version(version):
        version = '4.0'
        args.pop('migration')
    cctxt = self.client.prepare(server=host, version=version)
    #调用__init__方法中的self.client获取目标机RPC Client信息,prepare为python元类属性
    cctxt.cast(ctxt, 'live_migration', instance=instance,
               dest=dest, block_migration=block_migration,
               migrate_data=migrate_data, **args)
    #RPC cast主要用于异步形式,例如创建虚拟机,在创建过程中可能需要较长时间,
    #如果使用RPC call显然对性能有很大影响。cast()的第二个参数是RPC调用的函数名,
    #后面的参数将作为参数传入该函数。
    #cast 函数未在nova/compute/rpcapi.py中以及rpcapi.py所import的包中找到,
    #但只在nova/compute/cells_api.py中有找到,此处不明晰,特希大牛指点。

ComputeAPI中的函数就是Compute服务提供给RPC调用的端口,其他服务调用前需首先import这个模块(在 Nova Project 中大多数的服务都提供了 API 或 RPC API 的实现文件,这些 API 是服务进程自身为了能被其他的服务进程访问所提供出来的一种接口,当别的服务进程希望影响另一个服务进程时,就可以通过 import 另一个服务进程的 rpcapi 就可以实现了。)

其他服务对Nova RPC的调用(以nova-conductor通过RPC通知nova-compute创建虚拟机为例)

流程如图2-2
在这里插入图片描述
图2-2

  • nova-compute提供给Nova其他组件调用的rpcapi接口。nova-conductor通过import nova-compute的compute-rpcapi或者通过实例传参来compute_rpc对象。这样 nova-conductor 就拥有了通过 RPC 访问 nova-compue 的能力。
  • 在 nova-conductor 的代码实现中调用了 rpcapi 模块的方法,即 nova-conductor发送了一个请求到 Queue,并等待 nova-compute 接受和响应。
  • nova-compute 接收到 nova-conductor 的请求,调用nova-compute的manage模块来处理这个请求。
#nova/nova-conductor/tasks/live_migrate.py
class LiveMigrationTask(base.TaskBase):
    def __init__(self, context, instance, destination,
                 block_migration, disk_over_commit, migration, compute_rpcapi,
                 servicegroup_api, scheduler_client):
        super(LiveMigrationTask, self).__init__(context, instance)
        self.destination = destination
        self.block_migration = block_migration
        self.disk_over_commit = disk_over_commit
        self.migration = migration
        self.source = instance.host
        self.migrate_data = None

        self.compute_rpcapi = compute_rpcapi
        self.servicegroup_api = servicegroup_api
        self.scheduler_client = scheduler_client

    def _execute(self):
        self._check_instance_is_active()
        self._check_host_is_up(self.source)

        if not self.destination:
            self.destination = self._find_destination()
            self.migration.dest_compute = self.destination
            self.migration.save()
        else:
            self._check_requested_destination()

        # TODO(johngarbutt) need to move complexity out of compute manager
        # TODO(johngarbutt) disk_over_commit?
        #调用 ComputeAPI 类中的 live_migration() RPC接口,以RPC的方式发出一个请求到Queue再被nova-compute接收
        return self.compute_rpcapi.live_migration(self.context,
                host=self.source,
                instance=self.instance,
                dest=self.destination,
                block_migration=self.block_migration,
                migration=self.migration,
                migrate_data=self.migrate_data)

ComputeAPI类只是暴露给其他服务的RPC调用接口,Compute的RPC Server接受到RPC请求后,真正完成任务的是nova.compute.manager模块。

nova.compute.manager

nova.compute.manager 会一直在监听 Queue ,当Queue中存在相关的 RPC 请求时,实际上是由 manager 来实现的。

#nova/compute/manager.py
class ComputeManager(manager.Manager):
  def live_migration(self, context, dest, instance, block_migration,
                   migration, migrate_data):
    """Executing live migration.

    :param context: security context
    :param dest: destination host
    :param instance: a nova.objects.instance.Instance object
    :param block_migration: if true, prepare for block migration
    :param migration: an nova.objects.Migration object
    :param migrate_data: implementation specific params

    """

    # NOTE(danms): Remove these guards in v5.0 of the RPC API
    if migration:
        migration.status = 'queued'
        migration.save()

    def dispatch_live_migration(*args, **kwargs):
        with self._live_migration_semaphore:
            self._do_live_migration(*args, **kwargs)

    # NOTE(danms): We spawn here to return the RPC worker thread back to
    # the pool. Since what follows could take a really long time, we don't
    # want to tie up RPC workers.
    utils.spawn_n(dispatch_live_migration,
                  context, dest, instance,
                  block_migration, migration,
                  migrate_data)  

从ComputeAPI到ComputeManage的过程即是RPC调用过程。

整个过程 wsgi接受到请求去找resource resource会具体实现动作
resource-> compute.api->conductor.api->conductor.rpcapi (nova-api开始rpc调用nova-Conductor)
Conductor.manager接到调用开始处理 Conductor针对live-migration有个单独的tasks
Conductor rpc调用nova -compute
nova-compute manager接到调用开始处理 将请求传给后端driver也就是libvirt migration进行迁移操作

总结:

rpc调用中,
首先要有rpcapi
其次调用时不能直接调用rpcapi,api会对rpcapi进行封装,暴露给外面的其实是api

调用端: 调用的api–>要调用的api-要调用的rpcapi
被调用端: 对应的manager接收调用请求,并对调用行为进行实际动作

参考:
http://blog.youkuaiyun.com/jmilk/article/details/52116458
《Openstack设计与实现》
http://blog.youkuaiyun.com/li_101357/article/details/52841107

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值