NOVA-SCHEDULER服務啓動流程

同時發佈於: http://leiqzhang.com/2014/01/2014-01-09-nova-scheduler-service-initialization/


NOVA-SCHEDULER服務啓動流程

前提

  1. 對Nova的整體結構已經有所理解
  2. 基於stable/havana分支
  3. 基於Redhat的RDO庫進行的環境安裝,基於CentOS 6.4
  4. 主機名爲controller

內容

  1. openstack-nova-scheduler服務啓動流程
  2. MessageQueue的相關知識及在scheduler服務啓動過程中的涉及的行爲

執行結果

根據之前對Noah系統結構的理解,在scheduler啓動過程中,會和MessageQueue交互,創建相應的Exchange和Consumer。

啓動前的MessageQueue的狀態:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#rabbitmqctl list_exchanges name type durable internal
Listing exchanges ...
	direct	true	false
amq.direct	direct	true	false
amq.fanout	fanout	true	false
amq.headers	headers	true	false
amq.match	headers	true	false
amq.rabbitmq.log	topic	true	false
amq.rabbitmq.trace	topic	true	false
amq.topic	topic	true	false
...done.

#rabbitmqctl list_bindings source_name source_kind destination_name destination_kind routing_key
Listing bindings ...
...done.

#rabbitmqctl list_connections pid name  port host peer_port peer_host state channels protocol
Listing connections ...
...done.

#rabbitmqctl list_channels pid connection name number consumer_count
Listing channels ...
...done.

#rabbitmqctl list_consumers
Listing consumers ...
...done.

由上可知,在Scheduler啓動前,只有RabbitMQ-Server默認創建的一些exchange,而binding、connection、channel和consumer均爲空。

現在啓動Scheduler服務:

1
service openstack-nova-scheduler start

啓動成功後,再次查看MQ的狀態如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#rabbitmqctl list_exchanges name type durable internal
Listing exchanges ...
	direct	true	false
amq.direct	direct	true	false
amq.fanout	fanout	true	false
amq.headers	headers	true	false
amq.match	headers	true	false
amq.rabbitmq.log	topic	true	false
amq.rabbitmq.trace	topic	true	false
amq.topic	topic	true	false
nova	topic	false	false
scheduler_fanout	fanout	false	false
...done.

#rabbitmqctl list_bindings source_name source_kind destination_name destination_kind routing_key
Listing bindings ...
	exchange	scheduler	queue	scheduler
	exchange	scheduler.controller	queue	scheduler.controller
	exchange	scheduler_fanout_132fbd38ac304ffb9adb93c09656e769	queue	scheduler_fanout_132fbd38ac304ffb9adb93c09656e769
nova	exchange	scheduler	queue	scheduler
nova	exchange	scheduler.controller	queue	scheduler.controller
scheduler_fanout	exchange	scheduler_fanout_132fbd38ac304ffb9adb93c09656e769	queue	scheduler
...done.

#rabbitmqctl list_connections pid name  port host peer_port peer_host state channels protocol
Listing connections ...
<[email protected]>	192.168.251.11:56077 -> 192.168.251.11:5672	5672	192.168.251.11	56077	192.168.251.11	running	{0,8,0}
...done.

#rabbitmqctl list_channels pid connection name number consumer_count
Listing channels ...
<[email protected]>	<[email protected]>	192.168.251.11:56077 -> 192.168.251.11:5672 (1)	1	3
...done.

#rabbitmqctl list_consumers
Listing consumers ...
scheduler	<[email protected]>	1	true
scheduler.controller	<[email protected]>	2	true
scheduler_fanout_132fbd38ac304ffb9adb93c09656e769	<[email protected]>	3	true
...done.

   	#rabbitmqctl list_queues name pid owner_pid consumers status
Listing queues ...
scheduler	<[email protected]>		1	running
scheduler.controller	<[email protected]>		1	running
scheduler_fanout_132fbd38ac304ffb9adb93c09656e769	<[email protected]>		1	running
...done.

