227 lines
13 KiB
Markdown
227 lines
13 KiB
Markdown
### 1. Настраиваем асинхронную репликацию.
|
||
Я решил сделать сразу три хоста - один мастер и два слейва, чтобы к этому не возвращаться в 5-м пункте.
|
||
- #### [my.cnf мастера](../../cicd/mysql/mysql_master.conf):
|
||
```
|
||
[mysqld]
|
||
skip-host-cache
|
||
skip-name-resolve
|
||
server-id = 1
|
||
log_bin = /var/log/mysql/mysql-bin.log
|
||
binlog_do_db = app
|
||
```
|
||
- #### [my.cnf первого слэйва](../../cicd/mysql/mysql_slave1.conf):
|
||
```
|
||
[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
|
||
```
|
||
- #### [my.cnf первого слэйва](../../cicd/mysql/mysql_slave2.conf):
|
||
```
|
||
[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
|
||
```
|
||
- #### [Инициализация кластера](../../cicd/init.sh):
|
||
- на мастере создаем базу, пользователя для работы приложения и пользователя для репликации:
|
||
```
|
||
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](../dz002/REPORT.md). Тем более, что все инструменты для этого уже есть в наличии (wrk).
|
||
- #### Добавил в [структуру конфига](../../internal/models/config.go) адреса мастера и двух слейвов:
|
||
```
|
||
type DSN struct {
|
||
Master string
|
||
Slave1 string
|
||
Slave2 string
|
||
Port string
|
||
User string
|
||
Pass string
|
||
Base string
|
||
}
|
||
```
|
||
- #### В [хэндлере страницы app:port/search](../../internal/handlers/handlers.go) создал селектор, который направляет запросы чтения на первый слейв, если он присутствует в конфигурации:
|
||
```
|
||
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 скрипта](scripts/post.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
|
||
```
|
||
- #### Идем в [графану](http://localhost:3001/) и наблюдаем нагрузку на мастер.
|
||
- #### Ждем пока 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
|
||
```
|
||
- #### Идем в [графану](http://localhost:3001/) и наблюдаем нагрузку на слэйв.
|
||
- #### Ждем пока 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
|
||
|
||
З.Ы. Еще можно было бы поиграть с [отложенной репликацией](https://dev.mysql.com/doc/refman/5.6/en/replication-delayed.html), но не в рамках данного эксперимента
|
||
|
||
- #### Останавливаем на всех слейвах потоки получения обновлений бинарного лога:
|
||
```
|
||
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. Проверяем, есть ли потери транзакций:
|
||
- #### Проверяем количество строк в тестовой таблице на новом мастере:
|
||
```
|
||
sudo docker exec {slave_to_promote} sh -c "export MYSQL_PWD=root; mysql -u root -e 'USE app; SELECT count(*) from test;'"
|
||
```
|
||
В нашем случае, потерь транзакций не наблюдается. В таблице нового мастера те же 10233 строки.
|
||
|