OpenStack公共组件oslo之六——oslo.messaging

来源:互联网 发布:炒股软件mac版 编辑:程序博客网 时间:2024/05/20 16:09

        oslo.messaging在OpenStack各组件中有非常重要的作用,且也是oslo众多组件中较为复杂的组件。oslo.messaging主要封装了OpenStack各组件之间进行消息通信的方法,并将方法中所使用的数据结构封装为通用的类,以达到使用简单快捷、扩展性强的目的。另外,oslo.messaging除了支持RabbitMQ外,还支持kafka、pika、zmq等多种消息队列。

1. RPC常用概念及oslo.messaging的封装

        oslo.messaging在实现RPC调用时,将常用的概念封装成了多个对应的类。接下来将逐一详细介绍这些概念。

1.1 Target类

        Target类定义了一个消息的目标。一个Target对象包含了一个消息将发送到哪儿,或者一个服务队列将监听什么消息的全部信息。Target类中的属性主要包含有以下几个:

  1. exchange:表示一组topic的范围。如果不显示指定,则默认使用control_exchange配置项的内容。
  2. topic:定义了一组Message服务端暴露的接口。多个Message服务端可监听一个topic的消息,消息将通过轮询调度算法发送到其中一个服务端进行处理。
  3. namespace:定义一个特别的RPC接口。默认的消息接口没有定义namespace,即namespace为null。
  4. version:每个RPC接口都有自己的版本号(major.minor,主版本号.次版本号)。
  5. server:表示RPC服务端,RPC客户端请求将发送到指定的RPC服务端进行处理。
  6. fanout:如果fanout设置为True,则将RPC客户端的请求发送到监听该客户端topic的所有服务端进行处理;否则,发送到其中一个服务端进行处理。
  7. legacy_namespaces:服务端可以利用namespace接收消息,也可以通过legacy_namespaces接收消息。该参数在系统滚动升级时必须指定,以用来安全的选择namespace。

        一个Target对象设置不同的属性会构成不同的子集,其也将具有不同的作用:

  • 如果是RPC服务端的Target,则必须输入topic和server两个参数,exchange参数可选。
  • 如果是RPC endpoint的Target,则可输入namespace和version两个可选参数。
  • 如果是RPC客户端的Target,则必须输入topic参数,其他参数都可选。
  • 如果是Notification服务端或Notifier的Target,则必须输入topic参数,exchange参数可选,其他参数直接忽略。

1.2 Transport类

        Transport类封装了RPC消息的传输方式,其提供了一个传输基类。该类有两个主要的子类:RPCTransport类,用来定义RPC消息的传输方式;NotificationTransport类,用来定义Notification通知消息的传输方式。在实际使用过程中,通常会调用get_rpc_transport(conf, url=None, allowed_remote_exmods=None)和get_notification_transport(conf, url=None, allowed_remote_exmods=None)方法分别创建Transport类的实例。
        Transport类主要包含两个主要属性:
  1. conf:表示一个ConfigOpts对象,可用于配置消息队列和RPC调用。
  2. _driver:表示具体实现Transport类中传输方法的驱动类。在oslo.messaging中,OpenStack为各种消息队列(目前包括RabbitMQ、kafka、zmq、pika等)都实现了具体发送和接收消息的驱动类Driver;因此,在实例化Transport时,系统将根据配置文件指定的消息队列创建一个Driver对象,用来操作具体的消息队列。
        Transport类还定义了用于消息处理的多个方法:
  1. _send(target, ctxt, message, wait_for_reply=None, timeout=None, retry=None):发送RPC消息。
  2. _send_notification(target, ctxt, message, version, retry=None):发送Notification通知消息。
  3. _listen(target, batch_size, batch_timeout):令服务端监听某一类topic消息。
  4. _listen_for_notifications(targets_and_priorities, pool, batch_size, batch_timeout):为服务端监听某一类通知消息。
  5. cleanup():清空Transport对象中的所有内容。
  6. _require_driver_features(requeue=False):设置Driver的特性。

1.3 DispatcherBase类

        DispatcherBase类定义了RPC消息的分发机制。其主要包含了一个方法:dispatch(incoming),该方法可以将接受到的消息分发到对应的endpoint上并返回结果,incoming表示一个用于分发消息的实例化对象。

1.4 MessageHandlingServer类

        MessageHandlingServer类用来表示一个处理消息的服务端。其通过一个知道应用如何创建新任务的线程将一个Transport对象和一个Dispatcher对象联系起来处理消息。其继承自oslo_service.service.Service类和oslo.messaging的内部_OrderedTaskRunner类,一次来实现一个一直运行的RPC服务端。其包含以下主要的参数:
  1. transport:Transport类的一个对象,用于定义该RPC服务端的消息传输方式。
  2. dispatcher:DispatcherBase类的一个对象,为RPC服务端定义消息分发机制。
  3. excutor:表示RPC服务端消息的类型,包括eventlet和threading两种类型。
        MessageHandlingServer由于继承了Service类,所以也需要覆写Service类的start()、stop()、wait()、reset()等生命周期管理方法。除此之外,该类还定义了_create_listener()方法创建RPC消息监听器,_process_incoming(incoming)方法处理接收到的消息请求,_on_incoming(incoming)方法处理on_incoming事件。

1.5 Client

        有Server服务端,便有客户端Client。在oslo.messaging中,针对不同的消息队列,系统实现了不同的客户端类,下文中将详细分析使用RabbitMQ作消息队列的Client实现。
        oslo.messaging定义了两类消息通信机制:RPC调用和Notification。前者的客户端一般通过_call()和_cast()发送消息,服务端会找到与其topic对应的队列进行处理;而后者则是另一种典型了发布者/订阅者模式,用于向用户和开发者发送通知。下面,本文将详细分析oslo.messaging中RPC调用和Notification的实现原理。

2. RPC的实现原理

        在oslo.messaging组件中,源码在oslo_messaging/rpc目录下,实现了RPC调用;另外,由于消息队列类型众多,oslo.messaging在oslo_messaging/_drivers目录下提供了针对具体消息队列的驱动程序。
        在rpc目录下,主要实现了RPC调用的客户端(client.py)、服务端(server.py)、分发机制(dispatcher.py)以及传输方式(transport.py)。下面将对各部分实现分别进行分析:

2.1 Transport

        rpc的transport.py文件实际上非常简单,其只实现了一个方法:get_rpc_transport(conf, url=None, allowed_remote_exmods=None).在实际使用过程中,如果需要建立RPC连接,直接调用该方法即可。

2.2 Dispatcher

        rpc的dispatcher.py文件则实现了RPC通信的分发机制,其主要实现了一个RPCDispathcer类,用来实现RPC调用的分发机制。该类主要实现了dispatch(incoming)方法用来定义消息的分发方式。该类首先解析消息队列中缓存的一条消息对象incoming,获取其消息内容message和消息上下文ctxt,并根据message获取消息对应的endpoint和method;然后调用_do_dispatch(endpoint, method, ctxt, args)进行分发操作。在该方法中,通过endpoint和method两个字段找到对应的消息处理函数,并执行函数,返回结果即可实现消息处理操作。

2.3 Server

        rpc的server.py主要实现了一个继承自MessageHandlingServer的RPCServer类,并在该类中实现了_create_listener()方法,实现对消息的监听和接收,其实际调用了RPCServer对象所关联的RPCTransport对象的_listen(target)方法;还实现了_process_incoming(incoming)处理接收到的消息,其主要是从消息队列incoming中选择一条消息,并调用RPCServer对象关联的RPCDispatcher对象的dispatch()方法找到对应处理消息的方法进行处理。
        一般地,在使用时,server.py还提供了get_rpc_server(transport, target, endpoints, executor='blocking', serializer=None, access_policy=None)方法获取一个RPCServer对象,其中transport表示服务端关联的传输方式,与消息队列类型相关;target表示该服务端可以接收的消息类型;endpoints表示消息队列的endpoint列表,与RPCDispatcher对象有关。

