InnoDB cluster — оно работает, и вроде бы именно так, как обещали

в 0:13, , рубрики: clusterization, innodb, mysql, Администрирование баз данных, базы данных, хранилища данных

Я занимаюсь АТСками. И как-то так сложилась, что с самого первого заказа от меня хотели отказоустойчивости. Одним из ключевых компонентов современной АТС (как и любой информационной системы, наверное) является БД, где хранятся как данные о текущем состоянии системы, так и всякие конфигурационные параметры. Естественно, падение БД приводит к поломке всей системы. Начиналось все с MASTER-MASTER репликации в MySQL (исключительно для оперативности переключения), потом были эксперименты с MySQL over DRBD. Все это жило в pacemaker/corosync инфраструктуре. Там ездили IP-адреса, шлюзы и прочая лабудень. Со временем оно даже стало работать как-то более-менее устойчиво. Но тут мне попалась пара серверов, на которых DRBD сделать было нельзя, в MASTER-MASTER я разочаровался довольно давно (постоянно она у меня ломается, такая репликация), а без отказоустойчивой БД терялся весь смысл решения. На глаза мне попалось название InnoDB cluster и я решил: "была-не-была". Что из этого получилось — смотрите под катом.

Я все делаю на Debian Jessie. В других системах отличия будут, но не очень существенные.
Нам понадобятся:

  • MySQL APT репозиторий
  • MySQL Shell — для Debain пользователй в самом низу есть ссылка, по которой можно скачать скомпилированные бинарники, по какой причине они не попали в пакет именно для Debain для меня осталось загадкой
  • терпение
  • терпение
  • и еще раз терпение

Скачиваем (это можно сделать только вручную, пройдя регистрацию на сайте Оракла) и устанавливаем файл репозитория, обновляем наш кэш, устанавливаем MySQL Router (интересный зверь, познакомимся ниже) и MySQL Client:

dpkg -i mysql-apt-config_0.8.6-1_all.deb
apt-get update
apt-get install mysql-router
apt-get install mysql-client

Распаковываем и раскладываем по местам MySQL Shell из архива. Двигаемся дальше, ставим сервер:

apt-get install mysql-server

Тут устанавливаем ВСЕ предложенные компоненты (обратите внимание, третий компонент в списке по-умолчанию не выбран, а он понадобится). И останавливаем запущенный сразу ретивым установщиком сервер:

systemctl stop mysql

Далее, идем в
/etc/mysql/mysql.conf.d/mysqld.cnf и пишем туда что-то типа:

[mysqld]
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
datadir         = /var/lib/mysql
log-error       = /var/log/mysql/error.log
bind-address    = 0.0.0.0
port                    = 3300
symbolic-links=0
# Replication part
server_id=3
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog_checksum=NONE
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW
# Group replication part
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
loose-group_replication_start_on_boot=off
loose-group_replication_local_address= "1.1.1.1:33061"
loose-group_replication_group_seeds= "1.1.1.1:33061,1.1.1.2:33061,1.1.1.3:33061"
loose-group_replication_bootstrap_group= off

Здесь уже надо остановиться подробнее.

Первая опция, заслуживающая нашего внимания — port Рекомендую её значение устанавливать отличным от 3306. Почему — станет понятно ниже (на 3306 мы повесим кое-что другое)

Следующая: server_id. Для каждого сервера mysql в кластере это значение должно быть уникальным.

Опция, которая съела пару часов моего времени — безобидная (казалось бы) group_replication_local_address. Эта опция говорит о том, на каком адресе/порту слушать обращения за репликами с локальной БД. Алгоритм, по которому MySQL определяет, является ли указанный в этом поле IP-адрес локальным, мне угадать не удалось. На машине с 2-мя активными интерфейсами и тремя IP-адресами, подвешенными на эти интерфейсы только один адрес устроил MySQL.

И последняя: group_replication_group_seeds — в ней перечисляются сокеты, на которые необходимо обращаться (в указанном порядке) с попытками заказать реплики актуальных данных при подключении.

За подробностями можно обратиться на страничку с официальной документацией по GROUP REPLICATION

Итак, развернув на машине MySQL и настроив его таким образом двигаемся дальше. Удаляем все содержимое /var/lib/mysql Я серьезно. Взяли себя в руки, пошли и удалили. Если там лежит что-то, что дорого Вам как память — сделайте предварительно бэкап. Можно. Запускаем mysql:

systemctl start mysql

В общем-то, все подготовительные манипуляции завершены. Описанную процедуру надо будет повторить на всех серверах, которые планируется объединять в кластер.

Теперь особенности для первого сервера. Для начала объявляем root:

mysql
> create user 'root'@'%' identified by 'ochen-strashniy-parol';
> grant all to 'root'@'%' on *.* with grant option;
> flush privileges;
> q

Я знаю, знаю, знаю… Несекьюрно. Но, поверьте, указанному в документации набору привилегий верить нельзя (проверял: не работает), и подключиться с локалхоста тоже не выйдет. Более точный вариант с выделением отдельного пользователя в следующий раз, как будет "час та натхнення".

