首页 > 代码库 > OpenStack源码分析——Nova-Scheduler

OpenStack源码分析——Nova-Scheduler

<style></style>

一、服务启动

Nova-scheduler服务的启动入口脚本是cmd包下的scheduler.py,其主要监听来自于消息队列中topic=scheduler(可配置)的消息。在服务启动过程中,其将初始化一个SchedulerManager实例作为该服务的Handler,来处理接受到的消息请求。

 

同时,Nova-scheduler服务在启动的过程中,会将自己注册到DB中,即将自己的hostbinarytopicreport_count信息添加到services表中,并将自己同样注册到ServiceGroup服务中,默认是DBServiceGroup服务,并定时向ServiceGroup服务发送心跳,其本质上就是定时地更新services表中的report_count字段。另,ServiceGroup服务还有基于MCZK两种实现方式。

 

二、主要源码说明

1manager.py

SchedulerManager类主要来处理服务接收的消息,目前主要的操作就是live_migration

run_instanceprep_resizeselect_hostsselect_destinations,后两者将要deprecated。这些操作本质上还是要依赖于具体的SchedulerDriver去完成。

另外,_SchedulerManagerV3Proxy类与SchedulerManager类主要的区别在于消息版本的不同,

_SchedulerManagerV3Proxy消息是3.0版本,而SchedulerManager2.9版本。


2driver.pychance.pyfilter_scheduler.pycaching_scheduler.py

Scheduler类是所有SchedulerDriver类的基类,其定义了关键的接口协议。目前主要有ChanceSchedulerFilterSchedulerCachingScheduler三种实现方式,具体说明如下:

1ChanceScheduler:随机选择一台物理机,前提是该物理机上的nova-compute服务正常且该

物理机不在指定的ignore_hosts列表中。

2FilterScheduler:筛选出能通过整个过滤器链的物理机,然后根据相应指标计算权重,并进

行排序,最后返回一个besthost。具体过滤器详见下文。

3CachingSchedulerFilterScheduler的子类,在FilterScheduler的基础上将host资源信息缓

存在了本地内存中,然后通过后台定时任务定时从DB中拉取最新的host资源信息。在多节

点环境下存在问题。


3host_manager.py

HostState:表示物理机的资源信息集合,比如当前可用内存、已用内存、虚拟机数量、任务

数量、io负载、可用磁盘、已用磁盘等等,以及一些更新方法。

HostManager:该类是最重要的host筛选类,其内部主要有三个关键函数:

get_filtered_hosts:返回经过层层过滤器筛选后的host列表,其依赖于FilterHandler

get_weighed_hosts:返回经过权重计算排序后的host列表,其依赖于WeightHandler

get_all_host_states:获取当前最新的host资源列表。

 

4filters

这里是全部的各种filter的实现方式,主要有:

1__init__.py

BaseHostFilter:继承自BaseFilter,是所有具体Filter子类的基类,其中一个较重要的成

员变量run_filter_once_per_request表示该Filter子类是否在在一次request中仅执行一次。

 

HostFilterHandler:最重要的函数get_filtered_objectsload指定的filter_classes,层层过滤

host,最后返回符合所有过滤条件的host列表。

 

2)剩余就是很多个不同的具体Filter子类的实现,基于各自不同的策略,具体暂不一一介

绍。


5weights

1__init__.py

BaseHostWeigher:继承自BaseWeigher,是所有具体Weigher子类的基类,其中主要的函

数就是_weigh_object,由具体子类实现,完成对目标host列表权重的计算与排序,具体策

略见下文。


HostWeightHandler:最重要的函数get_weighed_objectsload指定的weigher_classes,层层

依据各自指标对目标host列表进行权重计算,最后排序后返回。

 

2ram.py

RAMWeigher:基于剩余可用内存进行权重计算排序。


3metrics.py

MetricsWeigher:支持自定义一些指标进行权重计算排序。


三、流程与算法说明

