PHP Profi

Простое разделение чтения и записи с помощью MySQLnd в PHP Перевод Серия 

MySQL всегда был основной базой данных для работы с PHP, так сложилось исторически, практически с самого начала. Конечно, некоторые используют PostgreSQL, SQL Server, или Oracle, но для интернет-проектов MySQL обычно является предпочтительной реляционной БД.

Это было связано, в основном, с легкостью внедрения и применения MySQL. Libmysqlclient шел в комплекте с PHP до тех пор, пока эта библиотека не была повторно лицензирована под GPL. После чего она была удалена из-за невозможности распространения вместе с PHP.

Это сделало процесс сборки для PHP немного более сложным, потому что теперь Libmysqlclient должна была быть доступна на хостинге.

Учитывая широкую распространенность PHP, а также тот факт, что PHP был самым распространенным языком, использующим MySQL, Oracle (а позже и Sun), понимая неприятность ситуации, пришли к соглашению: они создали MySQL Native Driver - лицензированное дополнение для PHP, которое позволяет получить доступ к MySQL без libmysqlclient.

MySQL Native Driver (mysqlnd) был добавлен в PHP 5.3 и стал библиотекой по умолчанию с версии 5.4 (хотя вы все еще можете скомпилировать и использовать libmysqlclient). У него есть некоторые новые возможности, большая производительность и лучшее использование памяти, чем libmysqlclient.

Из руководства MySQL:

Библиотека mysqlnd написана на C в виде расширения PHP. Она использует возможности PHP: управление памятью, потоки (ввод-вывод) и процедуры для работы со строками. Mysqlnd использует PHP-шное управление памятью, что дает возможность использовать ограничения памяти вдобавок с её экономией с помощью read-only переменных (копирование при записи).

Дополнительно библиотека может использовать плагины, которые можно найти в сети.

Установка

Чтобы установить, просто скомпилируйте одно из трех расширений MySQL. В каждом конкретном случае не указывайте явный путь до libmysqlclient.

Три расширения:

  • ext/pdo_mysql (начиная с PHP 5.1)
  • ext/mysqli (начиная с PHP 5.0)
  • ext/mysql (начиная с PHP 2.0, объявлена устаревшей (deprecated) начиная с PHP 5.6)

Примечание: если вы установите ext/mysql или ext/mysqli, ext/pdo_mysql станет доступно автоматически.

Вы можете указать расширение, выбрав один или несколько следующих опций конфигурации:

  • --with-mysql
  • --with-mysqli
  • --with-pdo-mysql

Если вы используете Debian или Ubuntu, то можете легко установить пакет php5-mysqlnd с помощью командной строки:

$ sudo apt-get install php5-mysqlnd

Таким образом вы удалите пакет php5-mysql, в основе которого лежит libmysqlclient, взамен получите предлагаемый со всеми тремя расширениями.

Плагины для MySQL Native Driver

Помимо преимуществ в производительности, большим преимуществом mysqlnd являются плагины. Эти плагины доступны через PECL, и могут быть легко установлены с помощью:

$ pecl install mysqlnd_< name >

Доступные (и стабильные) плагины:

  • mysqlnd_memcache: переводит SQL для использования memcache-protocol MySQL 5.6, который поддерживает демона NoSQL
  • mysqlnd_ms: выполняет разделение чтения и записи между ведущим (master) и ведомым (slave) серверами с простой балансировкой нагрузки
  • mysqlnd_qc: Добавляет простой кэш запросов в PHP
  • mysqlnd_uh: Позволяет писать mysqlnd плагины в PHP

Поскольку эти плагины для самого mysqlnd, они заработают для всех трех расширений.

Разделение чтения и записи

Наиболее полезный плагин - это mysqlnd_ms, от master/slave - ведущий/ведомый. Этот плагин позволяет прозрачно, хотя и несколько примитивно, разделить потоки чтения и записи между разными серверами.

Настройка

После установки плагина через PECL, необходимо настроить php.ini и конфигурационный файл mysqlnd_ms.

В файле php.ini (или в mysqlnd_ms.ini в Debian-подобных системах):

extension=mysqlnd_ms.so
mysqlnd_ms.enable=1
mysqlnd_ms.config_file=/путь/к/mysqlnd_ms.json

Затем вам надо создать файл и назвать его mysqlnd_ms.json. Этот файл определяет ведущий (master) и ведомый (slave) сервера, а также стратегии разделения чтения, записи и балансировки нагрузки.

Какие настройки выставлять зависит от вашей топологии репликации.

Простейшая конфигурация файла с одним ведущим и одним ведомым:

