Multi-Master репликация в MySQL

В данной статье будет рассмотрен процесс развертывания отказоустойчивой подсистемы баз данных на базе MySQL сервера.

Перед прочтением советую прочитать эту статью.

На работе встал вопрос по созданию зеркала сайта для другого региона (Азия). Т.к. время прохождения пакетов туда довольно большое, сказывается географическая удалённость, да и Великий Китайский Файервол тоже еще не отменили, то было решено создать зеркало в азиатском регионе. С переносом движка проблем не предвиделось, файлы пользователей можно спокойно синхронизировать через rsync, а вот с базой данных наметилась проблема. Как быть если пользователь добавил объект в Азии? Надо, чтобы этот объект был виден не только пользователям локального зеркала, но и всем остальным.

Изучение вопроса я начал с кластеризации MySQL. Выяснилось много интересных подробностей. Например, то, что версия сервера входящая в дистрибьютив Ubuntu(её я использую на тестовом стенде) не поддерживает NDB(Network Data Base) Storage Engine. Необходимо поставить либо версию mysql-max, которая по слухам поддерживает NDB, либо mysql-cluster. Самое интересно что deb-пакетов для них нет, так что надо либо ставить из бинарников, либо из исходников. Установка из бинарноков у меня не получилась. Я никак не мог удалить старый сервер (пакет удаляется, а файлы все на месте). Честно прокулупавшись с этим два дня я бросил сию затею. Из исходников компилировать не пробовал, но думаю это самый лучший вариант.

Master-Master replication

image

Вообщем следующим этапом изысканий была репликация. Тут то я и понял, что это то, что мне нужно. Есть центральный сервер, и есть реплика в регионе, которая будет таскать с него обновления. Но надо сделать так, чтобы и обновления с реплики попадали в основную базу. Получается, необходимо сделать master-master репикацию. Т.е. первый сервер будет мастером для второго(второй подключапется к нему как слейв), а второй будет мастером для первого(первый подключается к нему как слейв). Испытания проводил на виртуалках на базе Ubuntu Server 9.10

Итак, сервер ubuntu1(192.168.0.21), конфиг mysql:

[mysqld]
# номер сервера, у всех реплицируемых серверов они должны быть уникальными
server-id = 1

# конфигурация серера, как мастера
log-bin = /var/lib/mysql/mysql-bin

# конфигурация сервера, как слейва
relay-log = /var/lib/mysql/mysql-relay-bin
relay-log-index = /var/lib/mysql/mysql-relay-bin.index
replicate-do-db = test_db
master-host=192.168.0.22 #ubuntu2
master-user=replication
master-password=password_of_user_replication
master-port=3306

Для второго аналогично, только другой ip мастера и номер сервера.

Теперь перезапускаем сервера. Потом создаем юзера replication, даем ему права на репликацию:
mysql@ubuntu1> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replication'@'192.168.0.22' IDENTIFIED BY 'password';
mysql@ubuntu2> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replication'@'192.168.0.21' IDENTIFIED BY 'password';

Дальше необходимо привязать слейвов к своим мастерам. Делается это так:
Привязка ubuntu1 как мастера к ubuntu2:
Сначала блокируем запись в базу.
mysql@ubuntu1> SET GLOBAL read_only = OFF;
Подробнее об этом можно прочитать здесь

mysql@ubuntu1> show master status;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000006 | 7984 | | |
+------------------+----------+--------------+------------------+
1 row in set (0,00 sec)

Берем оттуда значение File и position и осуществляем подключение мастера.
mysql@ubuntu2> slave stop; # на всякий случай
mysql@ubuntu2> CHANGE MASTER TO MASTER_HOST = "192.168.0.22", MASTER_USER = "replication", MASTER_PASSWORD = "password_of_user_replication", MASTER_LOG_FILE = "mysql-bin.000006", MASTER_LOG_POS = 7984;
mysql@ubuntu2> slave start;

После этого проверить подключение можно либо через
mysql@ubuntu2>load data from master;
либо через
mysql@ubuntu2> show slave status;
Если ошибок нет, мастер подключен.
Теперь подключаем этот сервер в качестве мастера для первого (делается точно также).
В итоге мы имеем систему из двух серверов, вносим изменение на любой сервер и оно реплицируется на второй.

Multi-Master replication

Но этого мне показалось мало. А если захочется добавить третий сервер? Сначала я думал про линейную топологию
ubuntu1 <-> ubuntu2 <-> ubunt3 и даже топологию звезда. Но увы, это было далеко от реальности. MySQL не позволяет одному слейву иметь несколько мастеров. А в линейной топологии таким будет ubuntu2. Если кто-то знает, как можно прявязать один слейв к нескольким мастерам буду благодарен за информацию.
Звезда не подходит, линейная тоже, остается кольцо. А попробую, подумал я.

