Быстрая миграция MySQL на failover cluster
MySQL - одна из самых ходовых, распространенных и простых во внедрении СУБД. Этот СУБД использует, наверное, половина всех проектов веба. Исключительная простота установки и внедрения, распространенность, поддержка “из коробки” во всех ходовых языках программирования для веб (perl, PHP, ruby, python, JS/node). Из-за мнимой простоты внедрения на потенциальные проблемы внедрения внимания просто не обращают - 90% проектов не доживут до того момента, когда заботливо разложенные авторами MySQL грабли больно ударят по лбу.
Одна из серьезных грабель MySQL - серьезные проблемы с производительностью и надежностью. Тюнинг MySQL сложен, так, как изначально MySQL задуман для быстрого внедрения в небольшом проекте. Кроме того, имея серьезный, высоконагруженный проект, хочется снизить риск отказа базы данных (согласно закону Паркинсона, все, что может сломаться - сломается, и частно - в самый неудачный момент). В данной статье я распишу, как мигрировать одиночный MySQL на кластер из трех равновесных машин (для надежности - отказ любого сервера не оставит вас без базы) с автоматическим распределением нагрузки.
В данном примере участвуют три сервера:
- db1 (IP 10.10.171.2)
- db2 (IP 10.10.171.3)
- db3 (IP 10.10.171.4)
DB1 - “старый” сервер с обычным MySQL, данные с которого мигрируют в кластер. DB2 и DB3, соответственно - свежие сервера. В принципе, установка свежего кластера “с нуля” (без данных) - не будет отличаться практически ничем. Изначально статья ориентирована на Debian, но для CentOS сделаны необходимые отступления, благо отличий немного.
Немного истории
Percona XtraDB - это ответвление (fork) изначального MySQL, созданного Монти Видениусом. В какой-то момент Видениус продал свой проект, и это сильно затормозило развитие проекта. Так, как проект обладал спорным качеством когда и был (с другой стороны) очень популярен - появилось огромное количество патчей, которые расширяли, углубляли и ускоряли кодовую базу. Самыми известным были набор патчей от google и набор патчей от Петра Зайцева. Последний создал компанию Percona, в рамках которой оптимизирует MySQL и консультирует пользователей этой системы. Он же выпускает свой форк MySQL, в котором собирает удачно работающие патчи. Как следствие - percona sql server с одной стороны надежнее, с другой - функциональнее, да и просто быстрее. В определенный момент был выпущен специальный дистрибутив XtraDB Cluster с набором библиотек Gallera Cluster - очень удачного и производительного решения для кластеризации MySQL
Подготовка
XtraDB cluster не входит в стандартные дистрибутивы ОС (ни для Debian, ни для CentOS), по этому, нужно добавить дистрибутивы
Для Debian (все операции проводим от root):
#получаем ключи репозитория
apt-key adv --keyserver keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A
#добавляем в файл /etc/apt/sources.list следующие строки:
deb http://repo.percona.com/apt VERSION main
deb-src http://repo.percona.com/apt VERSION main
#где VERSION - версия вашего debian:
# 6.0 - squeeze
# 7.0 - wheezy
# 8.0 - jessie
#обновляем кеш:
apt-get update
Для CentOS:
#добавляем репозиторий
yum install http://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm
#на всякий случай - обновляем кеш
yum clean all && yum makecache
теперь установим xtradb.
В момент установки MySQL server будет удален из системы. Вы НЕ потеряете базы и данные в них, но сервис не будет работать до тех пор, пока вы не закончите настройку
#debian
apt-get install -y percona-xtradb-cluster-55
#centos
yum install -y Percona-XtraDB-Cluster-55
Чтобы члены кластера могли общаться друг с другом, на каждом сервере нужно разрешить доступ со всех членов кластера по портам 4444 и 4567. Кроме того, со всех серверов, которые будут использовать кластер БД нужно разрешить доступ на порт 3306 (штатный порт mysq) и 9199 (об этом - далее).
Настройка кластера
Добавим в /etc/my.cnf (в debian он может называться /etc/mysql/my.cnf - если он уже есть - правим его!) следующие строки:
[mysqld]
#расположение физических файлов баз
datadir=/var/lib/mysql
#пользователь, от которого запускается сервер
user=mysql
#путь к библиотеке синхронизации, перед добавлением убедитесь, что файл по этому пути существует
wsrep_provider=/usr/lib64/libgalera_smm.so
#список IP всех членов кластера. Параметр идентичен на всех узлах кластера
wsrep_cluster_address=gcomm://10.10.171.2,10.10.171.3,10.10.171.4
binlog_format=ROW
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
#IP адрес узла, на котором мы настраиваем кластер
wsrep_node_address=10.10.171.2
wsrep_sst_method=xtrabackup-v2
#имя кластера. Уникальное для инсталляции, но единое для всех узлов
wsrep_cluster_name=axsystems_xtradb_test
#имя пользователя и пароль для синхронизации
wsrep_sst_auth="syncuser:Ttm3wsbPE72Km96R"
запустим первый узел в кластере. Если вы мигрируете кластер с существующего MySQL на percona - это надо делать на старом сервере, где данные. Этот узел в кластере будет образцом - остальные заберут с него данные
/etc/init.d/mysql bootstrap-pxc
создадим пользователя для синхронизации:
mysql@db1> CREATE USER 'syncuser'@'localhost' IDENTIFIED BY 'Ttm3wsbPE72Km96R';
mysql@db1> GRANT RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'syncuser'@'localhost';
mysql@db1> FLUSH PRIVILEGES;
скопируем конфигурацию на DB2, и внесем необходимые изменения:
[mysqld]
#расположение физических файлов баз
datadir=/var/lib/mysql
#пользователь, от которого запускается сервер
user=mysql
#путь к библиотеке синхронизации, перед добавлением убедитесь, что файл по этому пути существует
wsrep_provider=/usr/lib64/libgalera_smm.so
#список IP всех членов кластера. Параметр идентичен на всех узлах кластера
wsrep_cluster_address=gcomm://10.10.171.2,10.10.171.3,10.10.171.4
binlog_format=ROW
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
#IP адрес узла, на котором мы настраиваем кластер.
wsrep_node_address=10.10.171.3
wsrep_sst_method=xtrabackup-v2
#имя кластера. Уникальное для инсталляции, но единое для всех узлов
wsrep_cluster_name=axsystems_xtradb_test
#имя пользователя и пароль для синхронизации
wsrep_sst_auth="syncuser:Ttm3wsbPE72Km96R"
Теперь подключим новый узел в кластер:
/etc/init.d/mysql start
Запускаться он будет долго, так как ему надо снять копию с работающего уже члена кластера. К слову - в этот момент кластер уже работает и ваши пользователи могут использовать базу.
Как только mysql на втором сервере стартует успешно - можно проверить состояние кластера. Для этого в mysql консоли любого сервера пишем:
mysql> show status like 'wsrep%';
+----------------------------+--------------------------------------+
| Variable_name | Value |
+----------------------------+--------------------------------------+
| wsrep_local_state_uuid | c2883338-834d-11e2-0800-03c9c68e41ec |
[...]
| wsrep_local_state | 4 |
| wsrep_local_state_comment | Synced |
[...]
| wsrep_cluster_size | 2 |
| wsrep_cluster_status | Primary |
| wsrep_connected | ON |
[...]
| wsrep_ready | ON |
+----------------------------+--------------------------------------+
40 rows in set (0.01 sec)
как видно - status - synced, размер кластера - 2 (узла). Все работает, по аналогии добавляем третий узел (не забудьте поменять wsrep_node_address в настройках!)
failover-доступ
Большая часть современного ПО, работающая с MySQL, не может нормально обрабатывать несколько серверов. По этому мы будем использовать haproxy для распределения нагрузки. В этом примере haproxy ставится на каждый сервер, который использует mysql. HAProxy есть в базовом репозитории любого современного дистрибутива, так что просто ставим пакет:
#debian
apt-get install -y haproxy
#centos
yum install -y haproxy
Теперь настраиваем его. Пишем в конфигурацию /etc/haproxy/haproxy.cfg:
global
log 127.0.0.1 local2
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
#stats socket /var/lib/haproxy/stats
defaults
log global
mode http
option tcplog
option dontlognull
retries 3
redispatch
maxconn 2000
contimeout 5000
clitimeout 50000
srvtimeout 50000
#webui со статистикой. Обязательно используйте пароль (или закройте доступ к порту)
listen stats 0.0.0.0:8086
mode http
stats enable
stats uri /
stats realm "balancer"
stats auth logan:q6SJYy5cB3KKy34z
#собственно, mysql кластер
listen mysql-cluster 0.0.0.0:3306
mode tcp
balance roundrobin
option httpchk
server db1 10.10.171.2:3306 check port 9199 inter 12000 rise 3 fall 3
server db2 10.10.171.3:3306 check port 9199 inter 12000 rise 3 fall 3
server db3 10.10.171.4:3306 check port 9199 inter 12000 rise 3 fall 3
Порт 9199 haproxy будет использовать, чтобы убедится, что член кластера работоспособен и функционирует, как нужно. Сам XtraDB кластер не ждет соединений на 9199 порту. Нам потребуется специальный сервис, который будет локально проверять работу xtradb-cluster сервера для HAProxy. Сервис проверки очень прост, это не демон, так что его будет запускать супердемон xinetd. Вернемся на db1. Для начала - установим xinetd:
#centos
yum install -y xinetd
#debian
apt-get install -y xinetd
Создадим там файл /etc/xinetd.d/mysqlchk со следующим содержимым:
service mysqlchk
{
disable = no
flags = REUSE
socket_type = stream
port = 9199
wait = no
user = nobody
server = /usr/bin/clustercheck
server_args = syncuser Ttm3wsbPE72Km96R 1 /var/tmp/mss.log 0 /etc/my.cnf
log_on_failure += USERID
only_from = 0.0.0.0/0
per_source = UNLIMITED
}
Немного подробностей о том, что тут написано. Главные настройки - это server_args. Они позиционные, так что очередность путать нельзя:
- имя пользователя для проверки. Нужны права CONNECT и REPLICATION CLIENT
- пароль
- отвечать, что сервер доступен, если он - донор (то есть остальные сервера в данный момент синхронизируются с него)
- путь к файлу журнала
- отвечать, что сервер не доступен, если он сейчас readonly (синхронизируется или заблокирован). Если поставить 1 - haproxy будет считать сервер в статусе readonly доступным
- путь к my.cnf. В некоторых версиях debian он находится в /etc/mysql/my.cnf
пользователь и пароль в директиве server_args - из конфигурации mysql (выше). Обратите внимание на путь к my.cnf, в некоторых версиях debian он находится в /etc/mysql/my.cnf
Так же нужно добавить следующую строку в /etc/services:
mysqlchk 9199/tcp #mysqlcheck
После этого можно перезапустить xinetd. Проверим, что сервис проверки работает, как задумано:
db1> telnet 127.0.0.1 9199
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
HTTP/1.1 200 OK
Content-Type: text/plain
Connection: close
Content-Length: 40
Percona XtraDB Cluster Node is synced.
Connection closed by foreign host.
Эту последовательность действий надо повторить на всех машинах - членах кластера.
Теперь можно спокойно перезапустить haproxy, зайти на страницу статистики (в данном примере - http://[SERVER_IP]:8086/) и убедится, что haproxy видит все сервера кластера. После этого можно спокойно прописывать на сервере-пользователе БД локальный адрес 127.0.0.1, порт и все остальные настройки - без изменений - и теперь у вас есть отказоустойчивый кластер баз mysql
послесловие
Не смотря на то, что данное решение кажется “идеальным решением”, у него есть и слабые стороны. На всякий случай, опишу их:
- Ведение кластеризации требует накладных расходов. Они очень незначительны (по моим замерам не получалось более 2% от базовой производительности сервера). Частично это нивелируется тем, что Percona быстрее, чем штатный MySQL, частично - тем, что мы используем балансировку нагрузки. Однако не упомянуть об этом было бы нечестно. Накладные расходы растут с увеличением количества узлов в кластере.
- Percona Xtradb cluster крайне плохо поддерживает таблицы типа MyISAM. Об этом даже пишут в официальной документации. Если вам нужен MyISAM - я очень не рекомендую использовать xtradb cluster
- Данная конфигурация отвечает исключительно за репликацию, шардирование (разделение данных между серверами в зависимости от содержимого, например - пользователи с четным ID - на правый сервер, с нечетным - на левый) - тут отсутствует.
в случае аварии
Если что-то из описанного в статье работает не так, как описано (или не работает вовсе) - попробуйте проверить ваши последние шаги. Самые частые проблемы, с которыми сталкивался я, это:
- Закрытые порты не дают узлам кластера общаться между собой (4444 используется для общения и координации, 4567 - для передачи данных свеже добавленным узлам).
- Проблемы с линковкой (ошибка xtrabackup is exited: Perl:DBD is not installed, при этом Perl:DBD в системе есть). Это решается простым удалением всех пакетов, связанных с MySQL и установкой XtraDB cluster заново.
- Подавляющее большинство ошибок можно диагностировать чтением log-файла. /var/log/mysql.log (для debian) или /var/log/mysqld.log (centos) - ваш друг.