2.4 Client

        rpc的client.py主要实现了RPC调用的客户端RPCClient类,该类即是用来发送消息的类;创建RPCClient对象也需要初始化两个参数:transport,表示传输方式的对象,需要与Server端的传输方式对应,即应用相同的消息队列RPCTransport类;target,表示消息的类型,主要通过topic来区分,即客户端发送的topic消息只能由监听topic消息的服务端接收并处理。该类主要实现了三个方法:
  • prepare():准备一个可以发送消息的环境变量_CallContext对象。_CallContext类继承自_BaseCallContext类,表示一个消息的上下文信息,其中缓存了transport、target、exchange、topic、namespace、version、retry等信息。服务端可以通过这个上下文信息找到消息对应的endpoint信息,进行消息分发。
  • _cast():该方法为发送消息的方法,且该方法不会等待处理消息返回结果,即不会阻塞。该方法调用prepare()方法返回的_CallContext对象的cast()方法发送消息,而最终则是调用Transport对象的_send()方法将消息发送到消息队列中。
  • _call():该方法也是发送消息的方法,与_cast()方法不同的是该方法会等待处理完返回相应的结果。该方法同样会调用_CallContext对象的call()方法发送消息,但是其会等待处理返回结果,并将结果返回给上层。

3. Notification

        Notification是oslo.messaging定义的另一种RPC调用方式,是一种典型的订阅者/发布者模式。oslo.messaging在oslo_messaging/notify中实现了Notification模式的RPC调用。在Notification的实现中,主要涉及到一下几个类或方法:
  • Notifier类:该类主要实现了通过一个Transport对象发送notification通知消息的功能。在实例化Notifier对象时,需要传入一个Transport对象,一个发布者的ID以及一个Notification的Driver对象。Notifier类似于上一节中RPC调用的客户端调用;与普通RPC调用同理,在使用Notifier对象发送消息时,也需要调用其prepare()方法创建一个上下文环境。但其发送具体消息时,则调用了_notify(ctxt, event_type, payload, priority, publisher_id=None, retry=None)方法;该方法主要调用了Notification的Driver对象的notify()方法实现消息发送。在具体使用时,定义了不同的方法表示不同优先级的方法,目前oslo.messaging定义的Notification消息优先级有:audit、debug、info、warn、error、critical、sample等。另外,oslo.messaging还实现了一种在请求中发送消息的机制RequestNotifier类,该类可以将发送请求和返回响应的消息发送到对应的监听器上。
  • Driver类:Notification中的Driver主要用来通过消息将Notification通知发送到对应的监听器上;其主要的实现包括MessagingDriver、MessagingV2Driver类,它们分别使用v1格式、v2格式的消息发送通知。Driver对象将阻塞Notifier对象所在的线程,直到消息通过Transport对象传输完成;但其并不能保证对应的监听器会消费该通知消息。另外,需要注意的是同样的Notification通知消息只能发送一次。在Driver对象中,主要实现了notify()方法实现具体的消息传输,即调用Transport对象的_send_notification()方法发送通知。Notification中的Driver除了上述两种实现外,还包括了一下几种常用的实现:通过Python日志框架发送通知的LogDriver,空操作(即notify()方法为空,即不传输该通知)的NoopDriver,用于缓存测试数据通知的TestDriver以及使用路由方式实现的RoutingDriver等。
  • Dispatcher类:同样的,Notification机制的RPC调用也定义了其自身的分发机制NotificationDispatcher和BatchNotificationDispatcher类,将通知分发到对应的endpoint方法中。其实现与RPC调用的Dispatcher实现类似,在此不再赘述。其中BatchNotificationDispatcher用于批量消息的分发。
  • Listener类:Notification机制中的Listener的作用即是普通RPC调用中服务端Server的作用,其实现也是类似的。Notification中定义了NotificationServer、BatchNotificationServer类来实现消息的监听与分发处理操作等。其中BatchNotificationServer类多用于处理批量的消息。
  • NotificationFilter类:该类主要用在Dispatcher类中对消息进行过滤。一般地,在NotificationDispatcher和BatchNotificationDispatcher类的dispatch()方法中,通过调用NotificationFilter对象的match()方法对消息的上下文信息context、发布者的publish_id、事件类型event_type、元数据metadata以及载荷payload等对消息进行过滤,从而使得监听器只处理监听器监听的消息。