image

Получается что в данной схеме каждый сервер имеет только одного мастера, а благодаря тому, что кольцо замкнуто, обновления дойдут до всех серверов из любой точки кольца. Тут важно упомянуть, что чтобы мастер передавал на слейв не только свои собственные обновления, но и обновления своего мастера надо добавить в [mysqld] секцию my.cnf строку:
log-slave-updates
соответственно в конфиг каждого сервера.

Проверил, и был приятно удивлен, что схема заработала. Где бы не меняли базу, изменения реально реплицируются на остальные сервера.

Автоинкрементные поля

При одновременном добавлении новых строк, содержащих автоинкрементные поля, на разные мастер-сервера может возникнуть конфликт. Чтобы такого не происходило, надо изменить шаг последовательности автоинкрементов на серверах БД.

  • auto_increment_increment определяет шаг изменения AUTO_INCREMENT .
  • auto_increment_offset определяет начальное значение инкремента

Подобрав правильные(не конфликтующие) значения этих параметров на разных мастерах, сервера, используемые в мульти-мастер конфигурации будут использовать неконфликтующие значения AUTO_INCREMENT при вставки записей. Например для N мастер-серверов, установим такие значения:

  • Установим auto_increment_increment в N на каждом мастере.
  • На каждом из N мастеров ставим разные значения auto_increment_offset, используя 1, 2,…, N.

Например, используя auto_increment_increment = 10 и auto_increment_offset=3, будут сгенерированы следующие значения поля 3, 13, 23. А используя 10, 7, будут такие 7, 17, 27, и т.д.

Отказоусточивость

После всего этого не мог не попробовать уронить один из серверов. Выдергиваем питание из ubuntu2. Потом загружаем его. Что же получается?
При последующих изменениях на ubnutu2 они без проблем тиражируются на ubuntu3 и ubuntu1. Но изменения вносимые на ubuntu2 на остальные сервера не тиражируеются. Смотрим show slave status на ubuntu3 и видим, что он потерял своего мастера.
В качестве рецепта, могу предложить следующее:
После старта сервера БД выполнять отвязывание мастера от слейва текущего сервера, и привязывание его вновь. Т.к. в топологии кольцо слейв у любого сервера один, это упрощает задачу. Алгоритм будет примерно такой:
1. ubuntu2 выключился по питанию и загрузился вновь
2. ubuntu2 у себя:
— блокирует реплицируемую базу на запись: mysql@ubuntu2> SET GLOBAL read_only = OFF;
— смотрит название и позицию в логе: mysql@ubuntu2> show master status;
2. ubuntu2 через mysql клиент заходит на своего слейва (ubuntu3)
— отвязывает себя от слейва mysql@ubuntu3> slave stop;
— объявляет себя мастером: mysql@ubuntu3> CHANGE MASTER TO MASTER_HOST = "192.168.0.22", MASTER_USER = "replication", MASTER_PASSWORD = "password_of_user_replication", MASTER_LOG_FILE = "mysql-bin.000006", MASTER_LOG_POS = 5161; с полученными на предыдущем шаге названием лога и позицией.
— вновь привязывает слейва: mysql@ubuntu3> slave start;
Пока реализация данного скрипта не готова, думаю на чем его писать perl/php/python/bash…
Буду рад услышать мысли хабрасообщества на эту тему. Пишите если статья оказалась полезной, в планах всё-таки разобраться с кластеризацией и сделать репликацию кластеров.

10 Comments

  1. Отличное решение! Хочу использовать такую схему при синхронизации данных с разных офисов. Возможно ли автоинкрементальное значение всегда начинать с номер офиса. Скажем если номер офиса 55, по AI значение болжно быть 551,552,55N. Это возможно?

    1. Спасибо, Даниил. Думаю, что без перекомпиляции MySQL это не возможно. Ведь там есть всего 2 параметра: auto_increment_increment и auto_increment_offset. Т.е. получается разделение области инкрементов на чет и нечет, т.е. в вашем случае два офиса. Думаю тут надо копать в сторону структуры таблиц, можно например сделать поле для id офиса. А потом написать какую-нибудь хитрую хранимку.

  2. Здавствуй.

    А зачем в конфиге my.cnf прописьівать айпи_мастера, порт, пароль, если в самой базе он указьівается запросом?

    Спасибо

    1. Поясните пожалуйста про “в самой базе он указывается запросом”.

      1. Ну типа Вы пишите

        mysql@ubuntu1> GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO ‘replication’@’192.168.0.22’ IDENTIFIED BY ‘password’;

        и потом то же дублируете в my.cnf

        1. Конфиг читается при запуске демона, запрос выполняется во время работы. Соответственно, да, при использовании запроса в конфиге эту строку можно не писать.

Leave a Reply to Андрей Токарчук Cancel reply