1、这里主要是基于FilterScheduler来说明下在Scheduler服务中创建虚拟机(包括筛选物理机)的流程(其他流程同理):


1)当nova-scheduler服务从MQ中接收到run_instance消息时,则由SchedulerManager类对其

进行处理,执行run_instance函数。


2SchedulerManager将具体逻辑交由具体的SchedulerDriver执行,这里就是FilterScheduler


3FilterScheduler接收到请求后,开始进行host筛选,选择出instance_num个目标host


4)首先判断这次requestretry次数是否已经达到max_attempts次,若达到,则停止retry,抛

NoValidHost异常;若是第一次,则设置filter_properties属性retry={num_attempts:1,hosts:

[]};否则继续执行。


5)然后从DB中获取当前最新的computenode节点资源信息,并更新本地内存缓存中的host

表资源数据。这里的好处是当一次批量申请多个虚拟机时,可以避免多次请求DB,但是

一个不容忽视的问题就是如果多个Scheduler服务并行部署时,就会因资源信息的延迟不同

步而导致虚拟机创建失败(实际后台host已不足)。


6)再次开始循环为每个instance筛选目标host:首先根据指定的ignore_hostsforce_hosts

force_nodes属性对候选hosts列表进行筛选,留下符合这些属性条件的hosts

 

7)然后根据配置指定的filter来循环层层过滤6)中的候选hosts,具体代码如下:

        def get_filtered_objects(self, filter_classes, objs,filter_properties, index=0):              list_objs = list(objs)              LOG.debug(_("Starting with %d host(s)"), len(list_objs))              for filter_cls in filter_classes:                    cls_name = filter_cls.__name__                    filter = filter_cls()                     if filter.run_filter_for_index(index):                          objs = filter.filter_all(list_objs,filter_properties)                          if objs is None:                                 LOG.debug(_("Filter %(cls_name)s says to stop filtering"),                                           {‘cls_name‘: cls_name})                           return                      list_objs = list(objs)                      if not list_objs:                           LOG.info(_("Filter %s returned 0 hosts"), cls_name)                           break                      LOG.debug(_("Filter %(cls_name)s returned  %(obj_len)d host(s)"),                               {‘cls_name‘: cls_name, ‘obj_len‘: len(list_objs)})              return list_objs

 


8)在7)中筛选的这批hosts此时可以认为是满足该instance的资源要求,然后开始对这批

hosts根据配置的各个Weigher子类进行权重计算并排序,具体如下:

        def get_weighed_objects(self, weigher_classes, obj_list,weighing_properties):                """Return a sorted (descending), normalized list of WeighedObjects."""               if not obj_list:                    return []               weighed_objs = [self.object_class(obj, 0.0) for obj in obj_list]               for weigher_cls in weigher_classes:                     weigher = weigher_cls()                     weights = weigher.weigh_objects(weighed_objs, weighing_properties)                     # Normalize the weights                     weights = normalize(weights,minval=weigher.minval,maxval=weigher.maxval)                      for i, weight in enumerate(weights):                           obj = weighed_objs[i]                           obj.weight += weigher.weight_multiplier() * weight           return sorted(weighed_objs, key=lambda x: x.weight, reverse=True)

 

9)从8)中根据权重排序后的hosts取出scheduler_host_subset_size(默认是1)个host,表明

未来该instance会落在该host上,并预扣掉该host的剩余可用资源(注意这里仅仅是修改

的本地缓存中host资源信息)。


10)在为每个instance选择好候选目标host后,开始循环创建虚拟机:更新instance

host,node,scheduled_at),并向nova-compute服务发送rpc请求。


11nova-compute服务接收到创建虚拟机请求后:首先将DBinstance状态设置为

vm_state=BUILDINGtask_state=SCHEDULING


12)然后获取该nodeResourceTracker实例(其主要是维护跟踪该node的资源明细),

claim将要预留资源信息,设置macnetwork信息,最后开始构建虚拟机。期间在不同

task阶段,会相应更新DBvm_statetask_state状态。