4. RPC消息传输的实现原理

        在RPC和Notification的实现中说到,oslo.messaging中通过Transport将消息Client端和Server端联系在一起。而Transport中定义了一个driver属性实现发送和接收消息的方法,该driver属性针对不同的消息队列提供不同的实现方式,包括符合AMQP协议的和不符合的AMQP协议的不同消息队列服务,而其所有的实现方式都定义在oslo_messaging/_drivers目录下。本节将详细分析oslo.messaging中针对RabbitMQ的Driver实现方法。
        在理解Driver的实现之前,首先需要对RabbitMQ中常用的几个概念有所了解:
  • Exchange:它指定了消息按什么规则路由到哪个队列,相当于消息队列的交换机。
  • Queue:消息队列,每个消息经过Exchange都会发送到一个或多个队列中等待处理。
  • Binding:将Exchange和Queue通过Binding绑定在一起。
  • Routing Key:路由关键字,Exchange通过该关键字将消息发送到对应的队列中。
  • Producer:消息生产者,会将消息发送到Exchange中。
  • Consumer:消息消费者,从消息队列中获取消息并进行处理。
  • Connection:为了实现消息通信,RabbitMQ需要建立Socket连接。
  • Channel:消息通道,每个Socket连接可以建立多个Channel,RabbitMQ发送和接收消息都是通过Channel实现的。
  • Broker:表示消息队列服务器实体。
  • vhost:虚拟主机,一个Broker可以开设多个vhost,用作不同用户的权限分离等。
        图1简单描述了RabbitMQ的结构,以及上述各个概念之间的联系。

图1 RabbitMQ结构
        另外,除了这些常用的概念之外,我们还需要了解RabbitMQ中Exchange的三种模式:
  1. direct模式:任何发送到direct模式的Exchange的消息都会根据消息中指定的Routing Key直接发送到对应的队列中。
  2. fanout模式:任何发送到fanout模式的Exchange的消息都会发送到与该Exchange相连的所有队列中,类似于广播。
  3. topic模式:任何发送到topic模式的Exchange的消息都会发送到与topic对应的队列中,使用的是模糊匹配,所有与指定Routing Key对应的队列都可以接收到该消息。
        简单了解了RabbitMQ的结构以及涉及的一些概念之后,下面开始详细分析oslo.messaging针对RabbitMQ实现的RPC调用驱动程序。简单来说,RabbitMQ的驱动程序的作用就是实现消息在RabbitMQ中的发送和接收等操作,只不过oslo.messaging在实现这些操作时对操作进行了进一步封装,使得程序更具有通用性和重用性。而由于RabbitMQ是符合AMQP协议的消息队列,因此oslo.messaging在设计RabbitMQ的实现时,很多处理方式也是直接使用了AMQP协议的驱动来实现。oslo.messaging针对RabbitMQ的消息处理流程,对一下几个概念进行了封装:
  • RabbitDriver类:可以理解为消息生产者,实现了消息的发送。该类继承自AMQPDriverBase类,其中定义了send和send_notification两个发送消息的方法;而这两个方法最终将会调用其内部的_send()方法实现消息的发送。该类还有两个重要的属性和方法:_waiter属性,该属性表示一个ReplyWaiter对象,上文中提到_call()方法发送消息将会等待返回结果,其底层便是通过ReplyWaiter对象实现的;_get_connection(purpose=rpc_common.PURPOSE_SEND)方法,该方法用于获取一个RabbitMQ的Connection对象,通过该连接对象实现消息的发送和接收。在_send()方法中,系统会根据其输入的参数判断具体使用哪种方式发送消息:如果输入参数notify为True,则调用Connection对象的notify_send()方法发送Notification通知消息;如果输入参数中target.fanout为True,则调用Connection对象的fanout_send()方法实现fanout模式的消息发送;否则,调用Connection对象的topic_send()方法实现topic模式的消息发送。而如果输入参数中wait_for_reply为True,即前端使用_call()方法发送消息,则通过ReplyWaiter的方法等待消息处理结果返回。另外,该类还实现了监听方法listen()和listen_for_notifications()用于监听消息。
  • Consumer类:消息消费者,实现消息的接收和处理。该类主要实现了两个方法:declare()方法,在创建Socket连接后将消息队列Queue和对应的Exchange通过RoutingKey进行绑定;consume()方法,用于接收和消费队列中的消息。
  • Connection类:该类实现了RabbitMQ的Socket连接的封装,其中除了建立连接相关的方法之外,还提供了两组重要的方法:第一组方法是declare_direct_consumer()、declare_topic_consumer()和declare_fanout_consumer(),这一组方法会根据输入参数创建一个Consumer对象,然后将该Consumer对应的Queue和Exchange根据RoutingKey关联;另一组方法是notify_send()、fanout_sned()和topic_send(),这一组方法实现了发送消息的底层逻辑。
  • ReplyWaiter类:前文中提到,使用_call()方法发送消息会等待消息处理完成返回结果,ReplyWaiter类便是实现等待消息处理结果的类。该类中包含了一个ReplyWaiters对象,该对象会将所有_call()请求发送的队列根据消息ID缓存到一个列表中,在发送_call()消息时,首先会调用ReplyWaiter对象的listen()方法,将该消息对应的队列添加到ReplyWaiters对象中;然后发送完消息后会调用ReplyWaiter对象的wait()方法等待消息发送并处理完成,然后获取消息的处理结果并返回;最后,再调用unlisten()方法将该消息对应的队列从ReplyWaiters对象中的列表中移除。
  • AMQPListener类:一个消息队列的监听器。当每次发送消息时,消息生产者会调用AMQPListener对象的__call__()方法,将消息封装并缓存到incoming列表中供消息消费者读取消息。而在接收消息时,消息消费者则会调用AMQPListener对象的poll()方法将消息从incoming消息列表中移除。
  • IncomingMessage/AMQPIncomingMessage类:该类将每一条消息进行了封装,这样便可以使用统一格式进行消息传输。

