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

создадим пользователя для синхронизации:

[email protected]> CREATE USER 'syncuser'@'localhost' IDENTIFIED BY 'Ttm3wsbPE72Km96R';
[email protected]> GRANT RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'syncuser'@'localhost';
[email protected]> 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) - ваш друг.