mirror of
https://github.com/Oreolek/ifhub.club.git
synced 2024-06-26 03:30:48 +03:00
Поддержка балансировки между репликациями БД master-slave
This commit is contained in:
parent
efd102aeee
commit
1fd222c441
|
@ -370,7 +370,7 @@ class ModuleTalk_MapperTalk extends Mapper {
|
||||||
WHERE
|
WHERE
|
||||||
talk_id = ?
|
talk_id = ?
|
||||||
{ AND user_id NOT IN (?a) }";
|
{ 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']['pass'] = '';
|
||||||
$config['db']['params']['type'] = 'mysql';
|
$config['db']['params']['type'] = 'mysql';
|
||||||
$config['db']['params']['dbname'] = 'social';
|
$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;
|
$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__);
|
die("This is protected constructor! Do not instantiate directly at ".__FILE__." line ".__LINE__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDsnParsed() {
|
||||||
|
return $this->_dsnParsed;
|
||||||
|
}
|
||||||
|
|
||||||
// Identifiers prefix (used for ?_ placeholder).
|
// Identifiers prefix (used for ?_ placeholder).
|
||||||
var $_identPrefix = '';
|
var $_identPrefix = '';
|
||||||
|
|
||||||
|
@ -1156,6 +1160,7 @@ class DbSimple_Generic_Database extends DbSimple_Generic_LastError
|
||||||
var $_cacher = null;
|
var $_cacher = null;
|
||||||
var $_placeholderArgs, $_placeholderNativeArgs, $_placeholderCache=array();
|
var $_placeholderArgs, $_placeholderNativeArgs, $_placeholderCache=array();
|
||||||
var $_placeholderNoValueFound;
|
var $_placeholderNoValueFound;
|
||||||
|
protected $_dsnParsed=array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When string representation of row (in characters) is greater than this,
|
* 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)
|
function DbSimple_Mysql($dsn)
|
||||||
{
|
{
|
||||||
$p = DbSimple_Generic::parseDSN($dsn);
|
$p = DbSimple_Generic::parseDSN($dsn);
|
||||||
|
$this->_dsnParsed=$p;
|
||||||
if (!is_callable('mysql_connect')) {
|
if (!is_callable('mysql_connect')) {
|
||||||
return $this->_setLastError("-1", "MySQL extension is not loaded", "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(Config::Get('path.root.engine').'/lib/external/DbSimple/Generic.php');
|
||||||
|
require_once('DbSimpleWrapper.class.php');
|
||||||
/**
|
/**
|
||||||
* Модуль для работы с базой данных
|
* Модуль для работы с базой данных
|
||||||
* Создаёт объект БД библиотеки DbSimple Дмитрия Котерова
|
* Создаёт объект БД библиотеки DbSimple Дмитрия Котерова
|
||||||
|
@ -35,6 +36,9 @@ class ModuleDatabase extends Module {
|
||||||
*/
|
*/
|
||||||
protected $aInstance=array();
|
protected $aInstance=array();
|
||||||
|
|
||||||
|
protected $aReplicaInstanceSlave=array();
|
||||||
|
protected $aReplicaMasterByTable=array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Инициализация модуля
|
* Инициализация модуля
|
||||||
*
|
*
|
||||||
|
@ -42,6 +46,20 @@ class ModuleDatabase extends Module {
|
||||||
public function Init() {
|
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)) {
|
if (is_null($aConfig)) {
|
||||||
$aConfig = Config::Get('db.params');
|
$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' ");
|
$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() {
|
public function GetStats() {
|
||||||
$aQueryStats=array('time'=>0,'count'=>-1); // не считаем тот самый костыльный запрос, который устанавливает настройки DB соединения
|
$aQueryStats=array('time'=>0,'count'=>-1); // не считаем тот самый костыльный запрос, который устанавливает настройки DB соединения
|
||||||
foreach ($this->aInstance as $oDb) {
|
foreach ($this->aInstance as $oDb) {
|
||||||
$aStats=$oDb->_statistics;
|
$aStats=$oDb->getStatistics();
|
||||||
$aQueryStats['time']+=$aStats['time'];
|
$aQueryStats['time']+=$aStats['time'];
|
||||||
$aQueryStats['count']+=$aStats['count'];
|
$aQueryStats['count']+=$aStats['count'];
|
||||||
}
|
}
|
||||||
|
@ -280,13 +303,16 @@ function databaseLogger($db, $sql) {
|
||||||
*/
|
*/
|
||||||
$caller = $db->findLibraryCaller();
|
$caller = $db->findLibraryCaller();
|
||||||
$msg=print_r($sql,true);
|
$msg=print_r($sql,true);
|
||||||
|
$aDsnParsed=$db->getDsnParsed();
|
||||||
|
$sDbPrefix='[DB:'.(isset($aDsnParsed['path']) ? $aDsnParsed['path'] : '').']';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получаем ядро и сохраняем в логе SQL запрос
|
* Получаем ядро и сохраняем в логе SQL запрос
|
||||||
*/
|
*/
|
||||||
$oEngine=Engine::getInstance();
|
$oEngine=Engine::getInstance();
|
||||||
$sOldName=$oEngine->Logger_GetFileName();
|
$sOldName=$oEngine->Logger_GetFileName();
|
||||||
$oEngine->Logger_SetFileName(Config::Get('sys.logs.sql_query_file'));
|
$oEngine->Logger_SetFileName(Config::Get('sys.logs.sql_query_file'));
|
||||||
$oEngine->Logger_Debug($msg);
|
$oEngine->Logger_Debug($sDbPrefix.$msg);
|
||||||
$oEngine->Logger_SetFileName($sOldName);
|
$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