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:
parent
efd102aeee
commit
1fd222c441
|
@ -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);
|
||||
}
|
||||
/**
|
||||
* Возвращает массив пользователей, участвующих в разговоре
|
||||
|
|
|
@ -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___',
|
||||
),
|
||||
*/
|
||||
);
|
||||
/**
|
||||
* Настройка таблиц базы данных
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
7
engine/lib/external/DbSimple/Generic.php
vendored
7
engine/lib/external/DbSimple/Generic.php
vendored
|
@ -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,
|
||||
|
|
1
engine/lib/external/DbSimple/Mysql.php
vendored
1
engine/lib/external/DbSimple/Mysql.php
vendored
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
?>
|
142
engine/modules/database/DbSimpleWrapper.class.php
Normal file
142
engine/modules/database/DbSimpleWrapper.class.php
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue