1
0
Fork 0
mirror of https://github.com/Oreolek/ifhub.club.git synced 2024-06-16 14:50:48 +03:00

Поддержка балансировки между репликациями БД master-slave

This commit is contained in:
Mzhelskiy Maxim 2013-12-30 00:13:59 +07:00
parent efd102aeee
commit 1fd222c441
7 changed files with 195 additions and 9 deletions

View file

@ -370,7 +370,7 @@ class ModuleTalk_MapperTalk extends Mapper {
WHERE
talk_id = ?
{ AND user_id NOT IN (?a) }";
return $this->oDb->select($sql,$sTalkId,!is_null($aExcludeId) ? $aExcludeId : DBSIMPLE_SKIP);
return $this->oDb->query($sql,$sTalkId,!is_null($aExcludeId) ? $aExcludeId : DBSIMPLE_SKIP);
}
/**
* Возвращает массив пользователей, участвующих в разговоре

View file

@ -315,6 +315,18 @@ $config['db']['params']['user'] = 'root';
$config['db']['params']['pass'] = '';
$config['db']['params']['type'] = 'mysql';
$config['db']['params']['dbname'] = 'social';
$config['db']['params']['replication']['slave'] = array(
/*
array(
'host'=>'slave1',
'port'=>'3306',
'user'=>'root',
'pass'=>'',
'type'=>'mysql',
'dbname'=>'___db.params.dbname___',
),
*/
);
/**
* Настройка таблиц базы данных
*/

View file

@ -33,9 +33,9 @@ abstract class Mapper extends LsObject {
/**
* Передаем коннект к БД
*
* @param DbSimple_Generic_Database $oDb
* @param DbSimple_Generic_Database|object $oDb
*/
public function __construct(DbSimple_Generic_Database $oDb) {
public function __construct($oDb) {
$this->oDb = $oDb;
}

View file

@ -1140,7 +1140,11 @@ class DbSimple_Generic_Database extends DbSimple_Generic_LastError
{
die("This is protected constructor! Do not instantiate directly at ".__FILE__." line ".__LINE__);
}
public function getDsnParsed() {
return $this->_dsnParsed;
}
// Identifiers prefix (used for ?_ placeholder).
var $_identPrefix = '';
@ -1156,6 +1160,7 @@ class DbSimple_Generic_Database extends DbSimple_Generic_LastError
var $_cacher = null;
var $_placeholderArgs, $_placeholderNativeArgs, $_placeholderCache=array();
var $_placeholderNoValueFound;
protected $_dsnParsed=array();
/**
* When string representation of row (in characters) is greater than this,

View file

@ -33,6 +33,7 @@ class DbSimple_Mysql extends DbSimple_Generic_Database
function DbSimple_Mysql($dsn)
{
$p = DbSimple_Generic::parseDSN($dsn);
$this->_dsnParsed=$p;
if (!is_callable('mysql_connect')) {
return $this->_setLastError("-1", "MySQL extension is not loaded", "mysql_connect");
}

View file

@ -16,6 +16,7 @@
*/
require_once(Config::Get('path.root.engine').'/lib/external/DbSimple/Generic.php');
require_once('DbSimpleWrapper.class.php');
/**
* Модуль для работы с базой данных
* Создаёт объект БД библиотеки DbSimple Дмитрия Котерова
@ -35,6 +36,9 @@ class ModuleDatabase extends Module {
*/
protected $aInstance=array();
protected $aReplicaInstanceSlave=array();
protected $aReplicaMasterByTable=array();
/**
* Инициализация модуля
*
@ -42,6 +46,20 @@ class ModuleDatabase extends Module {
public function Init() {
}
public function GetReplicaInstanceSlaveByHash($sHash) {
return isset($this->aReplicaInstanceSlave[$sHash]) ? $this->aReplicaInstanceSlave[$sHash] : null;
}
public function SetReplicaInstanceSlave($sHash,$oReplicaInstanceSlave) {
return $this->aReplicaInstanceSlave[$sHash]=$oReplicaInstanceSlave;
}
public function GetReplicaMasterByTable() {
return $this->aReplicaMasterByTable;
}
public function SetReplicaMasterByTable($aReplicaMasterByTable) {
$this->aReplicaMasterByTable=$aReplicaMasterByTable;
}
/**
* Получает объект БД
*
@ -55,7 +73,11 @@ class ModuleDatabase extends Module {
if (is_null($aConfig)) {
$aConfig = Config::Get('db.params');
}
$sDSN=$aConfig['type'].'wrapper://'.$aConfig['user'].':'.$aConfig['pass'].'@'.$aConfig['host'].':'.$aConfig['port'].'/'.$aConfig['dbname'];
$sParams='';
if (isset($aConfig['params']) and is_array($aConfig['params'])){
$sParams='?'.http_build_query($aConfig['params'],'','&');
}
$sDSN=$aConfig['type'].'wrapper://'.$aConfig['user'].':'.$aConfig['pass'].'@'.$aConfig['host'].':'.$aConfig['port'].'/'.$aConfig['dbname'].$sParams;
/**
* Создаём хеш подключения, уникальный для каждого конфига
*/
@ -85,14 +107,15 @@ class ModuleDatabase extends Module {
* считайте это костылём
*/
$oDbSimple->query("set character_set_client='utf8', character_set_results='utf8', collation_connection='utf8_bin' ");
$oWrapper=new ModuleDatabase_DbSimpleWrapper($oDbSimple);
/**
* Сохраняем коннект
*/
$this->aInstance[$sDSNKey]=$oDbSimple;
$this->aInstance[$sDSNKey]=$oWrapper;
/**
* Возвращаем коннект
*/
return $oDbSimple;
return $oWrapper;
}
}
/**
@ -103,7 +126,7 @@ class ModuleDatabase extends Module {
public function GetStats() {
$aQueryStats=array('time'=>0,'count'=>-1); // не считаем тот самый костыльный запрос, который устанавливает настройки DB соединения
foreach ($this->aInstance as $oDb) {
$aStats=$oDb->_statistics;
$aStats=$oDb->getStatistics();
$aQueryStats['time']+=$aStats['time'];
$aQueryStats['count']+=$aStats['count'];
}
@ -280,13 +303,16 @@ function databaseLogger($db, $sql) {
*/
$caller = $db->findLibraryCaller();
$msg=print_r($sql,true);
$aDsnParsed=$db->getDsnParsed();
$sDbPrefix='[DB:'.(isset($aDsnParsed['path']) ? $aDsnParsed['path'] : '').']';
/**
* Получаем ядро и сохраняем в логе SQL запрос
*/
$oEngine=Engine::getInstance();
$sOldName=$oEngine->Logger_GetFileName();
$oEngine->Logger_SetFileName(Config::Get('sys.logs.sql_query_file'));
$oEngine->Logger_Debug($msg);
$oEngine->Logger_Debug($sDbPrefix.$msg);
$oEngine->Logger_SetFileName($sOldName);
}
?>

View file

