OpenStack公共组件oslo之九——oslo.db

        oslo.db库为OpenStack其他组件提供了针对不同后端数据库的数据库连接,并提供了各种数据库操作的辅助工具类和方法。OpenStack各组件使用SQLAlchemy框架实现对数据库的连接、查询等操作,因此oslo.db并不是一个完整的ORM库,也没有封装执行SQL语句;其只是对SQLAlchemy进行了封装,其与SQLAlchemy结合使用使得在OpenStack各组件中实现数据库连接、查询等操作变得更加简单。因此,本文不对SQLAlchemy的使用进行详细介绍,如果需要了解SQLAlchemy的使用,可以参考python对Mysql操作和使用ORM框架(SQLAlchemy)和SQLAlchemy技术文档。本文将结合oslo.db的使用详细分析其实现方式。

1. Session Handling

1.1 使用方法

        Session handling指的是使用oslo_db.sqlalchemy.enginefacade模块管理数据库连接、会话和事务处理等,该模块一般以装饰器形式使用,也可以使用with语句进行调用。

        Session handling可以为一个函数提供装饰器,也可以作为一个上下文管理器传递一个Session或Connection对象。这两种方式都需要用到一个上下文对象。这个对象可以是任何一个类实例。下面这个例子便是使用上下文管理器形式的Session handling的使用方式:

    from oslo_db.sqlalchemy import enginefacade


    class MyContext(object):
        "User-defined context class."


    def some_reader_api_function(context):
        with enginefacade.reader.using(context) as session:
            return session.query(SomeClass).all()


    def some_writer_api_function(context, x, y):
        with enginefacade.writer.using(context) as session:
            session.add(SomeClass(x, y))


    def run_some_database_calls():
        context = MyContext()

        results = some_reader_api_function(context)
        some_writer_api_function(context, 5, 10)

        在这个例子中,首先定义了一个MyContext作为上下文管理器,然后分别在some_reader_api_function(context)和some_writer_api_function(context, x, y)来进行一些数据库操作。在some_reader_api_function(context)中,通过enginefacade.reader使用定义的上下文管理器生成一个数据库连接Session对象,然后使用Session对象的query()方法进行查询偶作;而在some_writer_api_function(context, x, y)中,则通过enginefacade.writer使用定义的上下文管理器生成一个数据库连接Session对象,然后使用这个Session对象的add()方法进行插入操作。其中,使用了enginefacade模块中的reader和writer两个重要的变量,这两个变量分别表示一个reader和writer函数的全局调用入口,关于reader和writer会在之后进行详细分析。除了这种方式之外,还可以使用装饰器模式使用enginefacade模块中的装饰器方法,使用方法如下所示:

    from oslo_db.sqlalchemy import enginefacade

    @enginefacade.transaction_context_provider
    class MyContext(object):
        "User-defined context class."

    @enginefacade.reader
    def some_reader_api_function(context):
        return context.session.query(SomeClass).all()


    @enginefacade.writer
    def some_writer_api_function(context, x, y):
        context.session.add(SomeClass(x, y))


    def run_some_database_calls():
        context = MyContext()

        results = some_reader_api_function(context)
        some_writer_api_function(context, 5, 10)
        首先,在定义MyContext类时,使用了@enginefacade.transaction_context_provider装饰器为其设置Session等属性,该装饰器可用于设置Session、Connection、Transaction等属性。在使用时,则可以直接使用@enginefacade.writer和@enginefacade.reader从MyContext对象中获取对应的session,并用其进行数据库操作。

    @enginefacade.reader.connection
    def _refresh_from_db(context, cache):
        sel = sa.select([table.c.id, table.c.name])
        res = context.connection.execute(sel).fetchall()
        cache.id_cache = {r[1]: r[0] for r in res}
        cache.str_cache = {r[0]: r[1] for r in res}
       当不需要一个具体的数据库Session对象时,可以直接使用@enginefacade.reader.connection获取一个临时的数据库连接进行数据库相关操作。使用方式是使用@engingefacade.reader.connection装饰器,为上下文对象context添加一个数据库连接对象connection,直接使用该对象进行数据库操作即可。

        需要注意的是,无论是context.session还是context.connection,都需要在一个writer或reader声明的对象中。否则,使用时将抛出异常。

        当然,装饰器也可以用在一个类或对象的方法中,使用方式如下:

    class DatabaseAccessLayer(object):

        @classmethod
        @enginefacade.reader
        def some_reader_api_function(cls, context):
            return context.session.query(SomeClass).all()

        @enginefacade.writer
        def some_writer_api_function(self, context, x, y):
            context.session.add(SomeClass(x, y))
        此时,需要注意的是,enginefacade的装饰器必须在classmethod装饰器之前使用,否则会抛出TypeError异常。