{
    "appname": {
        "master": {
            "master_0": {
                "host": "master.mysql.host",
                "port": "3306",
                "user": "dbuser",
                "password": "dbpassword",
                "db": "dbname"
            }
        },
        "slave": {
            "slave_0": {
                "host": "slave.mysql.host",
                "port": "3306"
                "user": "dbuser",
                "password": "dbpassword",
                "db": "dbname"
            },
        }
    }
}

Единственная обязательная настройка - это host. Все остальные опциональны.

Распределение нагрузки

Дополнительно mysqlnd_ms может сделать простое распределение нагрузки одним из нескольких путей:

  • random (случайный) — выбирается случайный ведомый для каждого запроса на чтение
  • random once (случайный единственный) — выбирается случайный ведомый для первого запроса на чтение и он же продолжает использоваться для всех последующих
  • round robin (циклический алгоритм) — в предопределенном порядке выбирается ведомый для каждого запроса на чтение
  • user (пользовательский) — вызов ведомого для каждого запроса определяется пользовательскими настройками

Важно понять, что пока вы не выбирете последний вариант и сами его не настроите, каждое request-обращение будет распределять нагрузку закрыто. Так, циклический алгоритм (round robin) применяется к каждому запросу в пределах одного и того же обращения и это не значит, что в последовательной цепочке обращений на каждое будет выделен один сервер, как можно было бы ожидать от фактических настроек по распределению у железа или софта.

Имея это в виду, я бы рекомендовал не использовать этот плагин в качестве распределения нагрузки. Вместо этого для ваших ведомых используйте балансировщик, например, haproxy, и просто укажите путь к его конфигу в качестве единственного ведомого.

Маршрутизация запросов

По умолчанию mysqlnd_ms будет распределять запросы так: начинающиеся с SELECT на ведомые сервера, а все остальные - на ведущий.

Это одновременно и хорошо, и плохо. Такое прозрачное распределение означает, что вам не придется делать никаких изменений в коде. Но в тоже время это примитивный подход, ибо не происходит никакого анализа, является ли запрос на самом деле запросом на чтение.

Обычный SELECT-запрос не попадет к ведущему, но и запрос вида SELECT ... INTO (то есть на запись) также к нему не попадет, а полетит прямиком на ведомый сервер, что может обернуться катастрофой.

К счастью, плагин включает в себя возможность указывать, что какому серверу послать. Это делается путем добавления к запросу одной из трех констант:

  • MYSQLND_MS_MASTER_SWITCH — запрос уйдет ведущему серверу
  • MYSQLND_MS_SLAVE_SWITCH — запрос уйдет ведомому серверу
  • MYSQLND_MS_LAST_USED_SWITCH — запрос уйдет серверу, который использовался последним

Эти три константы всего лишь представления строк: "ms=master", "ms=slave", и "ms=last_used" соответсвенно. Однако, в будущем строки могут быть изменены, поэтому следует использовать именно константы.

Чтобы использовать указатель, добавьте перед запросом комментарий, куда именно следует его отправить дальше. Самый простой способ сделать это - использовать sprintf(), которая использует описатели преобразования (в данном случае, %s - строковый описатель).

Для примера, пришлем SELECT-запрос ведущему серверу:

$sql = sprintf("/*%s*/ SELECT * FROM table_name;", MYSQLND_MS_MASTER_SWITCH);

Или, пошлем не-SELECT-запрос ведомому:

$sql = sprintf("/*%s*/ CREATE TEMPORARY TABLE `temp_table_name` SELECT * FROM table_name;", MYSQLND_MS_SLAVE_SWITCH);

Последний указатель позволяет вам быть уверенным, что будет использовано то же подключение, что и для предыдущего запроса. Это особенно полезно, когда нужно убедиться, что вы переключились на ведущий и будете читать с него после того, как информация была изменена, но, потенциально, репликация еще не сработала. Другой случай: транзакции, в которых есть запросы и на чтение, и на запись.

if ($request->isPost() && $form->isValid()) {
    $user>setValues($form->getValues());
    $user->save();
}

$sql = sprintf("/*%s*/ SELECT * FROM user_session WHERE user_id = :user_id", MYSQLND_LAST_USED_SWITCH);

Ведущему будет уходить запрос тогда (и только тогда!), когда он был использован в предыдущий раз. В данном конкретном примере это случится, если произошло обновление данных пользователя (с помощью $user->save()).

Заключение

Плагин mysqlnd_ms невероятно полезен, особенно, когда вы хотите добавить распределение чтения и записи на большие написанные поколениями программистов проекты. Не идеал, но для большинства проектов 80-90% работы будет сделано без изменения хотя бы одной строчки кода.

Во второй части мы рассмотрим более продвинутое использование плагина mysqlnd_ms.

2015-04-21 оригинал

Последние посты

Комментарии

авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий