同時發佈於: http://leiqzhang.com/2014/01/2014-01-09-nova-scheduler-service-initialization/
NOVA-SCHEDULER服務啓動流程
前提
- 對Nova的整體結構已經有所理解
- 基於stable/havana分支
- 基於Redhat的RDO庫進行的環境安裝,基於CentOS 6.4
- 主機名爲controller
內容
- openstack-nova-scheduler服務啓動流程
- 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的存活狀態,以支持自身的任務調度。
參考資料
- Nova RPC: cast && call
- Rabbitmqctl Manual
- Consumers and Queues created during Scheduler service init
- ServiceGroup