@ -0,0 +1,142 @@
<?php
/**
* Обертка для объекта коннекта к БД
* Позволяет определить тип запроса и выбрать нужный инстанс реплики.
* Изначально объект $this->oDbSimple всегда мастер, в момент запроса к slave мы его заменяем, а после запроса возвращаем обратно.
*
* Общая логика:
* 1. определяем тип запроса - чтение или запись
* 2. чтение таблиц - если еще не выбран инстанс slave, то делаем его выборку. При условии, что еще не выбран инстанс master(не было запроса на запись в эти таблицы)
* 3. запись в таблицу - если еще не выбран инстанс master, то делаем его выборку, далее запрещаем его менять на slave(последующие запросы к этим таблицам в рамках сеанса отправляем только на него)
*/
class ModuleDatabase_DbSimpleWrapper {
const REPLICA_TYPE_MASTER=1;
const REPLICA_TYPE_SLAVE=2;
protected $oDbSimple=null;
public function __construct($oDbSimple) {
$this->oDbSimple=$oDbSimple;
}
public function __getDbSimple() {
return $this->oDbSimple;
}
public function __getTables($sSql) {
if (preg_match_all('#((prefix_)|(\?_))([a-z0-9_]+)#i',$sSql,$aMatch)) {
return $aMatch[4];
}
return array('_unknown_');
}
/**
* Проверяет необходимость запроса на slave и возврашает его
*
* @param $aTables
* @param $aDsn
*
* @return null
*/
public function __getInstanceSlave($aTables,$aDsn) {
if (!isset($aDsn['replication']['slave']) or !count($aDsn['replication']['slave'])) {
return null;
}
$sHash=md5(serialize($aDsn));
/**
* Смотрим на необходимость мастера для таблицы
*/
$aReplicaMasterByTable=Engine::getInstance()->Database_GetReplicaMasterByTable();
foreach($aTables as $sTable) {
if (isset($aReplicaMasterByTable[$sHash][$sTable])) {
return null;
}
}
if ($oSlave=Engine::getInstance()->Database_GetReplicaInstanceSlaveByHash($sHash)) {
return $oSlave;
}
$iKey=array_rand($aDsn['replication']['slave']);
$oSlave=Engine::getInstance()->Database_GetConnect($aDsn['replication']['slave'][$iKey]);
$oSlave=$oSlave->__getDbSimple();
Engine::getInstance()->Database_SetReplicaInstanceSlave($sHash,$oSlave);
return $oSlave;
}
public function __select($args,$sMethod) {
$aTables=$this->__getTables(isset($args[0]) ? $args[0] : '');
if ($oDbSlave=$this->__getInstanceSlave($aTables,$this->oDbSimple->getDsnParsed())) {
$oDbOld=$this->oDbSimple;
$this->oDbSimple=$oDbSlave;
}
$mResult=call_user_func_array(array($this->oDbSimple,$sMethod),$args);
if (isset($oDbOld)) {
$this->oDbSimple=$oDbOld;
}
return $mResult;
}
public function query() {
$args = func_get_args();
$aDsn=$this->oDbSimple->getDsnParsed();
if (isset($aDsn['replication']['slave']) and count($aDsn['replication']['slave'])) {
$aTables=$this->__getTables(isset($args[0]) ? $args[0] : '');
$sHash=md5(serialize($aDsn));
$aReplicaMasterByTable=Engine::getInstance()->Database_GetReplicaMasterByTable();
foreach($aTables as $sTable) {
if (!isset($aReplicaMasterByTable[$sHash][$sTable])) {
$aReplicaMasterByTable[$sHash][$sTable]=true;
}
}
Engine::getInstance()->Database_SetReplicaMasterByTable($aReplicaMasterByTable);
}
return call_user_func_array(array($this->oDbSimple,'query'),$args);
}
public function select() {
$args = func_get_args();
return $this->__select($args,__FUNCTION__);
}
public function selectRow() {
$args = func_get_args();
return $this->__select($args,__FUNCTION__);
}
public function selectCol() {
$args = func_get_args();
return $this->__select($args,__FUNCTION__);
}
public function selectCell() {
$args = func_get_args();
return $this->__select($args,__FUNCTION__);
}
public function selectPage(&$total, $query) {
$args = func_get_args();
array_shift($args);
$total = true;
$aTables=$this->__getTables(isset($args[0]) ? $args[0] : '');
if ($oDbSlave=$this->__getInstanceSlave($aTables,$this->oDbSimple->getDsnParsed())) {
$oDbOld=$this->oDbSimple;
$this->oDbSimple=$oDbSlave;
}
$mResult=$this->oDbSimple->_query($args, $total);
if (isset($oDbOld)) {
$this->oDbSimple=$oDbOld;
}
return $mResult;
}
public function __call($sName,$aArgs) {
return call_user_func_array(array($this->oDbSimple,$sName),$aArgs);
}
}