openstack nova 基础知识——scheduler的filter和weight

本文解析了FilterScheduler的工作原理,包括如何筛选合适的主机以及如何通过评分机制选择最合适的主机。

http://www.verydemo.com/demo_c288_i57027.html

一开始在没有看源码的时候,看了下官方文档的Filter Scheduler,知道过滤(filter)是怎么回事,但是那个weight是什么意思始终没看明白,现在看下源码发现也挺简单的。

Scheduler做的工作就是在创建实例(instance)时,为实例找到合适的主机(host),这个过程分为两步:首先是过滤(filter),从所有的主机中找到符合实例运行条件的主机,然后从过滤出来的主机中,找到最合适的一个主机。这个“最合适”其实是由“你”说了算的,也就是可以通过配置文件,来让它按你的要求来filter和weight。如下图:


这部分功能的实现主要是在FilterScheduler类中的_schedule()方法中:

  1. def _schedule(self, context, topic, request_spec, filter_properties, *args,  
  2.         **kwargs):  
  3.     """Returns a list of hosts that meet the required specs, 
  4.     ordered by their fitness. 
  5.     """  
  6.     elevated = context.elevated()  
  7.     if topic != "compute":  
  8.         msg = _("Scheduler only understands Compute nodes (for now)")  
  9.         raise NotImplementedError(msg)  
  10.   
  11.     instance_properties = request_spec['instance_properties']  
  12.     instance_type = request_spec.get("instance_type"None)  
  13.   
  14.     cost_functions = self.get_cost_functions()  
  15.     config_options = self._get_configuration_options()  
  16.   
  17.     # check retry policy:  
  18.     self._populate_retry(filter_properties, instance_properties)  
  19.   
  20.     filter_properties.update({'context': context,  
  21.                               'request_spec': request_spec,  
  22.                               'config_options': config_options,  
  23.                               'instance_type': instance_type})  
  24.   
  25.     self.populate_filter_properties(request_spec,  
  26.                                     filter_properties)  
  27.   
  28.     # Find our local list of acceptable hosts by repeatedly  
  29.     # filtering and weighing our options. Each time we choose a  
  30.     # host, we virtually consume resources on it so subsequent  
  31.     # selections can adjust accordingly.  
  32.   
  33.     # unfiltered_hosts_dict is {host : ZoneManager.HostInfo()}  
  34.     unfiltered_hosts_dict = self.host_manager.get_all_host_states(  
  35.             elevated, topic)  
  36.   
  37.     # Note: remember, we are using an iterator here. So only  
  38.     # traverse this list once. This can bite you if the hosts  
  39.     # are being scanned in a filter or weighing function.  
  40.     hosts = unfiltered_hosts_dict.itervalues()#HostState对象  
  41.   
  42.     num_instances = request_spec.get('num_instances'1)  
  43.     selected_hosts = []  
  44.     for num in xrange(num_instances):  
  45.         # Filter local hosts based on requirements ...  
  46.         # 得到当前可用的符合条件的host,即每一个host要能穿过所有的过滤器,才会认为是可用的  
  47.         hosts = self.host_manager.filter_hosts(hosts,  
  48.                 filter_properties)  
  49.         if not hosts:  
  50.             # Can't get any more locally.  
  51.             break  
  52.   
  53.         LOG.debug(_("Filtered %(hosts)s") % locals())  
  54.   
  55.         # weighted_host = WeightedHost() ... the best  
  56.         # host for the job.  
  57.         # TODO(comstud): filter_properties will also be used for  
  58.         # weighing and I plan fold weighing into the host manager  
  59.         # in a future patch.  I'll address the naming of this  
  60.         # variable at that time.  
  61.         weighted_host = least_cost.weighted_sum(cost_functions,  
  62.                 hosts, filter_properties)  
  63.         LOG.debug(_("Weighted %(weighted_host)s") % locals())  
  64.         selected_hosts.append(weighted_host)  
  65.   
  66.         # Now consume the resources so the filter/weights  
  67.         # will change for the next instance.  
  68.         weighted_host.host_state.consume_from_instance(  
  69.                 instance_properties)  
  70.   
  71.     selected_hosts.sort(key=operator.attrgetter('weight'))  
  72.     return selected_hosts[:num_instances]  

类之间的相互调用关系如下类图:


_schedule()中主要涉及到两个方法:HostManager中的filter_hosts()和least_cost模块中的weighted_sum(),两者分别实现了filter和weight。

