通過demo學習OpenStack開發——數據庫(1)
2016-01-27 15:52:16 來源:劉陳泓 評論:0 點擊:
編者按:《通過demo學習OpenStack開發》專欄是劉陳泓的系列文章,專欄通過開發一個demo的形式來介紹一些參與OpenStack項目開發的必要的基礎知識,希望幫助大家入門企業級Python項目的開發和OpenStack項目的開發。劉陳泓主要關注OpenStack的身份認證和計費領域。另外,還對云計算、分布式系統應用和開發感興趣。
OpenStack中的關系型數據庫應用
OpenStack中的數據庫應用主要是關系型數據庫,主要使用的是MySQL數據庫。當然也有一些NoSQL的應用,比如Ceilometer項目。就SQL數據庫本身的應用而言,OpenStack的項目和其他項目并沒有什么區別,也是采用ORM技術對數據進行增刪改查而已。
本文的重點是講解OpenStack項目中對關系型數據庫的應用的基礎知識,更多的是涉及ORM庫的使用。對于數據庫的安裝和配置,需要讀者自己查找一下MySQL的教程,如果只是為了驗證ORM的相關知識,也可以使用sqlite數據庫。
數據庫的選擇
OpenStack官方推薦的保存生產數據的是MySQL數據庫,在devstack項目(這個項目用于快速搭建OpenStack開發環境)中也是安裝了MySQL數據庫。不過,因為OpenStack的項目中沒有使用特定的只有在MySQL上才能用的功能,而且所采用的ORM庫SQLAlchemy也支持多種數據庫,所以理論上選擇PostgreSQL之類的數據庫來替代MySQL也是可行的。
另外,OpenStack項目在單元測試中使用的是sqlite的內存數據庫,這樣開發者運行單元測試的時候不需要安裝和配置復雜的MySQL數據庫,只要安裝好sqlite3就可以了。而且,數據庫是保存在內存中的,會提高單元測試的速度。
ORM的選擇
什么是ORM
ORM的全稱是Object-Relational Mapping,即對象關系映射,是一種利用編程語言的對象來表示關系數據庫中的數據的技術,其更形式化的定義可以參考Wiki頁面Orject-relational mapping。簡單的說,ORM就是把數據庫的一張表和編程語言中的一個對象對應起來,這樣我們在編程語言中操作一個對象的時候,實際上就是在操作這張表,ORM(一般是一個庫)負責把我們對一個對象的操作轉換成對數據庫的操作。
Python中的ORM實現
一般來說,各種主流語言都有自己的ORM實現,一般來說也不只一種,比較出名的有Java的Hibernate,Ruby on Rails的ORM,C++的ODB等。在Python中也存在多種ORM的實現,最著名的兩種是Django的Model層的ORM實現,以及SQLAlchemy庫。這兩種ORM實現基本上是Python中ORM的事實上的標準,如果你寫Django應用,那么你就用Django自帶的實現;不然,你就可以選擇SQLAlchemy庫。
OpenStack基本上都是Python項目,所以在OpenStack中,ORM主要是使用了SQLAlchemy庫(Keystone、Nova、Neutron等);不過使用了Django的Horizon項目(面板)還是使用了Django自帶的ORM實現。本文主要是講解OpenStack中如何使用SQLAlchemy庫,這個也是開發OpenStack項目的最基本知識。
SQLAlchemy
SQLAlchemy簡介
SQLAlchemy項目是Python中最著名的ORM實現,不僅在Python項目中也得到了廣泛的應用,而且對其他語言的ORM有很大的影響。OpenStack一開始選擇這個庫,也是看中了它足夠穩定、足夠強大的特點。
SQLAlchemy項目最新的版本是1.0.11,1.0系列是今年剛發的,0.9系列應該還是應用最廣泛的版本。對于一般的應用來說,0.9系列和1.0系列差別不大。
關于SQLAlchemy的學習
我個人覺得SQLAlchemy的學習難度會比Django的Model層難一些,因為一個最簡單的例子也會有一些不太直觀的地方,對于沒用過的人來說,會比較難以理解。不過SQLAlchemy官網整理了一些比較不錯的入門教程,是一個比較好的學習起點:Tutorials。另外,官方的Reference其實是一個很好的教程,講了很多基本的概念,有助于理解SQLAlchemy的庫的使用。Reference還提供PDF版本的下載。我個人建議大家直接閱讀Reference即可,閱讀順序就按照PDF文件的章節編排順序進行。雖然這個文檔很長,但是我最后發現這么做是最節約時間的。
SQLAlchemy的架構
先讓我們來看一下SQLAlchemy這個庫的總體架構,如下圖(圖來自官網)所示:
SQLAlchemy這個庫分為兩層:
- 上面這層是ORM層,為用戶提供ORM接口,即通過操作Python對象來實現數據庫操作的接口。
- 下面這層是Core層,這層包含了Schema/Types、SQL Expression Language、Engine這三個部分:
- SQL Expression Language是SQLAlchemy中實現的一套SQL表達系統,主要是實現了對SQL的DML(Data Manipulation Language)的封裝。這里實現了對數據庫的SELECT、DELETE、UPDATE等語句的封裝。SQL Expression Language是實現ORM層的基礎。
- Schema/Types這部分主要是實現了對SQL的DDL(Data Definition Language)的封裝。實現了Table類用來表示一個表,Column類用來表示一個列,也是實現了將數據庫的數據類型映射到Python的數據類型。上面的SQL Expression Language的操作對象就是這里定義的Table。
Engine實現了對各種不同的數據庫客戶端的封裝和調度,是所有SQLAlchemy應用程序的入口點,要使用SQLAlchemy庫來操作一個數據庫,首先就要有一個Engine對象,后續的所有對數據庫的操作都要通過這個Engine對象來進行。下圖是官方文檔中的Engine位置的描述圖:
- Pool是Engine下面的一個模塊,用來管理應用程序到數據庫的連接。
- Dialect是Engine下的另一個模塊,用來對接不同的數據庫驅動(即DBMS客戶端),這些驅動要實現DBAPI接口。
- 最后,SQLAlchemy還要依賴各個數據庫驅動的DBAPI接口來實現對數據庫服務的調用。DBAPI是Python定義的數據庫API的實現規范,具體見PEP0249。
上面簡單的總結了SQLAlchemy的架構,希望大家能夠大概了解一下SQLAlchemy,在后面介紹一些相關概念時,能夠知道這個概念是屬于整個架構的哪個部分。
Dialect和數據庫客戶端
上面提到了Dialect是用來對接不同的數據庫驅動的,它主要負責將SQLAlchemy最后生成的數據庫操作轉換成對數據庫驅動的調用,其中會處理一些不同數據庫和不同DBAPI實現的差別。這個部分一般是SQLAlchemy的開發者關心的內容,如果你只是使用SQLAlchemy來操作數據庫,那么可以不用關心這個部分。不過我們還是要來了解一下SQLAlchemy支持的和OpenStack相關的數據庫驅動。
MySQL
OpenStack項目主要是使用MySQL,之前一直都在使用MySQL-Python驅動,因為這個驅動足夠成熟和穩定。不過這個情況正在轉變,有如下兩個原因:
- MySQL-Python不支持Python3,而OpenStack正在轉換到Python3的過程中,所以這個驅動最終是要放棄的。
- MySQL-Python是用C語言寫的,不支持eventlet庫的monkey-patch操作,無法被eventlet庫轉換成異步操作,所以使用了eventlet庫的到OpenStack項目在使用MySQL數據庫時,都是進行同步的串行操作,有性能損失。
為了解決這個問題,社區發起了一次對新驅動的評估,主要是評估MySQL-Python驅動:PyMySQL Evaluation。這個評估還在社區的郵件列表發起了好幾次討論,到目前為止的結果是:如果使用Python 2.7,那么繼續使用MySQL-Python這個驅動,否則就使用PyMySQL這個驅動。PyMySQL驅動是使用純Python寫的,不僅支持Python3而且可以支持eventlet的異步。
SQLite3
OpenStack項目一般會使用SQLite3數據庫來運行單元測試。OpenStack在Python2.7下會使用pysqlite驅動,不過這個驅動和標準庫中的sqlite3模塊是一樣的,也就是Python內置了SQLite3的驅動,你無需選擇其他的驅動。
SQLAlchemy的基本概念和使用
使用SQLAlchemy大體上分為三個步驟:連接到數據庫,定義數據模型,執行數據操作。
連接到數據庫
在你的應用可以使用數據庫前,你要先定義好數據庫的連接,包括數據庫在哪里,用什么賬號訪問等。所有的這些工作都是通過Engine對象來進行的(記得上面提到的Engine了么?)。
數據庫URL
SQLAlchemy使用URL的方式來指定要訪問的數據庫,整個URL的具體格式如下:
dialect+driver://username:password@host:port/database
其中,dialect就是指DBMS的名稱,一般可選的值有:postgresql、 mysql、sqlite等。driver就是指驅動的名稱,如果不指定,SQLAlchemy會使用默認值。database就是指DBMS中的一個數據庫,一般是指通過CREATE DATABASE
語句創建的數據庫。其他的參數就不言而喻了。dialect和driver參數有很多選擇,具體的可以參考官方文檔:Database URLs。
創建Engine對象
確定了要連接的數據庫信息后,就可以通過create_engine
函數來創建一個Engine對象了。
from sqlalchemy import create_engineengine = create_engine('sqlite://:memory:')
create_engine
函數還支持以下幾個參數:
- connect_args:一個字典,用來自定義數據庫連接的參數,比如指定客戶端使用的字符編碼。
- pool_size和max_overflow:指定連接池的大小。
- poolclass:指定連接池的實現
- echo:一個布爾值,用來指定是否打印執行的SQL語句到日志中。
還有很多其他的參數,可以參考官方文檔:Engine Configuration。
一般來說,Engine對象會默認啟用連接池,會根據不同的dialect來選擇不同的默認值。一般來說,你是不用考慮連接池的配置的,默認情況都配置好了。想了解關于連接池的更多內容,請查看官方文檔:Connection Pooling。
使用Engine對象
一般來說,應用程序的代碼是不直接使用Engine對象的,而是把Engine對象交給ORM去使用,或者創建session對象來使用。不過,我們還是來簡單看一下Engine對象能做什么事情。
應用程序可以調用Engine對象的connect()
方法來獲得一個到數據庫的連接對象;然后可以在這個連接對象上調用execute()
來執行SQL語句,調用begin()
、commit()
、rollback()
來執行事務操作;調用close()
來關閉連接。Engine對象也有一些快捷方法來直接執行上述操作,避免了每次都要調用connect()
來獲取連接這種繁瑣的代碼,比如engine.execute()
、with engine.begin()
等。
定義數據模型
有了數據庫連接后,我們就可以來定義數據模型了,也就是定義映射數據庫表的Python類。在SQLAlchemy中,這是通過Declarative的系統來完成的。
Declarative系統
根據官方文檔的描述,SQLAlchemy一開始是采用下面這種方式來定義ORM的:
- 首先定義一個映射類,這個類是數據庫表在代碼中的對象表示,這類的類屬性是很多Column類的實例。
- 然后定義一個Table對象,這里的Table就是上面提到的在Schema/Types模塊中的一個類,用來表示一個數據庫中的表。
- 調用
sqlalchemy.orm.mapper
函數把步驟1中定義的類映射到步驟2中定義的Table。
上面這種方式稱為Classical Mappings,看起來好麻煩啊。所以就有了Declarative系統。這個系統就是一次完成這三個步驟,你只需要定義步驟1中的類即可。這也是現在在SQLAlchemy中使用ORM的方式,無需在使用過去這種麻煩的方法。
要使用Declarative系統,你需要為所有映射類創建一個基類,這個基類用來維護所有映射類的元信息。
from sqlalchemy.ext.declarative import declarative_baseBase = declarative_base()
定義映射類
現在我們可以開始創建映射類了。假設我們在數據庫中有一個表Person,這個表有兩個列,分別是id和name,那么我們創建的映射類如下:
from sqlalchemy import Column, Integer, String# 這里的基類Base是上面我們通過declarative_base函數生成的class Person(Base): __tablename__ = 'person' id = Column(Interger, primary_key=True) name = Column(String(250), nullable=False)
這樣我們就定義了一個映射類Person,后續我們可以通過操作這個類的實例來實現對數據庫表person的操作。在我們的映射類中,我們使用__tablename__
屬性來指定該映射類所對應的數據庫表,通過Column
類實例的方式來指定數據庫的字段。這里,讀者可能會問:我如何能知道Column
都能支持哪些類型呢?這個查看官方文檔獲得:Column And Data Types。
因為我們使用了Declarative系統,所以雖然我們自己沒有定義Table對象,但是Declarative系統幫我們做了,并且幫我們調用了mapper
函數。因此,當我們定義好一個表的映射類后,這個類的__table__
屬性就保存了該映射類所映射的Table對象:
In [6]: Person.__table__Out[6]: Table('person', MetaData(bind=None), Column('id', Integer(), table=<person>, primary_key=True, nullable=False), Column('name', String(length=250), table=<person>, nullable=False), schema=None)
定義映射類是我們使用ORM的最主要的功能之一,不僅可以指定單表的映射,還能夠指定表之間的關系。由于篇幅限制,我們在本文就不展開講了。
Schema和Metadata
關于Table對象,我們上面也提到了,它屬于SQLAlchemy的core層的Schema/Types這個部分。SQLAlchemy中的Schema可以理解為和DDL相關的一套體系,它告訴SQLAlchemy的其他部分,數據庫中的表是如何定義的。這個相當于我們在MySQL中使用describe
命令,或者在PostgreSQL中使用d
命令。
SQLAlchemy中通過schema metadata來實現上面說的Schema。Schema metadata,官方文檔中也稱為database metadata,簡稱為metadata,是一個容器,其中包含了和DDL相關的所有信息,包括Table、Column等對象。當SQLAlchemy要根據映射類生成SQL語句時,它會查詢metadata中的信息,根據信息來生成SQL語句。
為了要讓metadata可以工作,我們需要把DDL的相關信息放到metadata中。如果你注意看上面Person.__table__
的輸出,就會發現Table
類的第二個參數就是一個Metadata實例,也就是說,我們需要在定義Table的時候就把DDL信息放到metadata中。如果是是用classical mapping的方式,我們需要先創建一個metadata實例,然后每次創建一個Table對象的時候就把metadata傳遞進去。從寫代碼的角度來說,這個方式沒有什么問題,也不算麻煩;問題是我們在使用ORM的過程中,幾乎不會用到metadata,metadata基本上是給SQLAlchemy用的,對于用戶來說metadata提供的接口只能用來創建表和刪除表,這種操作的頻率遠低于查詢操作。
好在Declarative系統則幫我們把這些都做好了。當我們通過declarative_base()
生成一個基類Base的時候,這個基類就已經包含了一個metadata實例,后面基于Base定義映射類都會被自動加入到這個metadata中。我們可以通過Base.metadata
來訪問這個metadata實例。
說了這么多關于metadata的內容,簡單總結一下:metadata是schema在SQLAlchemy中的實現,包含了DDL的信息,SQLAlchemy中的其他部分需要依賴于metadata中的信息,一般用戶很少使用metadata。
很少用?那說這么多是做啥?主要是讓讀者可以理解下面這個語句的原理:
Base = declarative_base()# 基于Base定義映射類Base.metadata.create_all(engine)
最后這行代碼是我們最常用到metadata的地方:創建所有的表。我們告訴create_all
使用哪個engine,它就會生成所有的CREATE TABLE
語句,并且通過engine發送到數據庫上執行。這個在單元測試的時候很有用。你可以執行一下下面的代碼來觀察輸出:
from sqlalchemy import Column, Integer, Stringfrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy import create_engineBase = declarative_base()class Person(Base): __tablename__ = 'person' id = Column(Integer, primary_key=True) name = Column(String(250), nullable=False)engine = create_engine('sqlite:///:memory:', echo=True)Base.metadata.create_all(engine)
輸出結果如下:
...2016-01-06 09:56:03,600 INFO sqlalchemy.engine.base.Engine PRAGMA table_info("person")2016-01-06 09:56:03,601 INFO sqlalchemy.engine.base.Engine ()2016-01-06 09:56:03,602 INFO sqlalchemy.engine.base.EngineCREATE TABLE person ( id INTEGER NOT NULL, name VARCHAR(250) NOT NULL, PRIMARY KEY (id))2016-01-06 09:56:03,603 INFO sqlalchemy.engine.base.Engine ()2016-01-06 09:56:03,603 INFO sqlalchemy.engine.base.Engine COMMIT
關于Metadata的更多信息,請查看官方文檔:Schema Definition Language。
會話
會話(session)是我們通過SQLAlchemy來操作數據庫的入口。我們前面有介紹過SQLAlchemy的架構,session是屬于ORM層的。Session的功能是管理我們的程序和數據庫之間的會話,它利用Engine的連接管理功能來實現會話。我們在上文有提到,我們創建了Engine對象,但是一般不直接使用它,而是把它交給ORM去使用。其中,通過session來使用Engine就是一個常用的方式。
要是用session,我們需要先通過sessionmaker
函數創建一個session類,然后通過這個類的實例來使用會話,如下所示:
from sqlalchemy.orm import sessionmakerDBSession = sessionmaker(bind=engine)session = DBSession()
我們通過sessionmaker
的bind參數把Engine對象傳遞給DBSession
去管理。然后,DBSession
實例化的對象session
就能被我們使用了。
CRUD
CRUD就是CREATE、READ、UPDATE、DELETE,增刪改查。這個也是SQLAlchemy中最常用的功能,而且都是通過上一小節中的session
對象來使用的。我們這簡單的介紹一下這四個操作,后面會給出官方文檔的位置。
Create
在數據庫中插入一條記錄,是通過session的add()
方法來實現的,你需要先創建一個映射類的實例,然后調用session.add()
方法,然后調用session.commit()
方法提交你的事務(關于事務,我們下面會專門講解):
new_person = Person(name='new person')session.add(new_person)session.commit()
Delete
刪除操作和創建操作差不多,是把一個映射類實例傳遞給session.delete()
方法。
Update
更新一條記錄需要先使用查詢操作獲得一條記錄對應的對象,然后修改對象的屬性,再通過session.add()
方法來完成更新操作。
Read
查詢操作,一般稱為query,在SQLAlchemy中一般是通過Query對象來完成的。我們可以通過session.query()方法來創建一個Query對象,然后調用Query對象的眾多方法來完成查詢操作。
事務
使用session,就會涉及到事務,我們的應用程序也會有很多事務操作的要求。當你調用一個session的方法,導致session執行一條SQL語句時,它會自動開始一個事務,直到你下次調用session.commit()
或者session.rollback()
,它就會結束這個事務。你也可以顯示的調用session.begin()
來開始一個事務,并且session.begin()
還可以配合Python的with來使用。
會話、CRUD、事務的小結
上面關于session,CRUD和事務的內容寫的比較少,因為這些功能的內容很多,而且官方文檔也寫得很全面,本文就不做一些重復說明了。我們會在下一篇文章中通過webdemo的代碼來看看如何使用這些功能。
總結
本文介紹了OpenStack中和數據庫相關的一些知識,重點講解了SQLAlchemy這個庫的基本概念和架構。下一篇文章,我們會通過demo來實際項目中如何使用SQLAlchemy。
感謝魏星對本文的策劃和審校。
給InfoQ中文站投稿或者參與內容翻譯工作,請郵件至editors@cn.infoq.com。也歡迎大家通過新浪微博(@InfoQ,@丁曉昀),微信(微信號:InfoQChina)關注我們,并與我們的編輯和其他讀者朋友交流(歡迎加入InfoQ讀者交流群(已滿),InfoQ讀者交流群(#2)
)。
相關熱詞搜索:learning openstack through demo part01 架構 & 設計 語言 & 開發 數據庫管理 OpenStack項目 數據庫設計 OpenStack 數據庫
上一篇:微軟牛津計劃,輕松打造自然語言應用
下一篇:可視化組合管理:協同校準企業
