13 KiB
13 KiB
1. Настраиваем асинхронную репликацию.
Я решил сделать сразу три хоста - один мастер и два слейва, чтобы к этому не возвращаться в 5-м пункте.
[mysqld]
skip-host-cache
skip-name-resolve
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = app
[mysqld]
skip-host-cache
skip-name-resolve
server-id = 2
log_bin = /var/log/mysql/mysql-bin.log
relay-log = /var/log/mysql/mysql-relay-bin.log
binlog_do_db = app
[mysqld]
skip-host-cache
skip-name-resolve
server-id = 3
log_bin = /var/log/mysql/mysql-bin.log
relay-log = /var/log/mysql/mysql-relay-bin.log
binlog_do_db = app
-
Инициализация кластера:
- на мастере создаем базу, пользователя для работы приложения и пользователя для репликации:
CREATE DATABASE IF NOT EXISTS app CHARACTER SET utf8 COLLATE utf8_general_ci; GRANT ALL ON app.* TO "app"@"%" IDENTIFIED BY "app"; GRANT REPLICATION SLAVE ON *.* TO "mydb_slave_user"@"%" IDENTIFIED BY "mydb_slave_pwd"; FLUSH PRIVILEGES;
- на обоих слэйвах создаем базу и пользователя для работы приложения:
CREATE DATABASE IF NOT EXISTS app CHARACTER SET utf8 COLLATE utf8_general_ci; GRANT ALL ON app.* TO "app"@"%" IDENTIFIED BY "app"; FLUSH PRIVILEGES;
- определяем текущий IP адрес мастера:
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "mysql_master"
- определяем текущий лог файл мастера и позицию в нем:
mysql -u root -e "SHOW MASTER STATUS"' | grep mysq | awk '{print $1}' mysql -u root -e "SHOW MASTER STATUS"' | grep mysq | awk '{print $2}'
- на обоих слейвах назначаем мастера и запускаем репликацию:
CHANGE MASTER TO MASTER_HOST={IP_мастера},MASTER_USER='mydb_slave_user',MASTER_PASSWORD='mydb_slave_pwd',MASTER_LOG_FILE={текущий_лог},MASTER_LOG_POS={текущая_позиция}; START SLAVE;
- на мастере создаем базу, пользователя для работы приложения и пользователя для репликации:
2. Выбираем 2 любых запроса на чтения (в идеале самых частых и тяжелых по логике работы сайта) и переносим их на чтение со слейва.
Я решил поэкспериментировать с теми же поисковыми запросами, над которыми мы экспериментировали в ДЗ002. Тем более, что все инструменты для этого уже есть в наличии (wrk).
-
Добавил в структуру конфига адреса мастера и двух слейвов:
type DSN struct { Master string Slave1 string Slave2 string Port string User string Pass string Base string }
-
В хэндлере страницы app:port/search создал селектор, который направляет запросы чтения на первый слейв, если он присутствует в конфигурации:
db := app.DBMaster if app.Config.DSN.Slave1!="" { db = app.DBSlave1 }
3. Делаем нагрузочный тест по странице, которую перевели на слейв до и после репликации. Замеряем нагрузку мастера (CPU, la, disc usage, memory usage).
-
Разворачиваем prom&grafana в docker настраиваем dashboard grafana на docker контейнеры
sudo make prom-up
-
Запускаем приложение с подключением только к мастеру и с помощью wrk и lua скрипта нагружаем страницу app:port/search:
sudo make app-up sudo docker run --rm -v /root/scripts:/scripts williamyeh/wrk -t1 -c10 -d5m --timeout 30s http://localhost:8080/search -s /scripts/post.lua -- debug true
-
Идем в графану и наблюдаем нагрузку на мастер.
-
Ждем пока wrk отработает:
Running 5m test @ http://192.168.1.66:8080/search 1 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 5.83s 1.74s 13.29s 77.58% Req/Sec 3.57 4.74 30.00 82.50% 511 requests in 5.00m, 18.69MB read Requests/sec: 1.70 Transfer/sec: 63.79KB
-
Подключаем первый слэйв в приложении, добавлением в docker-compose.yml переменной окружения:
APP_DSN_SLAVE1: mysql_slave1
-
Перезапускаем контейнер с приложением и нагружаем ту же страницу тем же запросом, с помощью wrk:
sudo make app-reload sudo docker run --rm -v /root/scripts:/scripts williamyeh/wrk -t1 -c10 -d5m --timeout 30s http://localhost:8080/search -s /scripts/post.lua -- debug true
-
Идем в графану и наблюдаем нагрузку на слэйв.
-
Ждем пока wrk отработает:
Running 5m test @ http://192.168.1.66:8080/search 1 threads and 10 connections Thread Stats Avg Stdev Max +/- Stdev Latency 32.12ms 31.59ms 561.62ms 90.65% Req/Sec 373.34 147.37 610.00 68.45% 110834 requests in 5.00m, 433.90MB read Non-2xx or 3xx responses: 110834 Requests/sec: 369.39 Transfer/sec: 1.45MB
-
Получаем график нагрузки:
4. ОПЦИОНАЛЬНО: в качестве конфига, который хранит IP реплики сделать массив для легкого добавления реплики. Это не самый правильный способ балансирования нагрузки. Поэтому опционально.
Не очень понял, что именно нужно сделать. Если речь о конфиге приложения, я пока сделал отдельные переменные для каждого сервера БД. Если потребуется, заменю срезом. Пока пусть останется так.
5. Настроить 2 слейва и 1 мастер.
Пропускаю этот пункт, т.к. все уже было сделано в п.1
6. Включить row-based репликацию.
-
Добавляем в my.cnf мастера и обоих слейвов, строки:
binlog_format=ROW binlog-checksum=crc32
7. Включить GTID.
-
Добавляем в my.cnf мастера, строки:
gtid-mode=on enforce-gtid-consistency=true
-
Добавляем в my.cnf обоих слейвов, строки:
gtid-mode=on enforce-gtid-consistency=true binlog-rows-query-log_events=1
8. Настроить полусинхронную репликацию.
-
Включаем динамическую загрузку модулей и полусинхронную репликацию с таймаутом 1с в my.cnf на мастере:
loose-rpl_semi_sync_master_enabled=1 loose-rpl_semi_sync_master_timeout=1000
-
Включаем динамическую загрузку модулей и полусинхронную репликацию в my.cnf на обоих слейвах:
loose-rpl_semi_sync_slave_enabled=1
-
Устанавливаем semisync плагин на мастере:
INSTALL PLUGIN rpl_semi_sync_master SONAME "semisync_master.so";
-
Устанавливаем semisync плагин на обоих слейвах:
INSTALL PLUGIN rpl_semi_sync_slave SONAME "semisync_slave.so";
-
Проверяем на всех хостах, установлен ли плагин:
SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE '%semi%';
-
Запускаем кластер и приложение:
sudo make app-up
9. Создать нагрузку на запись в любую тестовую таблицу. На стороне, которой нагружаем считать, сколько строк мы успешно записали.
-
Запускаем кластер:
sudo make db-up
-
Запускаем контейнер с mysql-client:
sudo make client-up
-
Заходим внутрь контейнера и запускаем скрипт:
/scripts/dz003_2.sh
10. С помощью kill -9 убиваем мастер MySQL.
-
Т.к. образ mysql:5.7 не содержит команды ps, не мудрим и грохаем контейнер с мастером:
sudo docker kill mysql_master
11. Заканчиваем нагрузку на запись.
-
Возвращаемся в контейнер клиента и запоминаем, знаение счетчика строк успешно записанных в таблицу мастера
В нашем случае, скрипт тормазнул после добавления 10233 строк
12. Выбираем самый свежий слейв. Промоутим его до мастера. Переключаем на него второй слейв.
-
Определяем свежайшую реплику:
sudo docker exec mysql_slave1 sh -c "export MYSQL_PWD=root; mysql -u root -e 'SHOW SLAVE STATUS\G' | grep Master_Log_File" sudo docker exec mysql_slave2 sh -c "export MYSQL_PWD=root; mysql -u root -e 'SHOW SLAVE STATUS\G' | grep Master_Log_File"
В нашем случае оба слейва остановились на mysql-bin.000003, поэтому мастером будем промоутить mysql_slave1
З.Ы. Еще можно было бы поиграть с отложенной репликацией, но не в рамках данного эксперимента
-
Останавливаем на всех слейвах потоки получения обновлений бинарного лога:
sudo docker exec mysql_slave1 sh -c "export MYSQL_PWD=root; mysql -u root -e 'STOP SLAVE IO_THREAD; SHOW PROCESSLIST;'" sudo docker exec mysql_slave2 sh -c "export MYSQL_PWD=root; mysql -u root -e 'STOP SLAVE IO_THREAD; SHOW PROCESSLIST;'"
-
Полностью останавливаем слэйв который будем промоутить до мастера и сбрасываем его бинлог:
sudo docker exec mysql_slave1 sh -c "export MYSQL_PWD=root; mysql -u root -e 'STOP SLAVE; RESET MASTER;'"
-
Оставшийся слэйв переключаем на новый мастер:
sudo docker exec {slave_to_promote} sh -c "export MYSQL_PWD=root; mysql -u root -e 'STOP SLAVE; CHANGE MASTER TO MASTER_HOST='{slave_to_promote}'; START SLAVE;'"
13. Проверяем, есть ли потери транзакций:
-
Проверяем количество строк в тестовой таблице на новом мастере:
В нашем случае, потерь транзакций не наблюдается. В таблице нового мастера те же 10233 строки.sudo docker exec {slave_to_promote} sh -c "export MYSQL_PWD=root; mysql -u root -e 'USE app; SELECT count(*) from test;'"