前段時間為SNS產品做了架構設計,在程式架構方面做了不少相關的壓力測試,最終選定了YiiFramework,至於為什麼沒選用公司內部的 PHP架構,其實理由很充分,公司的架構雖然是“前輩”們辛苦的積累,但畢竟不夠成熟,沒有大型項目的曆練,猶如一個涉世未深的年輕小夥。Yii作為一個 頗有名氣開源產品,必定有很多人在使用,意味著有一批人在維護,而且在這之前,我也使用Yii開發過大型項目,Yii的設計模式和它的易擴充特性足以堪當 重任。
SNS同一般的社交產品不同的就是它最終要承受大並發和大資料量的考驗,架構設計時就要考慮這些問題, web分布式、負載平衡、分布式檔案儲存體、MySQL分布式或讀寫分離、NoSQL以及各種緩衝,這些都是必不可少的應用方案,本文所講的就是MySQL 分庫和主從讀寫分離在Yii的配置和使用。
Yii預設是不支援讀寫分離的,我們可以利用Yii的事件驅動模式來實現MySQL的讀寫分離。
Yii提供了一個強大的CActiveRecord資料庫操作類,通過重寫getDbConnection方法來實現資料庫的切換,然後通過事件 beforeSave、beforeDelete、beforeFind 來實現讀寫伺服器的切換,還需要兩個設定檔dbconfig和modelconfig分別設定資料庫主從伺服器和model所對應的資料庫名稱,附代碼
DBConfig.php
| 代碼如下 |
複製代碼 |
<?php return array( ’passport’ => array( ’write’ => array( ’class’ => ‘CDbConnection’, ’connectionString’ => ‘mysql:host=10.1.39.2;dbname=db1′, ’emulatePrepare’ => true, //’enableParamLogging’ => true, ’enableProfiling’ => true, ’username’ => ‘root’, ’password’ => ”, ’charset’ => ‘utf8′, ‘schemaCachingDuration’=>3600, ), ’read’ => array( array( ’class’ => ‘CDbConnection’, ’connectionString’ => ‘mysql:host=10.1.39.3;dbname=db1, ’emulatePrepare’ => true, //’enableParamLogging’ => true, ’enableProfiling’ => true, ’username’ => ‘root’, ’password’ => ”, ’charset’ => ‘utf8′, ‘schemaCachingDuration’=>3600, ), array( ’class’ => ‘CDbConnection’, ’connectionString’ => ‘mysql:host=10.1.39.4;dbname=db3′, ’emulatePrepare’ => true, //’enableParamLogging’ => true, ’enableProfiling’ => true, ’username’ => ‘root’, ’password’ => ”, ’charset’ => ‘utf8′, ‘schemaCachingDuration’=>3600, ), ), ), );
|
ModelConfig.php
| 代碼如下 |
複製代碼 |
<?php return array( //key為資料庫名稱,value為Model ’passport’ => array(‘User’,’Post’), ‘microblog’ => array(‘…’), ); ?>
|
ActiveRecord.php
| 代碼如下 |
複製代碼 |
/** * 基於CActiveRecord類的封裝,實現多庫和主從讀寫分離 * 所有Model都必須繼承些類. * * @author atao<lnbalife@126.com> */ class ActiveRecord extends CActiveRecord { //model配置 public $modelConfig = ”; //資料庫配置 public $dbConfig = ”; //定義一個多資料庫集合 static $dataBase = array(); //當前資料庫名稱 public $dbName = ”; //定義庫類型(讀或寫) public $dbType = ‘read’; //’read’ or ‘write’ /** * 在原有基礎上添加了一個dbname參數 * @param string $scenario Model的應用情境 * @param string $dbname 資料庫名稱 */ public function __construct($scenario=’insert’, $dbname = ”) { if (!empty($dbname)) $this->dbName = $dbname; parent::__construct($scenario); } /** * 重寫父類的getDbConnection方法 * 多庫和主從都在這裡切換 */ public function getDbConnection() { //如果指定的資料庫物件存在則直接返回 if (self::$dataBase[$this->dbName]!==null) return self::$dataBase[$this->dbName]; if ($this->dbName == ‘db’){ self::$dataBase[$this->dbName] = Yii::app()->getDb(); }else{ $this->changeConn($this->dbType); } if(self::$dataBase[$this->dbName] instanceof CDbConnection){ self::$dataBase[$this->dbName]->setActive(true); return self::$dataBase[$this->dbName]; } else throw new CDbException(Yii::t(‘yii’,’Model requires a “db” CDbConnection application component.’)); } /** * 擷取設定檔 * @param unknown_type $type * @param unknown_type $key */ private function getConfig($type=”modelConfig”,$key=”){ $config = Yii::app()->params[$type]; if($key) $config = $config[$key]; return $config; } /** * 擷取資料庫名稱 */ private function getDbName(){ if($this->dbName) return $this->dbName; $modelName = get_class($this->model()); $this->modelConfig = $this->getConfig(‘modelConfig’); //擷取model所對應的資料庫名 if($this->modelConfig)foreach($this->modelConfig as $key=>$val){ if(in_array($modelName,$val)){ $dbName = $key; break; } } return $dbName; } /** * 切換資料庫連接 * @param unknown_type $dbtype */ protected function changeConn($dbtype = ‘read’){ if($this->dbType == $dbtype && self::$dataBase[$this->dbName] !== null) return self::$dataBase[$this->dbName]; $this->dbName = $this->getDbName(); if(Yii::app()->getComponent($this->dbName.’_’.$dbtype) !== null){ self::$dataBase[$this->dbName] = Yii::app()->getComponent($this->dbName.’_’.$dbtype); return self::$dataBase[$this->dbName]; } $this->dbConfig = $this->getConfig(‘dbConfig’,$this->dbName); //跟據類型取對應的配置(從庫是隨機值) if($dbtype == ‘write’){ $config = $this->dbConfig[$dbtype]; }else{ $slavekey = array_rand($this->dbConfig[$dbtype]); $config = $this->dbConfig[$dbtype][$slavekey]; } //將資料庫配置加到component中 if($dbComponent = Yii::createComponent($config)){ Yii::app()->setComponent($this->dbName.’_’.$dbtype,$dbComponent); self::$dataBase[$this->dbName] = Yii::app()->getComponent($this->dbName.’_’.$dbtype); $this->dbType = $dbtype; return self::$dataBase[$this->dbName]; } else throw new CDbException(Yii::t(‘yii’,’Model requires a “changeConn” CDbConnection application component.’)); } /** * 儲存資料前選擇 主 資料庫 */ protected function beforeSave(){ parent::beforeSave(); $this->changeConn(‘write’); return true; } /** * 刪除資料前選擇 主 資料庫 */ protected function beforeDelete(){ parent::beforeDelete(); $this->changeConn(‘write’); return true; } /** * 讀取資料選擇 從 資料庫 */ protected function beforeFind(){ parent::beforeFind(); $this->changeConn(‘read’); return true; } /** * 擷取master庫對象 */ public function dbWrite(){ return $this->changeConn(‘write’); } /** * 擷取slave庫對象 */ public function dbRead(){ return $this->changeConn(‘read’); } } |
這是我寫好的類,放在components檔案夾裡,然後所有的Model都繼承ActiveRecord類就可以實現多庫和主從讀寫分離了,至於如何支援原生的SQL也同時使用讀寫分離,此類都已經實現,使用方法會在以後的文章介紹。