oslo.db库为OpenStack其他组件提供了针对不同后端数据库的数据库连接,并提供了各种数据库操作的辅助工具类和方法。OpenStack各组件使用SQLAlchemy框架实现对数据库的连接、查询等操作,因此oslo.db并不是一个完整的ORM库,也没有封装执行SQL语句;其只是对SQLAlchemy进行了封装,其与SQLAlchemy结合使用使得在OpenStack各组件中实现数据库连接、查询等操作变得更加简单。因此,本文不对SQLAlchemy的使用进行详细介绍,如果需要了解SQLAlchemy的使用,可以参考python对Mysql操作和使用ORM框架(SQLAlchemy)和SQLAlchemy技术文档。本文将结合oslo.db的使用详细分析其实现方式。
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.1节中详细介绍了Session Handling,即enginefacade的几种使用场景和使用方法,本节将据此分析enginefacade中的几个重要的类和方法,以及其实现原理。
_TransactionFactory类是_TransactionContext对象的一个工厂类,_TransactionContext对象可以使用该类生成一个数据库Session或一个数据库连接Connection对象;而_TransactionFactory对象会通过读取配置文件中数据库的配置信息创建相应的Session和Connection对象以进行数据库的相关操作。
_TransactionContext类的每一个实例化对象都代表了单个数据库事务,其中,定义了factory和global_factory两个_TransactionFactory对象属性,factory用来为每一个_TransactionContext对象创建数据库连接或Session,而global_factory会被一个全局的_context_manager在创建一个新的_TransactionContext对象时使用。在_TransactionContext类中还定义了session、connection、transaction来分别表示一个数据库session、连接、事务对象,用于进行具体的数据库操作。最后,_TransactionContext类还定义了一个mode属性来表示数据库操作的状态。在oslo_db的enginefacade中定义了3种数据库操作状态:
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模块下,该模块中主要定义了一下几个类:
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,表示如果操作中发生死锁也会进行重试操作。