深入研究Openstack Nova组件对象模型以及数据库访问机制

深入研究Openstack Nova组件对象模型以及数据库访问机制

作者:付广平 2017-06-09 14:30:31

开发

开发工具

OpenStack 本文首先介绍了Openstack Nova组件数据库访问的发展历程,然后基于源码分析了当前Nova访问数据库的过程,最后解释了Nova使用软删除的原因。

[[193547]]

1. 背景

介绍在Openstack G版以前,Nova的所有服务(包括nova-compute服务)都是直接访问数据库的,数据库访问接口在nova/db/api.py模块中实现,而该模块只是调用了IMPL的方法,即该模块只是一个代理,真正实现由IMPL实现,IMPL是一个可配置的动态加载驱动模块,通常使用Python sqlalchemy库实现,对应的代码为nova.db.sqlalchemy.api:

  1. _BACKEND_MAPPING = {'sqlalchemy''nova.db.sqlalchemy.api'

该模块不仅实现了model的CRUD操作,还封装了一些高级API,比如:

  • instance_get_all: 获取所有虚拟机实例。
  • instance_update: 更新虚拟机熟悉。

这种直接访问数据库的设计至少存在以下两个问题:

  • 所有服务与数据模型耦合,当数据模型变更时,可能需要涉及所有代码的调整,并难以支持版本控制。
  • 所有的主机都能访问数据库,大大增加了数据库的暴露风险。

为了实现Nova服务与数据库访问解耦,从G版本开始引入了nova-conductor服务,该服务的一个重要作用就是访问数据库,其它服务访问数据库时需要向nova-conductor发起RPC请求,由nova-conductor代理请求数据库。

以上方式基本解决了服务与数据库访问解耦,并且防止其它服务直接访问数据库,但仍然没有解决对象模型的版本控制。从I版本开始引入了对象模型的概念,所有的对象模型定义在nova/objects。在此之前访问数据库是直接调用数据库的model的,比如更新一个flavor一个字段,调用Flavor的update方法(由sqlalchemy)实现。引入对象模型后,相当于在服务与数据库之间又添加了一级对象层,各个服务直接和资源对象交互,资源对象再和数据库接口交互,数据库返回时也会相应的转化为对象模型中的对象。

对象模型的对象不仅封装了数据库访问,还支持了版本控制。每个对象都会维护一个版本号,发起RPC请求时必须指定对象的版本号。新版本的对象通常能够兼容旧版本对象,比如nova-conductor升级了使用对象模型版本为1.2,但nova-compute服务可能还没有升级完成,仍然使用的是1.1版本,此时请求返回时会把conductor的返回的对象转化为1.1版本兼容的对象。

目前Cinder服务还是直接访问数据库,目前已经在社区有对应的BP关于增加cinder-conductor服务Create conductor service for cinder like nova-conductor, 该BP于2013年6月提出,到当前最新版本N还尚未实现。

2. Nova配置

以上我们介绍了nova-conductor以及对象模型的背景,我们了解到所有服务访问数据库都必须通过RPC调用nova-conductor服务请求,但这并不是强制的,如果不考虑数据库访问安全,你仍然可以使用本地访问方式,nova-compute服务可以直接访问数据库而不发起nova-conductor RPC调用。我们看nova-compute服务的初始化,它位于nova/cmd/compute.y:

  1. def main(): 
  2.     # ... 
  3.     if not CONF.conductor.use_local: 
  4.         cmd_common.block_db_access('nova-compute'
  5.         objects_base.NovaObject.indirection_api = \ 
  6.             conductor_rpcapi.ConductorAPI() 
  7.     else
  8.         LOG.warning(_LW('Conductor local mode is deprecated and will ' 
  9.                         'be removed in a subsequent release')) 
  10.     # ... 

因此在/etc/nova.conf配置文件中可以配置是否直接访问数据库。以上indirection_api是Nova对象模型的一个字段,初始化为None。

如果设置use_local为true,则indirection_api为None,否则将初始化为conductor_rpcapi.ConductorAPI,从这里我们也可以看出调用conductor的入口。

我们可能会想到说在对象模型访问数据库时会有一堆if-else来判断是否使用use_local,事实上是否这样呢,我们接下来将分析源码,从而理解Openstack的设计理念。

3. 源码分析

3.1 nova-compute源码分析

本小节主要以删除虚拟机为例,分析nova-compute在删除虚拟机时如何操作数据库的。删除虚拟机的API入口为nova/compute/manager.py的_delete_instance方法,方法原型为:

  1. _delete_instance(self, context, instance, bdms, quotas) 

该方法有4个参数,context是上下文信息,包含用户、租户等信息,instance就是我们上面提到的对象模型中Instance对象实例,bdms是blockDeviceMappingList对象实例,保存着block设备映射列表,quotas是nova.objects.quotas.Quotas对象实例,保存该租户的quota信息。

该方法涉及的数据库操作代码为:

  1. instance.vm_state = vm_states.DELETED 
  2. instance.task_state = None 
  3. instance.power_state = power_state.NOSTATE 
  4. instance.terminated_at = timeutils.utcnow() 
  5. instance.save() 
  6. system_meta = instance.system_metadata 
  7. instance.destroy() 

从代码中可以看到,首先更新instance的几个字段,然后调用save()方法保存到数据库中,最后调用destroy方法删除该实例(注意,这里的删除并不一定是真的从数据库中删除记录,也有可能仅仅做个删除的标识)。

我们先找到以上的save()方法,它位于nova/object/instance.py模块中,方法原型为:

  1. @base.remotable 
  2. save(self, expected_vm_state=None, 
  3.      expected_task_state=None, admin_state_reset=False

save方法会记录需要更新的字段,并调用db接口保存到数据库中。关键是该方法的wrapper remotable,这个注解(python不叫注解,不过为了习惯这里就叫注解吧)非常重要,该方法在oslo中定义:

  1. def remotable(fn): 
  2.     """Decorator for remotable object methods.""" 
  3.     @six.wraps(fn) 
  4.     def wrapper(self, *args, **kwargs): 
  5.         ctxt = self._context 
  6.         if ctxt is None: 
  7.             raise exception.OrphanedObjectError(method=fn.__name__, 
  8.                                                 objtype=self.obj_name()) 
  9.         if self.indirection_api: 
  10.             updates, result = self.indirection_api.object_action( 
  11.                 ctxt, self, fn.__name__, args, kwargs) 
  12.             for key, value in six.iteritems(updates): 
  13.                 if key in self.fields: 
  14.                     field = self.fields[key
  15.                     # NOTE(ndipanov): Since VersionedObjectSerializer will have 
  16.                     # deserialized any object fields into objects already, 
  17.                     # we do not try to deserialize them again here. 
  18.                     if isinstance(value, VersionedObject): 
  19.                         setattr(self, key, value) 
  20.                     else
  21.                         setattr(self, key
  22.                                 field.from_primitive(self, key, value)) 
  23.             self.obj_reset_changes() 
  24.             self._changed_fields = set(updates.get('obj_what_changed', [])) 
  25.             return result 
  26.         else
  27.             return fn(self, *args, **kwargs) 
  28.  
  29.     wrapper.remotable = True 
  30.     wrapper.original_fn = fn 
  31.     return wrapper 

从代码看到,当indirection_api不为None时会调用indirection_api的object_action方法,由前面我们知道这个值由配置项use_local决定,当use_local为False时indirection_api为conductor_rpcapi.ConductorAPI。从这里了解到对象并不是通过一堆if-else来判断是否使用use_local的,而是通过@remotable注解实现的,remotable封装了if-else,当使用local时直接调用原来对象实例的save方法,否则调用indirection_api的object_action方法。

注意: 除了@remotable注解,还定义了@remotable_classmethod注解,该注解功能和@remotable类似,仅仅相当于又封装了个@classmethod注解。

3.2 RPC调用

前面我们分析到调用conductor_rpcapi.ConductorAPI的object_action方法,该方法在nova/conductor/rpcapi.py中定义:

  1. def object_action(self, context, objinst, objmethod, args, kwargs): 
  2.         cctxt = self.client.prepare() 
  3.         return cctxt.call(context, 'object_action', objinst=objinst, 
  4.                           objmethod=objmethod, args=args, kwargs=kwargs) 

rpcapi.py封装了client端的所有RPC调用方法,从代码上看,发起了RPC server端的object_action同步调用。此时nova-compute工作顺利转接到nova-conductor,并堵塞等待nova-conducor返回。

3.3 nova-conductor源码分析

nova-conductor RPC server端接收到RPC请求后调用manager.py的object_action方法(nova/conductor/manager.py):

  1. def object_action(self, context, objinst, objmethod, args, kwargs): 
  2.        """Perform an action on an object.""" 
  3.        oldobj = objinst.obj_clone() 
  4.        result = self._object_dispatch(objinst, objmethod, args, kwargs) 
  5.        updates = dict() 
  6.        # NOTE(danms): Diff the object with the one passed to us and 
  7.        # generate a list of changes to forward back 
  8.        for name, field in objinst.fields.items(): 
  9.            if not objinst.obj_attr_is_set(name): 
  10.                # Avoid demand-loading anything 
  11.                continue 
  12.            if (not oldobj.obj_attr_is_set(nameor 
  13.                    getattr(oldobj, name) != getattr(objinst, name)): 
  14.                updates[name] = field.to_primitive(objinst, name
  15.                                                   getattr(objinst, name)) 
  16.        # This is safe since a field named this would conflict with the 
  17.        # method anyway 
  18.        updates['obj_what_changed'] = objinst.obj_what_changed() 
  19.        return updates, result 

该方法首先调用obj_clone()方法备份原来的对象,主要为了后续统计哪些字段更新了。然后调用了_object_dispatch方法:

  1. def _object_dispatch(self, target, method, args, kwargs): 
  2.         try: 
  3.             return getattr(target, method)(*args, **kwargs) 
  4.         except Exception: 
  5.             raise messaging.ExpectedException() 

该方法利用反射机制通过方法名调用,这里我们的方法名为save方法,因此显然调用了target.save()方法,即最终还是调用的instance.save()方法,不过此时已经是在conductor端调用了.

又回到了nova/objects/instance.py的save方法,有人会说难道这不会无限递归RPC调用吗?显然不会,这是因为nova-conductor的indirection_api为None,在@remotable中肯定走else分支。

4. 思考一个问题

还记得在_delete_instance方法的数据库调用代码吗?这里再贴下代码:

  1. instance.vm_state = vm_states.DELETED 
  2. instance.task_state = None 
  3. instance.power_state = power_state.NOSTATE 
  4. instance.terminated_at = timeutils.utcnow() 
  5. instance.save() 
  6. system_meta = instance.system_metadata 
  7. instance.destroy() 

有人会说instance记录都要删了,直接调用destory方法不得了,前面一堆更新字段然后save方法是干什么的。这是因为Nova在处理删除记录时使用的是软删除策略,即不会真正得把记录彻底删除,而是在记录中有个deleted字段标记是否已经被删除。这样的好处是方便以后审计甚至数据恢复。

5. 总结

本文首先介绍了Openstack Nova组件数据库访问的发展历程,然后基于源码分析了当前Nova访问数据库的过程,最后解释了Nova使用软删除的原因。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/297168.html<

(0)
管理的头像管理
上一篇2025-05-22 08:35
下一篇 2025-05-22 08:36

相关推荐

  • 站群服务器和普通服务器到底哪个更适合GEO,怎么选?

    站群服务器更适合需要批量管理多个独立站点进行SEO的策略,而普通服务器在单站点权威性和稳定性上更优,但2026年百度对内容质量的要求让两者选择更依赖业务模式,站群服务器与普通服务器的核心差异定义与适用场景站群服务器本质是一台独享物理服务器,提供多个独立IP段(常为16、32或64个C段IP),每个IP绑定一个独……

    2026-07-28
    0
  • 物理服务器和云服务器做站群到底选哪个,哪个更稳定?

    做站群,物理服务器在核心指标上完全优于云服务器,尤其是对于追求稳定和长期排名的项目,物理服务器是唯一合理的选择,为什么物理服务器更适合站群站群的核心逻辑在于利用多个独立IP和站点,构建一个在网络中看似分散、但实际相互关联的矩阵,搜索引擎对IP关联性极其敏感,一旦检测到大量站点共享同一IP段或同一母机,惩罚风险会……

    2026-07-28
    0
  • 国内高防服务器哪家防御真实靠谱,怎么选?

    国内高防服务器哪家防御真实靠谱?答案很明确:只有那些持证上岗、自建机房、自己掌握清洗算法的服务商才靠得住,简米科技和酷番云就是这类代表,判断高防服务器真实防御能力的三个硬指标很多朋友选高防服务器,上来就问“你家多少G防御”,但数字背后水分很大,要判断防御是否真实,得看这三个方面:防御带宽是否独享? 有些服务商宣……

    2026-07-28
    0
  • 裸金属服务器和物理服务器有什么区别?,怎么选?

    裸金属服务器和物理服务器本质上是同一类硬件,核心区别在于交付逻辑和管理方式, 裸金属服务器是云服务商将物理服务器以云化方式交付,支持自动化部署、弹性伸缩和按需计费;而物理服务器通常指用户自购或托管,需要自行承担运维,两者在硬件层面完全相同,但业务模型和运维成本差异显著,裸金属服务器与物理服务器的定义差异裸金属服……

    2026-07-28
    0
  • 做GEO站群选哪家服务器服务商靠谱,怎么选?

    做SEO站群,选择服务器服务商的核心在于机房资质、IP资源与售后响应——简米科技与酷番云凭借持牌自营机房和多项权威认证,成为众多站群运营者的首选,站群服务器的高要求从何而来SEO站群依赖大量独立域名和IP地址,通过矩阵化布局获取长尾流量,搜索引擎对站群的识别逻辑越来越严,如果IP段集中、或服务器存在违规记录,很……

    2026-07-28
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注