5. oslo.messaging的使用

        本节根据nova组件详细分析oslo.messaging的使用方法,包括普通RPC调用和Notification通知的使用。首先,在nova各个组件启动前,会调用nova.rpc模块中的init()方法对RPC调用进行基本的初始化操作。在该init()方法中,创建了RPC的Transport对象、Notification的Transport对象,还针对不同的Notification配置创建了不同的Notifier对象。
def init(conf):    global TRANSPORT, NOTIFICATION_TRANSPORT, LEGACY_NOTIFIER, NOTIFIER    exmods = get_allowed_exmods()    TRANSPORT = create_transport(get_transport_url())    NOTIFICATION_TRANSPORT = messaging.get_notification_transport(        conf, allowed_remote_exmods=exmods)    serializer = RequestContextSerializer(JsonPayloadSerializer())    if conf.notifications.notification_format == 'unversioned':        LEGACY_NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,                                             serializer=serializer)        NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,                                      serializer=serializer, driver='noop')    elif conf.notifications.notification_format == 'both':        LEGACY_NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,                                             serializer=serializer)        NOTIFIER = messaging.Notifier(            NOTIFICATION_TRANSPORT,            serializer=serializer,            topics=conf.notifications.versioned_notifications_topics)    else:        LEGACY_NOTIFIER = messaging.Notifier(NOTIFICATION_TRANSPORT,                                             serializer=serializer,                                             driver='noop')        NOTIFIER = messaging.Notifier(            NOTIFICATION_TRANSPORT,            serializer=serializer,            topics=conf.notifications.versioned_notifications_topics)

5.1 RPC调用的用法

        众所周知,Nova组件根据功能的不同将系统分成了多个服务,如API服务nova-api,调度服务nova-scheduler,计算服务nova-compute等,不同服务之间通信便是使用RPC调用。而一般地,Nova组件为每个服务创建一个RPC监听服务器,因此服务在启动时,也需要启动这个RPC服务器。
