11 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
-
Добавляем в my.cnf обоих слейвов строки:
binlog_format=ROW binlog-checksum=crc32
-
Запускаем кластер и приложение:
sudo make app-up
7. Включить GTID.
-
Добавляем в my.cnf мастера строки:
gtid-mode=on enforce-gtid-consistency=true
-
Добавляем в my.cnf обоих слейвов строки:
gtid-mode=on enforce-gtid-consistency=true
-
Запускаем кластер и приложение:
sudo make app-up
8. Настроить полусинхронную репликацию.
-
Включаем динамическую загрузку модулей и полусинхронную репликацию с таймаутом 1с в my.cnf на мастере:
have_dynamic_loading=YES rpl_semi_sync_master_enabled=1 rpl_semi_sync_master_timeout=1000
-
Включаем динамическую загрузку модулей и полусинхронную репликацию в my.cnf на обоих слейвах:
have_dynamic_loading=YES rpl_semi_sync_master_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. Создать нагрузку на запись в любую тестовую таблицу. На стороне, которой нагружаем считать, сколько строк мы успешно записали.
10. С помощью kill -9 убиваем мастер MySQL.
11. Заканчиваем нагрузку на запись.
12. Выбираем самый свежий слейв. Промоутим его до мастера. Переключаем на него второй слейв.
13. Проверяем, есть ли потери транзакций.
Встроен запуск prometeus, grafana и т.д. Графана доступна на http://localhost:3001/
$ sudo docker run --rm -v /root/scripts:/scripts williamyeh/wrk -t1 -c10 -d5m --timeout 30s http://192.168.1.66:8080/search -s /scripts/post.lua -- debug true 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
$ sudo make app-reload ... ... ... $ sudo docker run --rm -v /root/scripts:/scripts williamyeh/wrk -t1 -c10 -d5m --timeout 30s http://192.168.1.66:8080/search -s /scripts/post.lua -- debug true 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