MariaDB
 sql >> база данни >  >> RDS >> MariaDB

Множество подчинени устройства за отложена репликация за аварийно възстановяване с нисък RTO

Забавената репликация позволява на подчинения за репликация умишлено да изостава от главния с поне определен период от време. Преди да изпълни събитие, подчинението първо ще изчака, ако е необходимо, докато изтече даденото време от създаването на събитието на главния. Резултатът е, че робът ще отразява състоянието на господаря известно време назад в миналото. Тази функция се поддържа от MySQL 5.6 и MariaDB 10.2.3. Може да ви бъде полезен в случай на случайно изтриване на данни и трябва да бъде част от вашия план за възстановяване след бедствие.

Проблемът при настройването на роб на отложена репликация е колко забавяне трябва да поставим. Твърде малко време и рискувате лошата заявка да стигне до вашия забавен подчинен, преди да можете да стигнете до него, като по този начин губите смисъла да имате забавения подчинен. По избор можете да настроите времето за закъснение да бъде толкова дълго, че да са необходими часове, докато забавеният подчинен настигне мястото, където е бил главният по време на грешката.

За щастие с Docker, изолацията на процесите е неговата сила. Изпълнението на множество MySQL екземпляри е доста удобно с Docker. Тя ни позволява да имаме множество отложени подчинени устройства в рамките на един физически хост, за да подобрим времето си за възстановяване и да спестим хардуерни ресурси. Ако смятате, че 15-минутно закъснение е твърде кратко, можем да имаме друг екземпляр с 1-часово закъснение или 6-часово за още по-стара моментна снимка на нашата база данни.

В тази публикация в блога ще разположим множество MySQL отложени подчинени устройства на един физически хост с Docker и ще покажем някои сценарии за възстановяване. Следната диаграма илюстрира нашата окончателна архитектура, която искаме да изградим:

Нашата архитектура се състои от вече разгърната MySQL репликация с 2 възела, работеща на физически сървъри (синьо) и бихме искали да настроим още три подчинени MySQL (зелени) със следното поведение:

  • 15 минути закъснение
  • 1 час закъснение
  • 6 часа закъснение

Обърнете внимание, че ще имаме 3 копия на същите данни на същия физически сървър. Уверете се, че нашият хост на Docker разполага с необходимото място за съхранение, така че предварително заделете достатъчно дисково пространство.

Основна подготовка за MySQL

Първо, влезте в главния сървър и създайте потребител за репликация:

mysql> GRANT REPLICATION SLAVE ON *.* TO [email protected]'%' IDENTIFIED BY 'YlgSH6bLLy';

След това създайте PITR-съвместим архив на главния:

$ mysqldump -uroot -p --flush-privileges --hex-blob --opt --master-data=1 --single-transaction --skip-lock-tables --skip-lock-tables --triggers --routines --events --all-databases | gzip -6 -c > mysqldump_complete.sql.gz

Ако използвате ClusterControl, можете лесно да направите PITR-съвместимо архивиране. Отидете на Архивиране -> Създаване на архивиране и изберете "Пълен PITR-съвместим" под падащото меню "Тип на изхвърляне":

И накрая, прехвърлете този архив на хоста на Docker:

$ scp mysqldump_complete.sql.gz [email protected]:~

Този архивен файл ще се използва от подчинените MySQL контейнери по време на процеса на зареждане на подчинен, както е показано в следващия раздел.

Отложено подчинено внедряване

Подгответе нашите директории с контейнери Docker. Създайте 3 директории (mysql.conf.d, datadir и sql) за всеки MySQL контейнер, който ще стартираме (можете да използвате цикъл, за да опростите командите по-долу):

$ mkdir -p /storage/mysql-slave-15m/mysql.conf.d
$ mkdir -p /storage/mysql-slave-15m/datadir
$ mkdir -p /storage/mysql-slave-15m/sql
$ mkdir -p /storage/mysql-slave-1h/mysql.conf.d
$ mkdir -p /storage/mysql-slave-1h/datadir
$ mkdir -p /storage/mysql-slave-1h/sql
$ mkdir -p /storage/mysql-slave-6h/mysql.conf.d
$ mkdir -p /storage/mysql-slave-6h/datadir
$ mkdir -p /storage/mysql-slave-6h/sql

Директорията "mysql.conf.d" ще съхранява нашия персонализиран конфигурационен файл на MySQL и ще бъде картографиран в контейнера под /etc/mysql.conf.d. "datadir" е мястото, където искаме Docker да съхранява директорията с данни MySQL, която се преобразува в /var/lib/mysql на контейнера, а директорията "sql" съхранява нашите SQL файлове - архивни файлове в .sql или .sql.gz формат за етап подчинения преди репликация, както и .sql файлове за автоматизиране на конфигурацията и стартирането на репликацията.

15-минутно отложено подчинено устройство

Подгответе конфигурационния файл на MySQL за нашия 15-минутно отложен подчинен:

$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf

И добавете следните редове:

[mysqld]
server_id=10015
binlog_format=ROW
log_bin=binlog
log_slave_updates=1
gtid_mode=ON
enforce_gtid_consistency=1
relay_log=relay-bin
expire_logs_days=7
read_only=ON

** Стойността на сървърния идентификатор, която използвахме за това подчинено устройство, е 10015.

След това в директорията /storage/mysql-slave-15m/sql създайте два SQL файла, един за RESET MASTER (1reset_master.sql) и друг за установяване на връзката за репликация с помощта на оператор CHANGE MASTER (3setup_slave.sql).

Създайте текстов файл 1reset_master.sql и добавете следния ред:

RESET MASTER;

Създайте текстов файл 3setup_slave.sql и добавете следните редове:

CHANGE MASTER TO MASTER_HOST = '192.168.55.171', MASTER_USER = 'rpl_user', MASTER_PASSWORD = 'YlgSH6bLLy', MASTER_AUTO_POSITION = 1, MASTER_DELAY=900;
START SLAVE;

MASTER_DELAY=900 е равно на 15 минути (в секунди). След това копирайте архивния файл, взет от нашия главен (който е прехвърлен в нашия хост Docker) в директорията "sql" и го преименувайте на 2mysqldump_complete.sql.gz:

$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-15m/sql/2mysqldump_complete.tar.gz

Крайният вид на нашата "sql" директория трябва да бъде нещо подобно:

$ pwd
/storage/mysql-slave-15m/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql

Обърнете внимание, че ние поставяме префикс към името на SQL файла с цяло число, за да определим реда на изпълнение, когато Docker инициализира MySQL контейнера.

След като всичко е на мястото си, стартирайте MySQL контейнера за нашия 15-минутно отложен подчинен:

$ docker run -d \
--name mysql-slave-15m \
-e MYSQL_ROOT_PASSWORD=password \
--mount type=bind,source=/storage/mysql-slave-15m/datadir,target=/var/lib/mysql \
--mount type=bind,source=/storage/mysql-slave-15m/mysql.conf.d,target=/etc/mysql/mysql.conf.d \
--mount type=bind,source=/storage/mysql-slave-15m/sql,target=/docker-entrypoint-initdb.d \
mysql:5.7

** Стойността MYSQL_ROOT_PASSWORD трябва да е същата като паролата за root на MySQL на главния.

Следните редове са това, което търсим, за да проверим дали MySQL работи правилно и е свързан като подчинен към нашия главен (192.168.55.171):

$ docker logs -f mysql-slave-15m
...
2018-12-04T04:05:24.890244Z 0 [Note] mysqld: ready for connections.
Version: '5.7.24-log'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
2018-12-04T04:05:25.010032Z 2 [Note] Slave I/O thread for channel '': connected to master '[email protected]:3306',replication started in log 'FIRST' at position 4

След това можете да проверите състоянието на репликация със следния израз:

$ docker exec -it mysql-slave-15m mysql -uroot -p -e 'show slave status\G'
...
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
                    SQL_Delay: 900
                Auto_Position: 1
...

В този момент нашият 15-минутен отложен подчинен контейнер се репликира правилно и нашата архитектура изглежда нещо подобно:

1 час отложено подчинено устройство

Подгответе конфигурационния файл на MySQL за нашия 1-час отложен подчинен:

$ vim /storage/mysql-slave-1h/mysql.conf.d/my.cnf

И добавете следните редове:

[mysqld]
server_id=10060
binlog_format=ROW
log_bin=binlog
log_slave_updates=1
gtid_mode=ON
enforce_gtid_consistency=1
relay_log=relay-bin
expire_logs_days=7
read_only=ON

** Стойността на сървърния идентификатор, която използвахме за това подчинено устройство, е 10060.

След това в директорията /storage/mysql-slave-1h/sql създайте два SQL файла, един за RESET MASTER (1reset_master.sql) и друг за установяване на връзката за репликация с помощта на оператор CHANGE MASTER (3setup_slave.sql).

Създайте текстов файл 1reset_master.sql и добавете следния ред:

RESET MASTER;

Създайте текстов файл 3setup_slave.sql и добавете следните редове:

CHANGE MASTER TO MASTER_HOST = '192.168.55.171', MASTER_USER = 'rpl_user', MASTER_PASSWORD = 'YlgSH6bLLy', MASTER_AUTO_POSITION = 1, MASTER_DELAY=3600;
START SLAVE;

MASTER_DELAY=3600 е равно на 1 час (в секунди). След това копирайте архивния файл, взет от нашия главен (който е прехвърлен в нашия хост Docker) в директорията "sql" и го преименувайте на 2mysqldump_complete.sql.gz:

$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-1h/sql/2mysqldump_complete.tar.gz

Крайният вид на нашата "sql" директория трябва да бъде нещо подобно:

$ pwd
/storage/mysql-slave-1h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql

Обърнете внимание, че ние поставяме префикс към името на SQL файла с цяло число, за да определим реда на изпълнение, когато Docker инициализира MySQL контейнера.

След като всичко е на мястото си, стартирайте MySQL контейнера за нашия 1-час забавен подчинен:

$ docker run -d \
--name mysql-slave-1h \
-e MYSQL_ROOT_PASSWORD=password \
--mount type=bind,source=/storage/mysql-slave-1h/datadir,target=/var/lib/mysql \
--mount type=bind,source=/storage/mysql-slave-1h/mysql.conf.d,target=/etc/mysql/mysql.conf.d \
--mount type=bind,source=/storage/mysql-slave-1h/sql,target=/docker-entrypoint-initdb.d \
mysql:5.7

** Стойността MYSQL_ROOT_PASSWORD трябва да е същата като паролата за root на MySQL на главния.

Следните редове са това, което търсим, за да проверим дали MySQL работи правилно и е свързан като подчинен към нашия главен (192.168.55.171):

$ docker logs -f mysql-slave-1h
...
2018-12-04T04:05:24.890244Z 0 [Note] mysqld: ready for connections.
Version: '5.7.24-log'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
2018-12-04T04:05:25.010032Z 2 [Note] Slave I/O thread for channel '': connected to master '[email protected]:3306',replication started in log 'FIRST' at position 4

След това можете да проверите състоянието на репликация със следния израз:

$ docker exec -it mysql-slave-1h mysql -uroot -p -e 'show slave status\G'
...
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
                    SQL_Delay: 3600
                Auto_Position: 1
...

В този момент нашите 15-минутни и 1-часови MySQL забавени подчинени контейнери се репликират от главния и нашата архитектура изглежда така:

6-часово отложено подчинено устройство

Подгответе конфигурационния файл на MySQL за нашия 6-часов отложен подчинен:

$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf

И добавете следните редове:

[mysqld]
server_id=10006
binlog_format=ROW
log_bin=binlog
log_slave_updates=1
gtid_mode=ON
enforce_gtid_consistency=1
relay_log=relay-bin
expire_logs_days=7
read_only=ON

** Стойността на сървърния идентификатор, която използвахме за това подчинено устройство, е 10006.

След това в директорията /storage/mysql-slave-6h/sql създайте два SQL файла, един за RESET MASTER (1reset_master.sql) и друг за установяване на връзката за репликация с помощта на оператор CHANGE MASTER (3setup_slave.sql).

Създайте текстов файл 1reset_master.sql и добавете следния ред:

RESET MASTER;

Създайте текстов файл 3setup_slave.sql и добавете следните редове:

CHANGE MASTER TO MASTER_HOST = '192.168.55.171', MASTER_USER = 'rpl_user', MASTER_PASSWORD = 'YlgSH6bLLy', MASTER_AUTO_POSITION = 1, MASTER_DELAY=21600;
START SLAVE;

MASTER_DELAY=21600 е равно на 6 часа (в секунди). След това копирайте архивния файл, взет от нашия главен (който е прехвърлен в нашия хост Docker) в директорията "sql" и го преименувайте на 2mysqldump_complete.sql.gz:

$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-6h/sql/2mysqldump_complete.tar.gz

Крайният вид на нашата "sql" директория трябва да бъде нещо подобно:

$ pwd
/storage/mysql-slave-6h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql

Обърнете внимание, че ние поставяме префикс към името на SQL файла с цяло число, за да определим реда на изпълнение, когато Docker инициализира MySQL контейнера.

След като всичко е на мястото си, стартирайте MySQL контейнера за нашия 6-часов отложен подчинен:

$ docker run -d \
--name mysql-slave-6h \
-e MYSQL_ROOT_PASSWORD=password \
--mount type=bind,source=/storage/mysql-slave-6h/datadir,target=/var/lib/mysql \
--mount type=bind,source=/storage/mysql-slave-6h/mysql.conf.d,target=/etc/mysql/mysql.conf.d \
--mount type=bind,source=/storage/mysql-slave-6h/sql,target=/docker-entrypoint-initdb.d \
mysql:5.7

** Стойността MYSQL_ROOT_PASSWORD трябва да е същата като паролата за root на MySQL на главния.

Следните редове са това, което търсим, за да проверим дали MySQL работи правилно и е свързан като подчинен към нашия главен (192.168.55.171):

$ docker logs -f mysql-slave-6h
...
2018-12-04T04:05:24.890244Z 0 [Note] mysqld: ready for connections.
Version: '5.7.24-log'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
2018-12-04T04:05:25.010032Z 2 [Note] Slave I/O thread for channel '': connected to master '[email protected]:3306',replication started in log 'FIRST' at position 4

След това можете да проверите състоянието на репликация със следния израз:

$ docker exec -it mysql-slave-6h mysql -uroot -p -e 'show slave status\G'
...
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
                    SQL_Delay: 21600
                Auto_Position: 1
...

В този момент нашите 5-минутни, 1-часови и 6-часови забавени подчинени контейнери се репликират правилно и нашата архитектура изглежда така:

Сценарий за аварийно възстановяване

Да приемем, че потребител случайно е изпуснал грешна колона на голяма маса. Помислете, че следното изявление е било изпълнено на главния:

mysql> USE shop;
mysql> ALTER TABLE settings DROP COLUMN status;

Ако имате достатъчно късмет да го осъзнаете незабавно, можете да използвате 15-минутното отложено подчинено устройство, за да наваксате момента преди бедствието да се случи и да го повишите в главен, или да експортирате липсващите данни и да ги възстановите на главния.

Първо, трябва да намерим позицията на двоичния журнал, преди да се случи бедствието. Вземете времето сега() на главния:

mysql> SELECT now();
+---------------------+
| now()               |
+---------------------+
| 2018-12-04 14:55:41 |
+---------------------+

След това вземете активния двоичен регистрационен файл на главния:

mysql> SHOW MASTER STATUS;
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                                                                                                                                                                     |
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| binlog.000004 | 20260658 |              |                  | 1560665e-ed2b-11e8-93fa-000c29b7f985:1-12031,
1b235f7a-d37b-11e8-9c3e-000c29bafe8f:1-62519,
1d8dc60a-e817-11e8-82ff-000c29bafe8f:1-326575,
791748b3-d37a-11e8-b03a-000c29b7f985:1-374 |
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

Използвайки същия формат за дата, извлечете информацията, която искаме от двоичния дневник, binlog.000004. Ние оценяваме началния час за четене от binlog преди около 20 минути (2018-12-04 14:35:00) и филтрираме изхода, за да покажем 25 реда преди израза "drop column":

$ mysqlbinlog --start-datetime="2018-12-04 14:35:00" --stop-datetime="2018-12-04 14:55:41" /var/lib/mysql/binlog.000004 | grep -i -B 25 "drop column"
'/*!*/;
# at 19379172
#181204 14:54:45 server id 1  end_log_pos 19379232 CRC32 0x0716e7a2     Table_map: `shop`.`settings` mapped to number 766
# at 19379232
#181204 14:54:45 server id 1  end_log_pos 19379460 CRC32 0xa6187edd     Write_rows: table id 766 flags: STMT_END_F

BINLOG '
tSQGXBMBAAAAPAAAACC0JwEAAP4CAAAAAAEABnNidGVzdAAHc2J0ZXN0MgAFAwP+/gME/nj+PBCi
5xYH
tSQGXB4BAAAA5AAAAAS1JwEAAP4CAAAAAAEAAgAF/+AYwwAAysYAAHc0ODYyMjI0NjI5OC0zNDE2
OTY3MjY5OS02MDQ1NTQwOTY1Ny01MjY2MDQ0MDcwOC05NDA0NzQzOTUwMS00OTA2MTAxNzgwNC05
OTIyMzM3NzEwOS05NzIwMzc5NTA4OC0yODAzOTU2NjQ2MC0zNzY0ODg3MTYzOTswMTM0MjAwNTcw
Ni02Mjk1ODMzMzExNi00NzQ1MjMxODA1OS0zODk4MDQwMjk5MS03OTc4MTA3OTkwNQEAAADdfhim
'/*!*/;
# at 19379460
#181204 14:54:45 server id 1  end_log_pos 19379491 CRC32 0x71f00e63     Xid = 622405
COMMIT/*!*/;
# at 19379491
#181204 14:54:46 server id 1  end_log_pos 19379556 CRC32 0x62b78c9e     GTID    last_committed=11507    sequence_number=11508   rbr_only=no
SET @@SESSION.GTID_NEXT= '1560665e-ed2b-11e8-93fa-000c29b7f985:11508'/*!*/;
# at 19379556
#181204 14:54:46 server id 1  end_log_pos 19379672 CRC32 0xc222542a     Query   thread_id=3162  exec_time=1     error_code=0
SET TIMESTAMP=1543906486/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
ALTER TABLE settings DROP COLUMN status