def main():    config.parse_args(sys.argv)    logging.setup(CONF, 'nova')    priv_context.init(root_helper=shlex.split(utils.get_root_helper()))    utils.monkey_patch()    objects.register_all()    gmr_opts.set_defaults(CONF)    # Ensure os-vif objects are registered and plugins loaded    os_vif.initialize()    gmr.TextGuruMeditation.setup_autorun(version, conf=CONF)    cmd_common.block_db_access('nova-compute')    objects_base.NovaObject.indirection_api = conductor_rpcapi.ConductorAPI()    objects.Service.enable_min_version_cache()    server = service.Service.create(binary='nova-compute',                                    topic=compute_rpcapi.RPC_TOPIC)    service.serve(server)    service.wait()
        上述代码是nova-compute服务的启动程序,程序的最后三行创建并启动了一个对应的RPC服务器,其中的topic参数为"compute",表示该RPC服务端只接受topic为"compute"的消息。启动服务之后,RPC服务端便处于长期运行状态,监听消息队列中的消息。
        当各组件服务启动之后,如果需要发送消息,则需要定义发送消息的客户端,如nova-compute服务在发送消息时定义了ComputeAPI类实现发送消息的方法。使用时,首先需要创建一个ComputeAPI对象,此时会调用ComputeAPI类的__init__()方法初始化参数。
    def __init__(self):        super(ComputeAPI, self).__init__()        target = messaging.Target(topic=RPC_TOPIC, version='4.0')        upgrade_level = CONF.upgrade_levels.compute        if upgrade_level == 'auto':            version_cap = self._determine_version_cap(target)        else:            version_cap = self.VERSION_ALIASES.get(upgrade_level,                                                   upgrade_level)        serializer = objects_base.NovaObjectSerializer()        default_client = self.get_client(target, version_cap, serializer)        self.router = rpc.ClientRouter(default_client)
        在__init__()方法中,用户创建了一个Target对象,传入的topic参数与之前描述的服务端topic一致,为"compute",所以该客户端发送的消息发送到消息队列后,只有对应的nova-compute服务端才能处理该消息。创建Target对象后,便会调用前文中提到的方式获取一个RPC调用的客户端,nova组件将创建RPC客户端的代码封装在如下方法中,nova-compute服务可以直接调用。该方法主要通过nova组件启动时创建的Transport对象和传入的Target对象作为参数创建一个RPCClient。