1.2 实现原理

        在1.1节中详细介绍了Session Handling,即enginefacade的几种使用场景和使用方法,本节将据此分析enginefacade中的几个重要的类和方法,以及其实现原理。

1.2.1 _TransactionFactory类

        _TransactionFactory类是_TransactionContext对象的一个工厂类,_TransactionContext对象可以使用该类生成一个数据库Session或一个数据库连接Connection对象;而_TransactionFactory对象会通过读取配置文件中数据库的配置信息创建相应的Session和Connection对象以进行数据库的相关操作。

1.2.2 _TransactionContext类

        _TransactionContext类的每一个实例化对象都代表了单个数据库事务,其中,定义了factory和global_factory两个_TransactionFactory对象属性,factory用来为每一个_TransactionContext对象创建数据库连接或Session,而global_factory会被一个全局的_context_manager在创建一个新的_TransactionContext对象时使用。在_TransactionContext类中还定义了session、connection、transaction来分别表示一个数据库session、连接、事务对象,用于进行具体的数据库操作。最后,_TransactionContext类还定义了一个mode属性来表示数据库操作的状态。在oslo_db的enginefacade中定义了3种数据库操作状态:

  1. _READER:这种状态表示事务是只读的,且其只能在同步更新的从数据库上使用,否则应该使用主数据库。
  2. _ASYNC_READER:这个状态表示事务是只读的,且其可以在异步更新的从数据库上使用。
  3. _WRITER:这种状态表示事务写入数据,并且应该直接指向主数据库。

1.2.3 _TransactionContextManager类

        _TransactionContextManager类为数据库事务处理提供了上下文管理器context-management和装饰器。_TransactionContextManager对象将_TransactionFactory对象和用户定义的_TransactionContext上下文对象集成到一起,以完成对数据库的事务处理等操作。1.1节中提到的writer和reader都是一个全局的_TransactionContextManager对象_context_manager的两个不同的方法,分别用来产生一个用于写操作或读操作的_TransactionContextManager对象。

2 ModelBase

        ModelBase是oslo.db为OpenStack各个组件封装的Model的基类。通常,在将一个数据库表抽象为一个类时,定义的类会继承ModelBase类。如Nova中为抽象所有数据库表而定义的基类:
from oslo_db.sqlalchemy import models