13)若创建虚拟机流程中发生错误,会重新向MQ发送创建虚拟机请求,此时nova-scheduler

服务会继续处理该消息,则继续回到1)步骤。


2、下图展示了整个调度过程的大体流程:














这里主要说明下各个host的权重是如何计算出来的,大体公式如下:

host_weight = Weigher1_multiplier * Weigher1_host_weight+……+ WeigherN_multiplier

* WeigherN_host_weight


举例说明:

假若有6台候选HostH1H2H3H4H5H6

3WeigherW1W2W3,且其multiplier分别为121

 

具体一个Weigher如何计算出一个Host在该Weigher的权重,则依赖各个Weigher自己的

实现,比如RAMWeigher则是以每个Hostfree_ram_mb作为其原生权重。


1)假若H1~H6经过W1计算后的原生权重值如下:

 

W(multiplier)

H1

H2

H3

H4

H5

H6

W1 (1)

50

10

20

10

90

110

W2 (2)







W3 (1)







 

然后需要对这些原始权重值进行normalize化,具体方式是:找出这些原始权重值中的

最大值max_weight和最小值min_weight,然后按照如下公式来计算其比例权重值:

ratio_weight = ( raw_weight - min_weight ) / ( max_weight -min_weight )

 

那么H1~H6原生权重W1normalize化之后(ratio_weight)


W(multiplier)

H1

H2

H3

H4

H5

H6

W1 (1)

0.4

0

0.1

0

0.8

1

W2 (2)







W3 (1)







 

2)假若H1~H6经过W2计算后的原生权重值如下:

 

W(multiplier)

H1

H2

H3

H4

H5

H6

W1 (1)

0.4

0

0.1

0

0.8

1

W2 (2)

10

4

6

11

1

9

W3 (1)







 

那么H1~H6原生权重W2normalize化之后(ratio_weight)

 

W(multiplier)

H1

H2

H3

H4

H5

H6

W1 (1)

0.4

0

0.1

0

0.8

1

W2 (2)

0.9

0.3

0.5

1

0

0.8

W3 (1)








3)假若H1~H6经过W3计算后的原生权重值如下:


W(multiplier)

H1

H2

H3

H4

H5

H6

W1 (1)

0.4

0

0.1

0

0.8

1

W2 (2)

0.9

0.3

0.5

1

0

0.8

W3 (1)

15

25

10

5

10

5


那么H1~H6原生权重W3normalize化之后(ratio_weight)


W(multiplier)

H1

H2

H3

H4

H5

H6

W1 (1)

0.4

0

0.1

0

0.8

1

W2 (2)

0.9

0.3

0.5

1

0

0.8

W3 (1)

0.5

1

0.25

0

0.25

0


4)最后得出H1~H6的最终权重为:


W(multiplier)

H1

H2

H3

H4

H5

H6

W1 (1)

0.4

0

0.1

0

0.8

1

W2 (2)

0.9

0.3

0.5

1

0

0.8

W3 (1)

0.5

1

0.25

0

0.25

0

ratio_weights

2.7

1.6

1.35

2

1.05

2.6


5)最后对H1~H6ratio_weight进行降序排序,那么这次Host的排序则为H1H6H4H2H3H5


3nova-schedulernova-compute之间是如何知道具体host资源的详情的?

1)每当nova-compute服务起来之后,会自动更新DB中对应compute_node的资源信息,若是

第一次,则会将自己添加到compute_node中;并同时会将这些资源信息缓存在本地内存中,

并由一个ResourceTracker实例进行跟踪。


2)每次nova-compute服务接收到创建虚拟机的申请时,通过ResourceTracker进行

instance_claim时,会将这次申请所需要扣除的虚拟机资源大小及时反映到computenode中,

同时更新本地ResourceTracker中的缓存信息。


3nova-scheduler每次在接收到创建虚拟机请求时,都会从computenode中取出最新的host

源信息,被暂时缓存在本地内存中,当成功选择出一台host时,会及时扣除本地缓存相应

host资源信息。