先来看一下filter_hosts():

  1. # 对hosts中的每一个host进行过滤,返回通过过滤后的host  
  2. def filter_hosts(self, hosts, filter_properties, filters=None):  
  3.     """Filter hosts and return only ones passing all filters"""  
  4.     filtered_hosts = []  
  5.     filter_fns = self._choose_host_filters(filters)#得到过滤器类中的过滤器方法host_passes()  
  6.     for host in hosts:  
  7.         if host.passes_filters(filter_fns, filter_properties):  
  8.             filtered_hosts.append(host)  
  9.     return filtered_hosts  
其中调用了_choose_host_filters(),这个方法的作用是从配置文件读取配置的过滤类中的host_passes()方法,过滤类就是上面类图中的BaseHostFilter的派生类,派生类有很多,也可以自己定义,然后配置到nova.conf中,这些过滤类中的host_passes()就是具体的去选择符合条件的主机。

随后在对每一个主机循环的过程中,调用了每一个主机的passes_filters()方法,用过滤类中的过滤方法,根据所给的过滤条件(filter_properties)来判断这个主机是不是符合条件:

  1. def passes_filters(self, filter_fns, filter_properties):  
  2.     """Return whether or not this host passes filters."""  
  3.       
  4.     #忽略的host  
  5.     if self.host in filter_properties.get('ignore_hosts', []):  
  6.         LOG.debug(_('Host filter fails for ignored host %(host)s'),  
  7.                   {'host'self.host})  
  8.         return False  
  9.       
  10.     #强制使用哪个host  
  11.     force_hosts = filter_properties.get('force_hosts', [])  
  12.     if force_hosts:  
  13.         if not self.host in force_hosts:  
  14.             LOG.debug(_('Host filter fails for non-forced host %(host)s'),  
  15.                       {'host'self.host})  
  16.         return self.host in force_hosts  
  17.       
  18.     #调用各个Filter类中的host_passes()方法,对filter_properties进行验证  
  19.     #只有所有的过滤器都通过,才返回真,否则有一个不通过就返回假  
  20.     for filter_fn in filter_fns:  
  21.         if not filter_fn(self, filter_properties):  
  22.             LOG.debug(_('Host filter function %(func)s failed for '  
  23.                         '%(host)s'),  
  24.                       {'func': repr(filter_fn),  
  25.                        'host'self.host})  
  26.             return False  
  27.   
  28.     LOG.debug(_('Host filter passes for %(host)s'), {'host'self.host})  
  29.     return True  

选择出来符合条件的主机放到filtered_hosts列表中返回,这第一步工作就完成了。


然后是第二步weight,这个weight该怎么理解呢?从它做的工作来看,就是从符合条件的主机中选择“最合适”的主机,这个选择的过程是通过“评分”来实现的,我们判断一个人的水平高低,往往是看它的综合成绩,把各科成绩加起来,根据总分来判断,或者是求平均成绩。在这里也是同样的道理,判断哪一个主机“最合适”,也是看多方面的,从主机的剩余内存,剩余磁盘空间,vcpu的使用情况来综合考虑,但是有一个问题就是这些数据都不是一类的,单位不同,不能直接进行相加,但是他们有一个共同点就是他们的值都是线性变化的,所以可以给他们都乘上一个weight,让他们达到同一个数量级别,就可以进行相加了,得出来的“总分”也就能说明这个主机整体的情况了。来看一下weighted_sum()的代码:

  1. def weighted_sum(weighted_fns, host_states, weighing_properties):  
  2.       
  3.     # weight=-1.0  
  4.     # 取最小值,即找剩余内存最大的host  
  5.     min_score, best_host = NoneNone  
  6.     for host_state in host_states:  
  7.         score = sum(weight * fn(host_state, weighing_properties)  
  8.                     for weight, fn in weighted_fns)  
  9.         if min_score is None or score < min_score:  
  10.             min_score, best_host = score, host_state  
  11.   
  12.     return WeightedHost(min_score, host_state=best_host)  
传递进来的第一个参数weight_fns就是从配置文件得到的要对主机的哪写方面进行“评分”,可以看到这是一个简单的求最小值的算法,问题来了,为什么要用“得分”最少的主机呢?因为weight可以为负值,负数越小,它的绝对值就大。那用正值会怎么样呢?这从官网文档中可以得到答案,使用正值,就可以优先使用一个计算节点,即先把实例都运行在这个计算节点上,等到这个节点的资源使用的完了,再用下一个计算节点。使用负数,就是总是找最优条件的主机。这个weight也是可以通过配置文件来配置的,看到了配置文件的好处了,灵活!这个函数最后返回一个WeightedHost对象,这个没什么,就是对数据进行了一下封装。

现在再来看官网上的那张图片就应该容易理解了吧:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值