class NovaBase(models.TimestampMixin,
               models.ModelBase):
    metadata = None

    def __copy__(self):
        """Implement a safe copy.copy().

        SQLAlchemy-mapped objects travel with an object
        called an InstanceState, which is pegged to that object
        specifically and tracks everything about that object.  It's
        critical within all attribute operations, including gets
        and deferred loading.   This object definitely cannot be
        shared among two instances, and must be handled.

        The copy routine here makes use of session.merge() which
        already essentially implements a "copy" style of operation,
        which produces a new instance with a new InstanceState and copies
        all the data along mapped attributes without using any SQL.

        The mode we are using here has the caveat that the given object
        must be "clean", e.g. that it has no database-loaded state
        that has been updated and not flushed.   This is a good thing,
        as creating a copy of an object including non-flushed, pending
        database state is probably not a good idea; neither represents
        what the actual row looks like, and only one should be flushed.

        """
        session = orm.Session()

        copy = session.merge(self, load=False)
        session.expunge(copy)
        return copy
        NovaBase类便继承了oslo.db中的ModelBase和TimestampMixin类。ModelBase的使用就是这么简单。接下来,将通过ModelBase的实现分析该类的主要作用。与ModelBase相关的类与方法都定义在oslo_db.sqlalchemy.models模块下,该模块中主要定义了一下几个类:
  • ModelBase类:该类作为所有数据库表抽象的基类,提供了save、get、update、keys、items等常用的数据库操作方法,程序员在为数据库表定义数据结构时,都需要继承该类,然后使用或覆写这些方法完成对一个数据库表的所有CURD操作。
  • ModelIterator类:该类主要用于对数据库表中数据进行迭代查询的操作。
  • TimestampMixin类:由于OpenStack多数数据库表中都定义了创建时间和更新时间字段,因此该类专门用来获取记录的创建时间和更新时间,如果为空则可以自动设置为当前时间。
  • SoftDeleteMixin类:OpenStack多数数据库表都为定义了删除记录标志和删除记录时间,该类则是专门用来获取记录的删除记录标志和删除时间。
        由此可以看出,oslo_db.sqlalchemy.models模块为OpenStack其他组件进行数据库操作和定义创建一个一组模板类,可以在具体实现数据库操作时很好的封装数据库表的数据结构。

3 DB API backup support

        OpenStack项目众多,为了使OpenStack项目可以使用统一的数据库后端API支持,oslo.db为OpenStack其他项目提供了统一的数据库后端操作API支持。oslo.db定义了DBAPI类统一管理OpenStack其他组件数据库后端操作的API,该类在初始化是需要指定backend_name和backend_mapping属性,在使用时,首先调用该类的类方法from_config()方法创建DBAPI对象,接着调用_load_backend()方法,通过oslo.utils中定义的导入模块的方法将对应数据库操作的API导入,然后就可以调用相应的API进行数据库操作了。具体使用方法如下:
    from oslo_config import cfg
    from oslo_db import api as db_api


    _BACKEND_MAPPING = {'sqlalchemy': 'project.db.sqlalchemy.api'}

    IMPL = db_api.DBAPI.from_config(cfg.CONF, backend_mapping=_BACKEND_MAPPING)

    def get_engine():
        return IMPL.get_engine()

    def get_session():
        return IMPL.get_session()

    # DB-API method
    def do_something(somethind_id):
        return IMPL.do_something(somethind_id)
        oslo.db除了定义DBAPI统一管理数据库后端API,还定义了多个装饰器辅助进行数据库操作,如@wrap_db_retry可以捕获数据库操作过程中抛出的异常,然后重连数据库等。其使用方法如下:
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
@pick_context_manager_writer
def service_update(context, service_id, values):
    service_ref = service_get(context, service_id)
    # Only servicegroup.drivers.db.DbDriver._report_state() updates
    # 'report_count', so if that value changes then store the timestamp
    # as the last time we got a state report.
    if 'report_count' in values:
        if values['report_count'] > service_ref.report_count:
            service_ref.last_seen_up = timeutils.utcnow()
    service_ref.update(values)

    return service_ref
        上述示例中表示如果更新service_group表操作抛出异常则重试该操作,最大重试次数为5次,设置retry_on_deadlock为True,表示如果操作中发生死锁也会进行重试操作。

4 DB migration extensions

        oslo.db中还定义了与数据库相关的扩展操作,这些操作主要定义在oslo_db.sqlalchemy.migration模块中。其中,主要定义了db_version_control(engine, abs_path, version=None)方法进行版本控制,定义了db_sync(engine, abs_path, version=None, init_version=0, sanity_check=True)方法进行数据库的数据同步。

你可能感兴趣的:(OpenStack)