Потом подгружаем модуль X. Честно-честно, именно так его и зовут:

mysqlsh --classic --dba enableXProtocol

И в этот момент модные брюки превращаются… превращаются… превращаются в элегантные шорты. То бишь, MySQL уже не совсем SQL, а вовсе даже Document Store

Но нас это никак не расстраивает, поэтому двигаемся дальше:

mysqlsh

Теперь мы можем общаться с MySQL на JavaScript. Для начала подключимся к нашему серверу:

c root@1.1.1.1:3300

Введем пароль и проверим, все ли в порядке с нашим сервером, подходит ли его конфигурация для кластеризации:

dba.checkInstanceConfiguration('root@1.1.1.1:3300')

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

dba.configureLocalInstance('localhost:3300', {password:'somePwd', mycnfPath:'some path'})

Однако, у меня этот вариант сходу не заработал, потребовались допиливания напильником конфига до состояния, указанного в начале заметки. Естественно, что после каждого перепиливания конфига, сервер надо перезапускать. Например, при помощи systemctl restart mysq. Ну или как вам больше нравится…
Создаем кластер (предполагаю, что вы все еще в mysqlsh и сессия не обрывалась):

var cl = dba.createCluster('moyCluster')

Ну и добавляем в него только что настроенный сервер:

cl.addInstance('root@1.1.1.1:3300')

Начиная с этого момента рекомендую на какой-нибудь отдельной консоли держать

tail -f /var/log/mysql/error.log

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

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

mysqldump --all-databases  --triggers --routines --events > dump.sql

Теперь займемся остальными. Для этого на всех машинах повторяем манипуляции, описанные от начал статьи и до

systemctl start mysql

Не забываем поправить значения server_id и group_replication_local_address. После этого:

mysql
> reset master;
mysql < dump.sql

Да-да, заливаем в машину дамп с работающего в кластере сервера. Теперь с локальной машины (той, на которой крутится инстанс mysql-server`а, который мы будем подключать к кластеру) делаем:

mysql
> set GLOBAL group_replication_allow_local_disjoint_gtids_join=ON;
mysqlsh
> dba.checkInstanceConfiguration('root@1.1.1.2:3300')
> c root@1.1.1.1:3300
> var cl = getCluster('moyCluster')
> cl.addInstance('root@1.1.1.2:3300',{ipWhitelist: '1.1.1.0/24, 127.0.0.1/8'})

Если мы ничего не перепутали, то в этот момент у нас в кластере уже две машины. Аналогично добавляем третью.

В данный момент у нас есть кластер, который сам за собой присматривает, выбирает мастера и реплицируется на слэйвы. К каждому серверу можно обратиться на его порт 3300 и, если вы обратитесь к мастеру, то вы сможете читать и писать, а если к слейву — то только читать. Выяснить, мастером является сервер или слейвом можно из вывода cluster.status(). Но это еще не очень удобно. Хотелось бы обращаться всегда в одно и то же место, на один и тот же ip/port и не зависеть от внутреннего состояния кластера. Для этого используем MySQL Router Прямо в доке есть пример его начального конфигурирования, который делает почти все, что нам нужно. Изменим его немного:

mysqlrouter --bootstrap 1.1.1.1:3300 --user mysqlrouter

Теперь идем в /etc/mysqlrouter/mysqlrouter.conf и исправляем там порты как-нибудь так:

[routing:moyCluster_default_rw]
...
bind_port=3306
...

[routing:moyCluster_default_ro]
...
bind_port=3307
...

После этого можно делать

systemctl start mysqlrouter

И теперь у вас на порту 3306 отвечает обычный mysql с поддержкой чтения/записи вне зависимости от того, какая из машин кластера сейчас мастером, а какая слейвом. На 3307 — всегда read-only. Количество экземпляров mysqlrouter никак не ограничено, вы можете на каждой клиентской машине запустить свой экземпляр и повесить его на внутренний интерфейс 127.0.0.1:3306. mysqlrouter сам отслеживает изменения в кластере (как добавления, так и пропадания нод в нем) и актуализирует маршрутизацию. Происходит это раз в пять минут (если верить логам). Если в этом промежутке возникает обращение которое не может быть обработано (нода выпала или была целенаправленно выведена из кластера), то роутер дает отказ в исполнении транзакции и перечитывает состояние кластера во внеочередном порядке.

Кстати, если по какой-то причине нода отвалилась от кластера, её можно вернуть в семью командой

mysqlsh
> c root@<ip:port-текущего_мастера>
> var cl = dba.getCluster('moyCluster')
> cl.rejoinInstance('root@ip:port-отвалившейся_ноды')

Спасибо, что дочитали. Надеюсь, что это будет полезно. Если заметите в тексте неточности — пишите в комментарии, писал на горячую, но по памяти, так что мог напутать.

Автор: Borikinternet

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js