由上可知,在scheduler啓動過程中,對於MQ的操作包括:

  • 創建了名稱爲nova,類型爲topic的exchange
  • 創建了名稱爲scheduler_fanout,類型爲fanout的exchange
  • 創建了名稱爲scheduler、scheduler.controller和scheduler_fanout_xx的三個queue
  • 創建了一個具有一個channel的connection
  • 創建了名稱爲scheduler、schueler.controller和scheduler_fanout_xx的三個consumer
  • 創建了6個bindings,分別將上面的三個新建的queue和上述的三個新建的exchange和RabbitMQ默認的名稱爲空的exchange進行了binding

故可以得知,Scheduler啓動後,相應的MQ結構如下:

1
2
3
4
5
Exchange(nova,topic) <---<routing_key:scheduler>---Queue(scheduler)--->Consumer(scheduler)

Exchange(nova,topic) <---<routing_key:scheduler.controller>---Queue(scheduler.controller)--->Consumer(scheduler.controller)

Exchange(scheduler_fanout,fanout) <---<routing_key:scheduler>---Queue(scheduler_fanout_xxx)--->Consumer(scheduler_fanout_xxx)

對比RabbitMQ和OpenStack的概念,其中名稱爲nova的Exchange爲Topic Exchange,支持時類似於MSG的Unicast。名稱爲scheduler_fanout_xx的Exchange爲Fanout Exchange,支持的是類似於MSG的Broadcast。

名稱分別爲scheduler和scheduler.controller的Consumer均爲Topic Consumer,用於從連接的Queue中接收相應topic的msg,這裏的topic就是指RabbitMQ中的Queue的”Routing Key”。

故Scheduler服務啓動時,就會創建一個Topic Exchange,並會初始化兩個Queue連接此Exchange和兩個Topic Consumer,兩個Consumer分別接受的topic分別是“scheduler”和”scheduler.{hostname}”。

結合OpenStack的文檔,當進行rpc.cast調用時,實際是使用scheduler這個Queue發送消息,當進行rpc.call調用時,實際是使用scheduler.{hostname}這個Queue發送消息。 (注: 使用哪個隊列跟使用rpc.cast和rpc.call無關係,只跟調用這倆rpc時傳入的topic有關係。 這倆rpc調用的區別僅僅在於是否需要後續的雙向通信)

當前尚不知曉在服務初始化過程中創建的fanout Exchange、Queue和Consumer的具體作用,從如下參考資料中瞭解到其主要作用是:

  • 發送原子性和非關鍵性的信息到所有的workers
  • 計算節點定期發送容量等信息到scheduler

這一點待後續進一步確定。

以上初始化過程中未涉及到Publisher的創建,結合OpenStack的文檔,Publisher是在真正發送消息時創建的。實際上在發送消息和處理消息過程中會涉及到創建Direct Exchange、Topic Publisher、Direct Publisher和Direct Consumer等MQ相關組件,這些的詳細過程會在後續分析。

代碼路徑

主要路徑

服務啓動腳本爲/etc/init.d/openstack-nova-scheduler,查看此腳本發現在啓動服務時實際執行的是/usr/bin/nova-scheduler,而/usr/bin/nova-scheduler是一個python腳本,實際執行的是nova.cmd.scheduler.main

nova.cmd.scheduler.main的主要代碼如下:

1
2
3
4
    server = service.Service.create(binary='nova-scheduler',
                                    topic=CONF.scheduler_topic)
    service.serve(server)
    service.wait()

第一行中的CONF.scheduler_topic,是從配置文件/etc/nova/nova.conf中讀取的,默認是”scheduler”。

第二行的serve方法如下:

1
2
3
4
5
global _launcher
    if _launcher:
        raise RuntimeError(_('serve() can only be called once'))

    _launcher = service.launch(server, workers=workers)

在最後一行調用的launch方法中,會在workers爲None時,調用ServiceLauncher.launch_service來運行service,否則會調用ProcessLauncher.launch_service來運行service:

1
2
3
4
5
6
7
8
def launch(service, workers=None):
    if workers:
        launcher = ProcessLauncher()
        launcher.launch_service(service, workers=workers)
    else:
        launcher = ServiceLauncher()
        launcher.launch_service(service)
    return launcher

其中ProcessLauncher是fork出workers數量的進程,ServiceLauncher是生成一個線程(greenthread),此線程的入口是先後調用相應service的start和wait方法,進而完成服務的初始化。

在Scheduler中,使用的是ServiceLauncher。

Service.create

Service.create的主要邏輯是獲取配置的”scheduler_manager”對應的類作爲manager,加上配置中的report_internal、periodic_enable、periodic_fuzzy_delay等參數,初始化一個Service Object。

在Service的init方法中,主要是根據上述參數進行了屬性初始化。

Service.start

這裏主要關注start方法中關於MessageQueue和ServiceGroup的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
self.conn = rpc.create_connection(new=True)
        LOG.debug(_("Creating Consumer connection for Service %s") %
                  self.topic)

        rpc_dispatcher = self.manager.create_rpc_dispatcher(self.backdoor_port)

        # Share this same connection for these Consumers
        self.conn.create_consumer(self.topic, rpc_dispatcher, fanout=False)

        node_topic = '%s.%s' % (self.topic, self.host)
        self.conn.create_consumer(node_topic, rpc_dispatcher, fanout=False)

        self.conn.create_consumer(self.topic, rpc_dispatcher, fanout=True)

        # Consume from all consumers in a thread
        self.conn.consume_in_thread()

        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)

由上可知,第一行首先創建了一個新的Connection,Connection是nova.openstack.common.amqp.ConnectionContext對象,針對create_connection方法,當前由多個driver進行了實現,其中RabbitMQ對應的是nova.rpc.impl_kombu,也即配置文件中配置的rpc_backend。

接着由manager創建了一個rpc_dispatcher,此對象負責在接收到消息後進行處理,細節在後續分析。

接着分別調用create_consumer了三次,在此方法中會創建出上節提到的各個Exchange、Queue、Bindings和Consumers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 前兩個create_consumer調用, 最終會調用TopicConsumer.__init__:

# Default options
    options = {'durable': conf.amqp_durable_queues,
               'queue_arguments': _get_queue_arguments(conf),
               'auto_delete': conf.amqp_auto_delete,
               'exclusive': False}
    options.update(kwargs)
#這一行決定了exchange_name, 默認會從配置文件中讀取control_exchange配置(值爲#openstack),但是在nova.config中通過		
#rpc.set_defaults(control_exchange='nova')將其設置爲了"nova"
    exchange_name = exchange_name or rpc_amqp.get_control_exchange(conf)     
#exchange的type是topic
    exchange = kombu.entity.Exchange(name=exchange_name,
                                     type='topic',                                         durable=options['durable'],
                                     auto_delete=options['auto_delete'])
    #此調用會創建出Consumer、Binding和Queue
    super(TopicConsumer, self).__init__(channel,
                                        callback,
                                        tag,
                                        name=name or topic,
                                        exchange=exchange,
                                        routing_key=topic,
                                        **options)

# 第三個create_consumer調用,最終會調用FanoutConsumer.__init__:

unique = uuid.uuid4().hex
# 這也就是上一節看到的scheduler_fanout_xx
    exchange_name = '%s_fanout' % topic
    queue_name = '%s_fanout_%s' % (topic, unique)

    # Default options
    options = {'durable': False,
              'queue_arguments': _get_queue_arguments(conf),                 'auto_delete': True,
               'exclusive': False}
    options.update(kwargs)
    exchange = kombu.entity.Exchange(name=exchange_name, type='fanout',
                                     durable=options['durable'],
                                     auto_delete=options['auto_delete'])
    super(FanoutConsumer, self).__init__(channel, callback, tag,
                                         name=queue_name,
                                         exchange=exchange,
                                         routing_key=topic,
                                         **options)

接着調用的consume_in_thread最終會調用到queue的consume方法,從隊列中取出消息並進行處理。

最後調用的join方法來加入到相應的ServiceGroup。關於ServiceGroup,主要用途是爲了管理Group中各個節點的liveness狀態的,具體實現目前默認是通過db,也可以通過ZooKeeper實現。對於Scheduler來說,需要通過ServiceGroup感知到所有Compute Node的存活狀態,以支持自身的任務調度。

參考資料


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章