對於一些訪問量比較大的項目,我們常常采用數據庫主從的方式進行讀寫分離,以分流用戶操作,實現負載均衡。因此網上查找了相關的信息,做一個總結。下面的概念部分內容摘自百科或網絡PPT,結尾的代碼源自此次項目。
首先,因為之前沒有做過類似的功能,需要在概念上進行了解:
負載均衡
負載均衡(Load Balance):將負載(工作任務)進行平衡、分攤到多個操作單元上進行執行,從而共同完成工作任務。主要分為兩種類型:
1.集群(clustering)
單個重負載的運算分擔到多台節點設備上做並行處理,每個節點設備處理結束後,將結果匯總,返回給用戶,使系統處理能力得到大幅度提高。
2.分流
大量的並發訪問或數據流量分擔到多台節點設備上分別處理,減少用戶等待響應的時間,這主要針對Web服務器、FTP服務器、企業關鍵應用服務器等網絡應用。主從架構就是這種類型的負載均衡。
主從架構的好處
1.負載均衡(讀寫分離,提升數據處理效率)
2.高可用和故障轉移的能力(數據分布,穩定性提升。主服務器出現故障,還可以用從服務器支撐)
3.備份(本身不能備份,但是能提供一個備份機,便於實現數據庫的容災、備份、恢復等操作)
4.數據一致性,避免沖突
5.測試Mysql升級
Mysql的復制功能
1:支持一主多從機制。數據通過主服務器復制到從服務器上。
2:支持多級結構。主從,從從,主主(互為主從)。
3:支持過濾功能(可以只復制主服務器上的部分數據,而非全部)。
復制的類型
1. 基於語句的復制:在主服務器上執行的SQL語句,在從服務器上執行同樣的SQL語句。Mysql默認采用基於語句的復制,效率比較高。
2. 基於行的復制:把改變的內容復制過去,而不是把命令在從服務器上執行一遍(mysql5.0開始支持)。
3. 混合類型的復制:默認采用基於語句的復制。發現基於語句無法精確復制時,就會采用基於行的復制
相應的二進制日志也有三種:
1:STATEMENT
2:ROW
3:MIXED
服務器結構的要求
1:主從服務器中的表可以使用不同的表類型。另外:一台主服務器同時帶多台從服務器,會影響其性能,可以拿出一台服務器作為從服務器代理,使用BLOCKHOLE表類型。只記錄日志,不寫數據,由它帶多台服務器,從而提升性能。
2:主從服務器中的表可以使用不同的字段類型。
3:主從服務器中的表可以使用不同的索引。主服務器主要用來寫操作,所以除了主鍵和唯一索引等保證數據關系的索引一般都可以不加;從服務器一般用來讀操作,所以可以針對查詢特征設置索引。甚至:不同的從服務器可以針對不同的查詢設置不同的索引。
復制流程
1:master服務器將改變記錄到二進制日志文件(binary log)中,這些記錄叫做二進制日志事件(binary log events)
2:slave服務器將master的binary log events拷貝到他的中繼日志(relay log)
3:slave重做中繼日志的事件,將改變反映到它自己的數據。
PHP代碼實現
1.服務器連接配置文件
如有多態主|從服務器,那麼只需數字往下遞增即可。
[php]
[database]
dbname = "vis_db"
charset = "utf8"
;主
servers.0.master = true
servers.0.adapter = "MYSQLI"
servers.0.host = "vis_db"
servers.0.username = "vis"
servers.0.password = "vis"
;從
servers.1.master = false
servers.1.adapter = "MYSQLI"
servers.1.host = "vis_mmc"
servers.1.username = "vis"
servers.1.password = "vis"
2.數據庫操作類代碼
根據用戶IP取余後,確定連接哪台服務器上的數據庫。
項目中使用了Zend Framework框架。
[php]
<?php
/**
* 數據庫工廠類
*
* @create 2012-05-29
* @note:該類用於創建各種配置參數的Zend_Db_Adapter實例
*/
include_once 'lib/getRequestIP.php';
class Free_Db_Factory
{
/**
* Zend_Db_Adapter實例數組
*
* @var array
*/
protected static $_dbs = array();
protected function __construct($sName)
{
try {
$params = $this->_getDbConfig($sName);
self::$_dbs[$sName] = Zend_Db::factory($params['adapter'], $params);
} catch (Exception $e) {
if (DEBUG) {
echo $e->getMessage();
}
exit;
}
}
/**
* 獲取Zend_Db_Adapter實例
* @return Zend_Db_Adapter
*/
public static function getDb($sName)
{
if (emptyempty($sName)) {
exit;
}
if (!isset(self::$_dbs[$sName])) {
new self($sName);
}
return self::$_dbs[$sName];
}
/**
* 獲取數據庫的配置
*/
private function _getDbConfig($sName)
{
$configArr = array();
$dbConfig = Zend_Registry::get('db')->database->toArray();
$serverConfigs = $dbConfig['servers'];
$masters = array();
$slaves = array();
foreach ($serverConfigs as $value) {
if (!isset($value['master'])) {
continue;
}
if (true == $value['master']) {
$masters[] = $value;
}
if (false == $value['master']) {
$slaves[] = $value;
}
}
$masterNum = count($masters);
$slaveNum = count($slaves);
$requestIP = $this->_getRequestIP();
switch ($sName) {
case 'master' :
if ($masterNum > 1) {
$configArr = $masters[$requestIP % $masterNum];
} else {
$configArr = $masters[0];
}
break;
case 'slave' :
if ($slaveNum > 1) {
$configArr = $slaves[$requestIP % $slaveNum];
} else {
$configArr = $slaves[0];
}
break;
default :
break;
}
if (emptyempty($configArr)) {
return array();
}
$configArr['dbname'] = $dbConfig['dbname'];
$configArr['charset'] = $dbConfig['charset'];
return $configArr;
}
/**
* 獲取請求IP
*/
private function _getRequestIP()
{
$ip = getRequestIP(true);
return sprintf('%u', ip2long($ip));
} www.2cto.com
/**
* 析構Zend_Db_Adapter實體(由於有些請求很耗時間,這段時間可能會讓數據庫超時)
*/
public static function destructDb($sName = null)
{
if (null === $sName) {
self::$_dbs = null;
} else {
unset(self::$_dbs[$sName]);
}
}
}
調用代碼時,傳入一個標志,確定是操作主還是從數據庫即可:
[php]
$oSlaveDb = Free_Db_Factory::getDb('slave');