В долните няколко реда на изхода на mysqlbinlog трябва да имате грешната команда, която е била изпълнена на позиция 19379556. Позицията, която трябва да възстановим, е една стъпка преди това, която е в позиция 19379491. Това е позицията на binlog, където искаме нашата забавено подчинено да бъде до.

След това, на избрания отложен подчинен, спрете подчинения с отложена репликация и стартирайте отново подчинения до фиксирана крайна позиция, която разбрахме по-горе:

$ docker exec -it mysql-slave-15m mysql -uroot -p
mysql> STOP SLAVE;
mysql> START SLAVE UNTIL MASTER_LOG_FILE = 'binlog.000004', MASTER_LOG_POS = 19379491;

Наблюдавайте състоянието на репликация и изчакайте, докато Exec_Master_Log_Pos е равно на стойността Until_Log_Pos. Това може да отнеме известно време. След като настигнете, трябва да видите следното:

$ docker exec -it mysql-slave-15m mysql -uroot -p -e 'SHOW SLAVE STATUS\G'
... 
          Exec_Master_Log_Pos: 19379491
              Relay_Log_Space: 50552186
              Until_Condition: Master
               Until_Log_File: binlog.000004
                Until_Log_Pos: 19379491
...

Накрая проверете дали липсващите данни, които търсихме, са там (колона "състояние" все още съществува):

mysql> DESCRIBE shop.settings;
+--------+------------------+------+-----+---------+----------------+
| Field  | Type             | Null | Key | Default | Extra          |
+--------+------------------+------+-----+---------+----------------+
| id     | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| sid    | int(10) unsigned | NO   | MUL | 0       |                |
| param  | varchar(100)     | NO   |     |         |                |
| value  | varchar(255)     | NO   |     |         |                |
| status | int(11)          | YES  |     | 1       |                |
+--------+------------------+------+-----+---------+----------------+

След това експортирайте таблицата от нашия подчинен контейнер и я прехвърлете на главния сървър:

$ docker exec -it mysql-slave-1h mysqldump -uroot -ppassword --single-transaction shop settings > shop_settings.sql

Изхвърлете проблемната таблица и я възстановете обратно на главната:

$ mysql -uroot -p -e 'DROP TABLE shop.settings'
$ mysqldump -uroot -p -e shop < shop_setttings.sql

Сега възстановихме нашата маса обратно в първоначалното й състояние преди катастрофалното събитие. За да обобщим, забавената репликация може да се използва за няколко цели:

  • За да се предпази от потребителски грешки на главния. DBA може да върне обратно забавено подчинено устройство към времето точно преди бедствието.
  • За да тествате как се държи системата, когато има забавяне. Например, в приложение забавянето може да бъде причинено от голямо натоварване на подчинения. Въпреки това може да е трудно да се генерира това ниво на натоварване. Забавената репликация може да симулира забавянето, без да се налага да се симулира натоварването. Може да се използва и за отстраняване на грешки при условия, свързани с изоставащ подчинен.
  • За да проверите как е изглеждала базата данни в миналото, без да се налага да презареждате резервно копие. Например, ако забавянето е една седмица и DBA трябва да види как е изглеждала базата данни преди последните няколко дни на разработка, забавеният подчинен може да бъде проверен.

Последни мисли

С Docker стартирането на множество MySQL екземпляри на един и същ физически хост може да се извърши ефективно. Можете да използвате инструменти за оркестриране на Docker като Docker Compose и Swarm, за да опростите внедряването с няколко контейнера, за разлика от стъпките, показани в тази публикация в блога.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как работи SEC_TO_TIME() в MariaDB

  2. Как работи TIME_TO_SEC() в MariaDB

  3. Как QUARTER() работи в MariaDB

  4. Справяне с ненадеждни мрежи при изработване на HA решение за MySQL или MariaDB

  5. 2 начина за връщане на редове, които съдържат само буквено-цифрови знаци в MariaDB