sql >> Databasteknik >  >> RDS >> MariaDB

Flera fördröjda replikeringsslavar för katastrofåterställning med låg RTO

Fördröjd replikering tillåter en replikeringsslav att medvetet släpa efter mastern med åtminstone en specificerad tid. Innan en händelse utförs kommer slaven först att vänta, om nödvändigt, tills den givna tiden har gått sedan händelsen skapades på mastern. Resultatet är att slaven kommer att återspegla befälhavarens tillstånd en tid tillbaka i det förflutna. Denna funktion stöds sedan MySQL 5.6 och MariaDB 10.2.3. Det kan vara praktiskt vid oavsiktlig radering av data och bör vara en del av din katastrofåterställningsplan.

Problemet när man ställer in en fördröjd replikeringsslav är hur mycket fördröjning vi ska lägga på. För kort tid och du riskerar att den dåliga frågan kommer till din försenade slav innan du kan komma till den, vilket slösar bort poängen med att ha den försenade slaven. Alternativt kan du få din försenade tid att vara så lång att det tar timmar för din försenade slav att komma ikapp där befälhavaren befann sig vid tidpunkten för felet.

Som tur är med Docker är processisolering dess styrka. Att köra flera MySQL-instanser är ganska bekvämt med Docker. Det tillåter oss att ha flera fördröjda slavar inom en enda fysisk värd för att förbättra vår återhämtningstid och spara hårdvaruresurser. Om du tycker att en 15-minuters fördröjning är för kort kan vi ha en annan instans med 1 timmes fördröjning eller 6 timmar för en ännu äldre ögonblicksbild av vår databas.

I det här blogginlägget kommer vi att distribuera flera MySQL-fördröjda slavar på en enda fysisk värd med Docker, och visa några återställningsscenarier. Följande diagram illustrerar vår slutliga arkitektur som vi vill bygga:

Vår arkitektur består av en redan utrullad 2-nods MySQL-replikering som körs på fysiska servrar (blå) och vi skulle vilja sätta upp ytterligare tre MySQL-slavar (grön) med följande beteende:

  • 15 minuters fördröjning
  • 1 timmes försening
  • 6 timmars försening

Observera att vi kommer att ha 3 kopior av exakt samma data på samma fysiska server. Se till att vår Docker-värd har den lagring som krävs, så allokera tillräckligt med diskutrymme i förväg.

MySQL Master Preparation

Först loggar du in på huvudservern och skapar replikeringsanvändaren:

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

Skapa sedan en PITR-kompatibel säkerhetskopia på mastern:

$ 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

Om du använder ClusterControl kan du enkelt göra en PITR-kompatibel säkerhetskopia. Gå till Säkerhetskopiering -> Skapa säkerhetskopia och välj "Fullständig PITR-kompatibel" under rullgardinsmenyn "Dumptyp":

Överför slutligen denna säkerhetskopia till Docker-värden:

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

Denna säkerhetskopia kommer att användas av MySQL-slavbehållarna under slavstartprocessen, som visas i nästa avsnitt.

Försenad slavdistribution

Förbered våra Docker-containerkataloger. Skapa 3 kataloger (mysql.conf.d, datadir och sql) för varje MySQL-behållare som vi ska lansera (du kan använda loopen för att förenkla kommandona nedan):

$ 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"-katalogen kommer att lagra vår anpassade MySQL-konfigurationsfil och kommer att mappas till behållaren under /etc/mysql.conf.d. "datadir" är där vi vill att Docker ska lagra MySQL-datakatalogen, som mappas till /var/lib/mysql för behållaren och "sql"-katalogen lagrar våra SQL-filer - säkerhetskopieringsfiler i .sql- eller .sql.gz-format till scenen slaven före replikering och även .sql-filer för att automatisera replikeringskonfigurationen och uppstarten.

15-minuters fördröjd slav

Förbered MySQL-konfigurationsfilen för vår 15-minuters fördröjda slav:

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

Och lägg till följande rader:

[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

** Server-id-värdet vi använde för denna slav är 10015.

Skapa sedan två SQL-filer under katalogen /storage/mysql-slave-15m/sql, en för RESET MASTER (1reset_master.sql) och en annan för att upprätta replikeringslänken med CHANGE MASTER-satsen (3setup_slave.sql).

Skapa en textfil 1reset_master.sql och lägg till följande rad:

RESET MASTER;

Skapa en textfil 3setup_slave.sql och lägg till följande rader:

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 är lika med 15 minuter (i sekunder). Kopiera sedan säkerhetskopian som tagits från vår master (som har överförts till vår Docker-värd) till katalogen "sql" och döpt om den till 2mysqldump_complete.sql.gz:

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

Det slutliga utseendet på vår "sql"-katalog bör vara ungefär så här:

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

Observera att vi prefixar SQL-filnamnet med ett heltal för att bestämma exekveringsordningen när Docker initierar MySQL-behållaren.

När allt är på plats, kör MySQL-behållaren för vår 15-minuters fördröjda slav:

$ 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-värdet måste vara detsamma som MySQL root-lösenordet på mastern.

Följande rader är vad vi letar efter för att verifiera om MySQL körs korrekt och ansluten som en slav till vår master (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

Du kan sedan verifiera replikeringsstatusen med följande uttalande:

$ 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
...

Vid det här laget replikerar vår 15-minuters försenade slavbehållare korrekt och vår arkitektur ser ut ungefär så här:

1 timmes fördröjd slav

Förbered MySQL-konfigurationsfilen för vår 1-timmes fördröjda slav:

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

Och lägg till följande rader:

[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

** Server-id-värdet vi använde för denna slav är 10060.

Sedan, under katalogen /storage/mysql-slave-1h/sql, skapa två SQL-filer, en för RESET MASTER (1reset_master.sql) och en annan för att upprätta replikeringslänken med CHANGE MASTER-satsen (3setup_slave.sql).

Skapa en textfil 1reset_master.sql och lägg till följande rad:

RESET MASTER;

Skapa en textfil 3setup_slave.sql och lägg till följande rader:

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 är lika med 1 timme (i sekunder). Kopiera sedan säkerhetskopian som tagits från vår master (som har överförts till vår Docker-värd) till katalogen "sql" och döpt om den till 2mysqldump_complete.sql.gz:

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

Det slutliga utseendet på vår "sql"-katalog bör vara ungefär så här:

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

Observera att vi prefixar SQL-filnamnet med ett heltal för att bestämma exekveringsordningen när Docker initierar MySQL-behållaren.

När allt är på plats, kör MySQL-behållaren för vår 1-timmes försenade slav:

$ 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-värdet måste vara detsamma som MySQL root-lösenordet på mastern.

Följande rader är vad vi letar efter för att verifiera om MySQL körs korrekt och ansluten som en slav till vår master (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

Du kan sedan verifiera replikeringsstatusen med följande uttalande:

$ 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
...

Vid det här laget replikerar våra 15-minuters och 1-timmars MySQL-fördröjda slavbehållare från mastern och vår arkitektur ser ut ungefär så här:

6 timmars fördröjd slav

Förbered MySQL-konfigurationsfilen för vår 6-timmars fördröjda slav:

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

Och lägg till följande rader:

[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

** Server-id-värdet vi använde för denna slav är 10006.

Sedan, under katalogen /storage/mysql-slave-6h/sql, skapa två SQL-filer, en för RESET MASTER (1reset_master.sql) och en annan för att upprätta replikeringslänken med CHANGE MASTER-satsen (3setup_slave.sql).

Skapa en textfil 1reset_master.sql och lägg till följande rad:

RESET MASTER;

Skapa en textfil 3setup_slave.sql och lägg till följande rader:

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 är lika med 6 timmar (i sekunder). Kopiera sedan säkerhetskopian som tagits från vår master (som har överförts till vår Docker-värd) till katalogen "sql" och döpt om den till 2mysqldump_complete.sql.gz:

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

Det slutliga utseendet på vår "sql"-katalog bör vara ungefär så här:

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

Observera att vi prefixar SQL-filnamnet med ett heltal för att bestämma exekveringsordningen när Docker initierar MySQL-behållaren.

När allt är på plats, kör MySQL-behållaren för vår 6-timmars försenade slav:

$ 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-värdet måste vara detsamma som MySQL root-lösenordet på mastern.

Följande rader är vad vi letar efter för att verifiera om MySQL körs korrekt och ansluten som en slav till vår master (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

Du kan sedan verifiera replikeringsstatusen med följande uttalande:

$ 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
...

Vid det här laget replikerar våra 5 minuter, 1 timme och 6 timmar försenade slavcontainrar korrekt och vår arkitektur ser ut ungefär så här:

Scenario för katastrofåterställning

Låt oss säga att en användare av misstag har tappat en fel kolumn på ett stort bord. Tänk på att följande uttalande kördes på mastern:

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

Om du har turen att inse det omedelbart, kan du använda den 15-minuters fördröjda slaven för att komma ikapp ögonblicket innan katastrofen inträffar och främja den till att bli master, eller exportera den saknade informationen och återställa den på mastern.

För det första måste vi hitta den binära loggpositionen innan katastrofen inträffade. Ta tiden now() på mastern:

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

Hämta sedan den aktiva binära loggfilen på mastern:

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 |
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

Använd samma datumformat och extrahera informationen vi vill ha från den binära loggen, binlog.000004. Vi uppskattar starttiden för att läsa från binloggen för cirka 20 minuter sedan (2018-12-04 14:35:00) och filtrerar utdata för att visa 25 rader före "släpp kolumn"-satsen:

$ 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

På de nedre raderna av mysqlbinlog-utgången bör du ha det felaktiga kommandot som kördes vid position 19379556. Positionen som vi bör återställa är ett steg före detta, som är i position 19379491. Detta är binlog-positionen där vi vill ha vår fördröjd slav att vara upp till.

Sedan, på den valda fördröjda slaven, stoppa den fördröjda replikeringsslaven och starta om slaven till en fast slutposition som vi räknat ut ovan:

$ 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;

Övervaka replikeringsstatusen och vänta tills Exec_Master_Log_Pos är lika med Until_Log_Pos-värdet. Det här kan ta lite tid. När du är ikapp bör du se följande:

$ 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
...

Kontrollera slutligen om den saknade informationen som vi letade efter finns där (kolumnen "status" finns fortfarande):

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       |                |
+--------+------------------+------+-----+---------+----------------+

Exportera sedan tabellen från vår slavbehållare och överför den till masterservern:

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

Släpp den problematiska tabellen och återställ den på mastern:

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

Vi har nu återställt vårt bord till sitt ursprungliga skick innan den katastrofala händelsen. Sammanfattningsvis kan fördröjd replikering användas för flera syften:

  • För att skydda mot användarmisstag på mastern. En DBA kan rulla tillbaka en fördröjd slav till tiden precis före katastrofen.
  • För att testa hur systemet beter sig när det finns en fördröjning. Till exempel, i en applikation, kan en fördröjning orsakas av en tung belastning på slaven. Det kan dock vara svårt att generera denna belastningsnivå. Fördröjd replikering kan simulera fördröjningen utan att behöva simulera belastningen. Den kan också användas för att felsöka förhållanden relaterade till en eftersläpande slav.
  • För att inspektera hur databasen såg ut tidigare, utan att behöva ladda om en säkerhetskopia. Till exempel, om förseningen är en vecka och DBA behöver se hur databasen såg ut innan de senaste dagarnas utveckling, kan den försenade slaven inspekteras.

Sluta tankar

Med Docker kan körning av flera MySQL-instanser på samma fysiska värd göras effektivt. Du kan använda Docker-orkestreringsverktyg som Docker Compose och Swarm för att förenkla distributionen av flera behållare i motsats till stegen som visas i det här blogginlägget.


  1. MySQL GÅ MED PÅ kontra ANVÄNDA?

  2. MySQL – ELT() och FILED() funktioner för att extrahera indexposition från listan

  3. Hämta senast infogade id med Mysql

  4. Skillnaden mellan lokala och globala tillfälliga tabeller i SQL Server