def get_client(target, version_cap=None, serializer=None):    assert TRANSPORT is not None    if profiler:        serializer = ProfilerRequestContextSerializer(serializer)    else:        serializer = RequestContextSerializer(serializer)    return messaging.RPCClient(TRANSPORT,                               target,                               version_cap=version_cap,                               serializer=serializer)
        之后,便可以通过call和cast方法实现发送消息的方法。
    def change_instance_metadata(self, ctxt, instance, diff):        version = '4.0'        cctxt = self.router.client(ctxt).prepare(                server=_compute_host(None, instance), version=version)        cctxt.cast(ctxt, 'change_instance_metadata',                   instance=instance, diff=diff)
        该方法通过cast调用发送消息实现改变该虚机元数据的操作,此时该方法不会阻塞等待返回结果。
    def check_instance_shared_storage(self, ctxt, instance, data, host=None):        version = '4.0'        cctxt = self.router.client(ctxt).prepare(                server=_compute_host(host, instance), version=version)        return cctxt.call(ctxt, 'check_instance_shared_storage',                          instance=instance,                          data=data)
        该方法通过call调用发送消息实现查询实例是否使用共享存储的操作,此时该方法会阻塞等待返回结果。在上面两个方法中,需要特别注意的是第二个参数的值change_instance_metadata和check_instance_shared_storage,这两个参数代表了服务端处理该消息的方法名称。在消息分发时,对应的Dispatcher会根据endpoint和这些方法名确定消息的处理操作。
        介绍完如何发送消息,接下来接收如何处理消息。前面已经介绍到,消息分发是通过Dispatcher根据endpoint和method来实现的,而Dispatcher则是创建RPC服务时进行关联的。由Nova源码可知,在创建和启动nova-compute的RPC服务的过程中,有一个非常重要的参数manager,该manager便是Dispatcher所需的endpoint的重要组成部分,其代表了一个处理消息的"包.类"名,如nova-compute中,该manager表示nova.compute.manager.ComputeManager类。
    def start(self):        """Start the service.        This includes starting an RPC service, initializing        periodic tasks, etc.        """        assert_eventlet_uses_monotonic_clock()        verstr = version.version_string_with_package()        LOG.info(_LI('Starting %(topic)s node (version %(version)s)'),                  {'topic': self.topic, 'version': verstr})        self.basic_config_check()        self.manager.init_host()        self.model_disconnected = False        ctxt = context.get_admin_context()        self.service_ref = objects.Service.get_by_host_and_binary(            ctxt, self.host, self.binary)        if self.service_ref:            _update_service_ref(self.service_ref)        else:            try:                self.service_ref = _create_service_ref(self, ctxt)            except (exception.ServiceTopicExists,                    exception.ServiceBinaryExists):                # NOTE(danms): If we race to create a record with a sibling                # worker, don't fail here.                self.service_ref = objects.Service.get_by_host_and_binary(                    ctxt, self.host, self.binary)        self.manager.pre_start_hook()        if self.backdoor_port is not None:            self.manager.backdoor_port = self.backdoor_port        LOG.debug("Creating RPC server for service %s", self.topic)        target = messaging.Target(topic=self.topic, server=self.host)        endpoints = [            self.manager,            baserpc.BaseRPCAPI(self.manager.service_name, self.backdoor_port)        ]        endpoints.extend(self.manager.additional_endpoints)        serializer = objects_base.NovaObjectSerializer()        self.rpcserver = rpc.get_server(target, endpoints, serializer)        self.rpcserver.start()        self.manager.post_start_hook()        LOG.debug("Join ServiceGroup membership for this service %s",                  self.topic)        # Add service to the ServiceGroup membership group.        self.servicegroup_api.join(self.host, self.topic, self)        if self.periodic_enable:            if self.periodic_fuzzy_delay:                initial_delay = random.randint(0, self.periodic_fuzzy_delay)            else:                initial_delay = None            self.tg.add_dynamic_timer(self.periodic_tasks,                                     initial_delay=initial_delay,                                     periodic_interval_max=                                        self.periodic_interval_max)
        上文中说到ComputeManager类定义了处理nova-compute消息的所有方法,下面就详细分析该类的实现。该类首先包含了一个Target对象,接着可以在该类中分别找到上文中提到的两个消息发送的方法名所对应的方法create_instance_metadata()和check_instance_shared_storage()。这样,Dispatcher在进行消息分发时便可以根据topic准确定位到这两个方法,实现消息的处理。
target = messaging.Target(version='4.18')
    @wrap_exception()    @wrap_instance_fault    def change_instance_metadata(self, context, diff, instance):        """Update the metadata published to the instance."""        LOG.debug("Changing instance metadata according to %r",                  diff, instance=instance)        self.driver.change_instance_metadata(context, instance, diff)
    @wrap_exception()    def check_instance_shared_storage(self, ctxt, instance, data):        """Check if the instance files are shared        :param ctxt: security context        :param instance: dict of instance data        :param data: result of driver.check_instance_shared_storage_local        Returns True if instance disks located on shared storage and        False otherwise.        """        return self.driver.check_instance_shared_storage_remote(ctxt, data)
        以上便是OpenStack其他项目使用oslo.messaging实现RPC调用的详细流程和代码示例。接下来,继续分析Notification通知消息的处理流程。

5.2 Notification的用法

        在本节开头,介绍了Nova各组件启动时调用的init()方法中,根据配置文件创建了不同的Notifier用于发送Notification通知。而在nova/notification目录下,Nova组件则定义各种不同类型的Notification对象和Payload数据格式。例如下面的代码则定义了监控服务状态的Notification通知和对应的Payload数据格式的封装。
