// 14 июля, 2010 // No Comments » // Без рубрики
В этой статье я хочу рассказать, как настроить Doctrine таким образом, чтобы использовать master-сервера для записи, а slave-сервера для чтения данных. Эта стратегия позволит добиться распределения нагрузки между серверами БД. Речь идет именно о нескольких серверах, на которых реплицируются базы, а не о ndbcluster, как некоторые могли бы подумать.
Итак, сначала мы должны сконфигурировать все дсотупные соединения в Doctrine.
$connections = array(
'master' => 'mysql://root:@master/dbname',
'slave_1' => 'mysql://root:@slave1/dbname',
'slave_2' => 'mysql://root:@slave2/dbname',
'slave_3' => 'mysql://root:@slave3/dbname',
'slave_4' => 'mysql://root:@slave4/dbname'
);
foreach ($connections as $name => $dsn) {
Doctrine_Manager::connection($dsn, $name);
}
Теперь, когда у нас есть один master-сервер, и 4 slave-сервера мы можем переписать классы Doctrine_Record и Doctrine_Query для добавления логики по разделению запросов для чтения и для записи. Все запросы на запись мы будем отправлять на master-сервер, а все запросы на чтение на случайно выбранный slave-сервер. См. статью про мульти-мастер репликацию
class MyQuery extends Doctrine_Query
{
// Since php doesn't support late static binding in 5.2 we need to override
// this method to instantiate a new MyQuery instead of Doctrine_Query
public static function create($conn = null)
{
return new MyQuery($conn);
}
public function preQuery()
{
// If this is a select query then set connection to one of the slaves
if ($this->getType() == Doctrine_Query::SELECT) {
$this->_conn = Doctrine_Manager::getInstance()->getConnection('slave_' . rand(1, 4));
// All other queries are writes so they need to go to the master
} else {
$this->_conn = Doctrine_Manager::getInstance()->getConnection('master');
}
}
}
Теперь DQL-запросы будут распределятсья между серверами, а что делать с сохранением записи? Мы сможем указать соединение дял записи, переписав класс Doctrine_Record, и используя его как базовый для наших моделей.
abstract class MyRecord extends Doctrine_Record
{
public function save(Doctrine_Connection $conn = null)
{
// If specific connection is not provided then lets force the connection
// to be the master
if ($conn === null) {
$conn = Doctrine_Manager::getInstance()->getConnection('master');
}
parent::save($conn);
}
}
Отлично! Теперь запросы на чтение буду траспределены между slave-серверами, а на запись пойдут на master-сервер. Вот несколько примеров работы запросоов и моделей:
class User extends MyRecord
{
public function setTableDefinition()
{
$this->setTableName('user');
$this->hasColumn('username', 'string', 255, array('type' => 'string', 'length' => '255'));
$this->hasColumn('password', 'string', 255, array('type' => 'string', 'length' => '255'));
}
}
// The save() method will happen on the master connection because it is a write
$user = new User();
$user->username = 'jwage';
$user->password = 'changeme';
$user->save();
// This query goes to one of the slaves because it is a read
$q = new MyQuery();
$q->from('User u');
$users = $q->execute();
print_r($users->toArray(true));
// This query goes to the master connection because it is a write
$q = new MyQuery();
$q->delete('User')
->from('User u')
->execute();