最近開始在看discuzx3.1的代碼,看到數據庫層的實現,discuzx的數據庫層可以支撐數據庫分庫,分佈式部署,主要水平分表,也能夠很方便的支持其餘數據庫。性能上,能夠作讀寫分離,支持數據緩存。能夠說,是一個很完善的數據庫層的解決方案了。php
數據庫層分爲三層,業務邏輯層封裝,抽象層,和驅動層。如圖:mysql
其中,數據抽象層封裝定義數據庫操做,負責解析sql語句,鏈接底層驅動執行sql,並數據安全過濾。sql
數據庫抽象層由discuzx_database類實現,該類全部的成員變量和方法都是靜態的,能夠直接調用。本類中,init 方法用來初始化底層驅動,設置數據庫配置,而且創建默認的數據庫鏈接。table方法,設置當前操做的數據表,這個方法很關鍵,數據庫的分佈式部署,讀寫分離,都是由表名來肯定的。這個在驅動層具體實現。其餘如insert,delete,update,fetch_all等等方法是封裝了數據表的基本操做,checkquery方法負責數據的安全檢查,防注入。query方法負責解析sql語句,調用驅動層,執行sql語句,quote,field_quote,field,format等方法負責sql語句的格式化。數據庫
數據庫驅動層選擇數據庫,直接鏈接數據庫,跟數據庫交互。目前discuzx默認提供兩種底層驅動,mysql和mysqli。以mysql爲例,整個驅動層其實由db_dirver_mysql和db_driver_mysql_slave兩個類外加數據庫配置文件實現,db_driver_mysql_salve繼承於db_driver_mysql。緩存
配置文件中(./config/config_global.php )可配置數據表的讀寫分離,包括多臺從服務器,分佈式部署。安全
$_config['db']['1']['slave']['1']['dbhost'] = 'localhost'; $_config['db']['1']['slave']['1']['dbuser'] = 'root'; $_config['db']['1']['slave']['1']['dbpw'] = 'root'; $_config['db']['1']['slave']['1']['dbcharset'] = 'gbk';
可配置 禁用從數據庫的數據表, 表名字之間使用逗號分割服務器
* @example common_session, common_member 這兩個表僅從主服務器讀寫, 不使用從服務器 * $_config['db']['common']['slave_except_table'] = 'common_session, common_member';
可根據數據表進行分佈式部署session
@example 將 common_member 部署到第二服務器, common_session 部署在第三服務器, 則設置爲 $_config['db']['map']['common_member'] = 2; $_config['db']['map']['common_session'] = 3;
先看在抽象層(DB 類)初始化。在discuz_application的init時,調用_init_db()方法app
private function _init_db() { if($this->init_db) { $driver = function_exists('mysql_connect') ? 'db_driver_mysql' : 'db_driver_mysqli'; if(getglobal('config/db/slave')) { $driver = function_exists('mysql_connect') ? 'db_driver_mysql_slave' : 'db_driver_mysqli_slave'; } DB::init($driver, $this->config['db']); } }
抽象層DB的initcurl
public static function init($driver, $config) { self::$driver = $driver; self::$db = new $driver; self::$db->set_config($config); self::$db->connect(); }
根據配置文件中的讀寫分離來肯定底層dirver,若是支持從庫,則初始化db_dirver_mysql_salve。
db_driver_mysql中的table_name表是實現分庫的鑰匙,根據表名來肯定當前須要鏈接的數據庫服務器編號,並鏈接數據庫,存入鏈接池中,同時設置爲當前鏈接。
function table_name($tablename) { if(!empty($this->map) && !empty($this->map[$tablename])) { $id = $this->map[$tablename]; if(!$this->link[$id]) { $this->connect($id); } $this->curlink = $this->link[$id]; } else { $this->curlink = $this->link[1]; } return $this->tablepre.$tablename; }
這裏提一下,$this->link能夠實現數據庫只須要鏈接一次,不須要重複鏈接。
在寫入數據時,調用主庫,而讀取數據時調用從庫,見db_driver_mysql_salve, 該類覆蓋了table_name表,判斷該表是否須要讀寫分離,而且肯定該表的數據庫服務器組編號
public function table_name($tablename) { $this->tablename = $tablename; if(!$this->slaveexcept && $this->excepttables) { $this->slaveexcept = in_array($tablename, $this->excepttables, true); } $this->serverid = isset($this->map[$this->tablename]) ? $this->map[$this->tablename] : 1; return $this->tablepre.$tablename; }
重寫了query方法,判斷若是當前須要讀從庫則選擇並鏈接從庫。
public function query($sql, $silent = false, $unbuffered = false) { if(!(!$this->slaveexcept && strtoupper(substr($sql, 0 , 6)) === 'SELECT' && $this->_slave_connect())) { $this->_master_connect(); } $this->tablename = ''; $this->slaveexcept = false; return parent::query($sql, $silent, $unbuffered); }
db_dirver_mysql中其餘方法的用途。set_config方法,用於加載配置信息。content方法,根據服務器號鏈接相應數據庫,存入鏈接池,並設置爲當前鏈接。query使用當前鏈接執行sql語句。fetch_all,fetch_first,result_first定義經常使用操做。
db_dirver_mysql_slave中的其餘方法的用途。set_config執行父類方法,設置不須要讀寫分離的數據表。_choose_slave方法,在多臺從服務器的狀況下,根據權重選擇一臺從庫。
至此,數據庫的抽象層和底層基本清楚了。
業務邏輯層實際上是對抽象層的封裝。邏輯層實際上是全部的業務邏輯中涉及數據庫操做的部分。都能直接經過抽象層的DB::query或者DB::insert等方法來讀寫數據。不過一般數據表的操做都再次進行了封裝。數據表業務類在source/class/table目錄中。數據表操做類都繼承於discuz_table類,該在在source/calss/discuz目錄下。數據庫的數據緩存也在本層中實現。
__coustruct方法,設置表名和主鍵,設置是否緩存和緩存時間。該類主要封裝了一些經常使用數據庫操做,count,insert,delete,fetch,fetch_all等。數據緩存相關的方法:獲取指定鍵值的數據緩存fetch_cache,存儲單條記錄緩存store_cache,清除指定記錄緩存clear_cache,update_cache 更新指定單條記錄緩存,update_batch_cache 批量更新指定記錄緩存,reset_cache 重置指定數據的緩存,increase_cache 在原有緩存上追加更新緩存。緩存操做的方法由實際數據庫操做的方法調用,如fetch方法:
public function fetch($id, $force_from_db = false){ $data = array(); if(!empty($id)) { if($force_from_db || ($data = $this->fetch_cache($id)) === false) { $data = DB::fetch_first('SELECT * FROM '.DB::table($this->_table).' WHERE '.DB::field($this->_pk, $id)); if(!empty($data)) $this->store_cache($id, $data); } } return $data; } public function store_cache($id, $data, $cache_ttl = null, $pre_cache_key = null) { $ret = false; if($this->_allowmem) { if($pre_cache_key === null) $pre_cache_key = $this->_pre_cache_key; if($cache_ttl === null) $cache_ttl = $this->_cache_ttl; $ret = memory('set', $id, $data, $cache_ttl, $pre_cache_key); } return $ret; }
判斷是否緩存和緩存中是否存,若是是則直接返回緩存,而不讀數據庫。從數據庫中讀出數據後,存入緩存中。而是否從緩存中讀取時由外部參數來定義的,默認是可緩存。本方法中的緩存讀寫用經過memory來實現的,memory方法定義在function_core文件中,至於緩存的具體實現,須要在另一篇文章中說明了。
至此,discuzx3.1的數據庫層都有了,分析的有點凌亂。
原地址:
http://blog.csdn.net/jetxt/article/details/17242581