@base.notification_sample('service-update.json')@nova_base.NovaObjectRegistry.register_notificationclass ServiceStatusNotification(base.NotificationBase):    # Version 1.0: Initial version    VERSION = '1.0'    fields = {        'payload': fields.ObjectField('ServiceStatusPayload')    }@nova_base.NovaObjectRegistry.register_notificationclass ServiceStatusPayload(base.NotificationPayloadBase):    SCHEMA = {        'host': ('service', 'host'),        'binary': ('service', 'binary'),        'topic': ('service', 'topic'),        'report_count': ('service', 'report_count'),        'disabled': ('service', 'disabled'),        'disabled_reason': ('service', 'disabled_reason'),        'availability_zone': ('service', 'availability_zone'),        'last_seen_up': ('service', 'last_seen_up'),        'forced_down': ('service', 'forced_down'),        'version': ('service', 'version'),        'uuid': ('service', 'uuid')    }    # Version 1.0: Initial version    # Version 1.1: Added uuid field.    VERSION = '1.1'    fields = {        'host': fields.StringField(nullable=True),        'binary': fields.StringField(nullable=True),        'topic': fields.StringField(nullable=True),        'report_count': fields.IntegerField(),        'disabled': fields.BooleanField(),        'disabled_reason': fields.StringField(nullable=True),        'availability_zone': fields.StringField(nullable=True),        'last_seen_up': fields.DateTimeField(nullable=True),        'forced_down': fields.BooleanField(),        'version': fields.IntegerField(),        'uuid': fields.UUIDField()    }    def __init__(self, service):        super(ServiceStatusPayload, self).__init__()        self.populate_schema(service=service)
        在实际使用中,则可以使用一下方式实现发送Notification通知消息到对应的订阅者。首先,根据具体通知类型,创建Payload对象和Notification对象;然后调用Notification对象的emit()发送notification通知即可。
@rpc.if_notifications_enableddef notify_about_instance_action(context, instance, host, action, phase=None,                                 source=fields.NotificationSource.COMPUTE,                                 exception=None, bdms=None):    """Send versioned notification about the action made on the instance    :param instance: the instance which the action performed on    :param host: the host emitting the notification    :param action: the name of the action    :param phase: the phase of the action    :param source: the source of the notification    :param exception: the thrown exception (used in error notifications)    :param bdms: BlockDeviceMappingList object for the instance. If it is not                provided then we will load it from the db if so configured    """    fault, priority = _get_fault_and_priority_from_exc(exception)    payload = instance_notification.InstanceActionPayload(            instance=instance,            fault=fault,            bdms=bdms)    notification = instance_notification.InstanceActionNotification(            context=context,            priority=priority,            publisher=notification_base.NotificationPublisher(                host=host, source=source),            event_type=notification_base.EventType(                    object='instance',                    action=action,                    phase=phase),            payload=payload)    notification.emit(context)
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 优购m9锁屏密码忘记了怎么办视频 中百仓储的购物卡过期了怎么办 武汉中百超市购物卡过期怎么办 已认证抵扣的发票发生退货怎么办 京东白条分期买手机额度不够怎么办 京东白条闪付手机不支持开通怎么办 拼多多不小心下两次单付款了怎么办 不小心给了京东快递差评怎么办 京东金融不小心卸载了怎么办 如果京东快递不小心完成订单怎么办 订机票时没有用常旅客卡怎么办 京东退货退款卖家已签收了怎么办 京东账号手机号换了密码忘了怎么办 qq飞车手游录像下载失败怎么办 手机知道qq号怎么破解密码怎么办 网上购物已付钱迟迟不发货怎么办 有人冒充微信头像和熟人借钱怎么办 微信红包输了50多万怎么办 脖子里的绳结接口磨脖子怎么办 母比格犬生了小狗毛色变了怎么办 母比格犬生了小狗毛色很差怎么办 貂皮大衣上的暗扣掉了怎么办 呢子夹克袖子长但又不想改短怎么办 黑色的衣服熨的有点发亮怎么办 宿管阿姨工作中与学生起冲突怎么办 中通快递发的衣服不合适怎么办 加盟母婴店如果不干了货怎么办 双十一搞活动买的东西退货怎么办 天猫预售30天不发货怎么办 天猫搞活动的商品总不发货怎么办 淘宝买的东西物流把货物损坏怎么办 苹果手机5s每部存满怎么办 天猫退货分开发货退的话怎么办 淘宝优惠券电脑端显示不出来怎么办 天猫退货退款寄错了怎么办 天猫已退款货又发过来了怎么办 小米商城预售订单点错退款了怎么办 淘宝发布宝贝类目价格受限制怎么办 2019天猫续签评分不达标怎么办 天猫店铺动态不达标不能续签怎么办 京东自提发现货有问题怎么办