Forsinket replikering gør det muligt for en replikeringsslave bevidst at halte bagud masteren med mindst en specificeret tid. Før en hændelse udføres, vil slaven først vente, hvis det er nødvendigt, indtil den givne tid er gået, siden hændelsen blev oprettet på masteren. Resultatet er, at slaven vil afspejle mesterens tilstand et stykke tid tilbage i fortiden. Denne funktion er understøttet siden MySQL 5.6 og MariaDB 10.2.3. Det kan være nyttigt i tilfælde af utilsigtet sletning af data og bør være en del af din katastrofegenopretningsplan.
Problemet ved opsætning af en forsinket replikeringsslave er, hvor meget forsinkelse vi skal bruge. For kort tid, og du risikerer, at den dårlige forespørgsel når til din forsinkede slave, før du kan nå den, og dermed spilder pointen med at have den forsinkede slave. Du kan eventuelt få din forsinkede tid til at være så lang, at det tager timer for din forsinkede slave at indhente, hvor masteren var på fejltidspunktet.
Heldigvis med Docker er procesisolering dens styrke. At køre flere MySQL-instanser er ret praktisk med Docker. Det giver os mulighed for at have flere forsinkede slaver inden for en enkelt fysisk vært for at forbedre vores restitutionstid og spare hardwareressourcer. Hvis du synes, en 15-minutters forsinkelse er for kort, kan vi have en anden instans med 1-times forsinkelse eller 6-timers for et endnu ældre øjebliksbillede af vores database.
I dette blogindlæg vil vi implementere flere MySQL-forsinkede slaver på én enkelt fysisk vært med Docker og vise nogle gendannelsesscenarier. Følgende diagram illustrerer vores endelige arkitektur, som vi ønsker at bygge:
Vores arkitektur består af en allerede implementeret 2-node MySQL-replikering, der kører på fysiske servere (blå), og vi vil gerne opsætte yderligere tre MySQL-slaver (grøn) med følgende adfærd:
- 15 minutters forsinkelse
- 1 times forsinkelse
- 6 timers forsinkelse
Bemærk, at vi kommer til at have 3 kopier af nøjagtig de samme data på den samme fysiske server. Sørg for, at vores Docker-vært har den nødvendige lagerplads, så tildel tilstrækkelig diskplads på forhånd.
MySQL Master Forberedelse
Først skal du logge ind på masterserveren og oprette replikeringsbrugeren:
mysql> GRANT REPLICATION SLAVE ON *.* TO [email protected]'%' IDENTIFIED BY 'YlgSH6bLLy';
Opret derefter en PITR-kompatibel sikkerhedskopi på masteren:
$ 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
Hvis du bruger ClusterControl, kan du nemt lave en PITR-kompatibel backup. Gå til Backups -> Create Backup, og vælg "Complete PITR-compatible" under "Dump Type" dropdown-menuen:
Til sidst skal du overføre denne sikkerhedskopi til Docker-værten:
$ scp mysqldump_complete.sql.gz [email protected]:~
Denne backup-fil vil blive brugt af MySQL-slave-beholderne under slave-bootstrapping-processen, som vist i næste afsnit.
Forsinket slaveimplementering
Forbered vores Docker container mapper. Opret 3 mapper (mysql.conf.d, datadir og sql) for hver MySQL-container, som vi vil starte (du kan bruge loop til at forenkle kommandoerne nedenfor):
$ 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" mappen vil gemme vores brugerdefinerede MySQL konfigurationsfil og vil blive mappet til containeren under /etc/mysql.conf.d. "datadir" er det sted, hvor vi ønsker, at Docker skal gemme MySQL-datamappen, som er knyttet til /var/lib/mysql af containeren, og "sql"-mappen gemmer vores SQL-filer - backup-filer i .sql- eller .sql.gz-format til scenen slaven før replikering og også .sql-filer for at automatisere replikeringskonfigurationen og opstarten.
15-minutters forsinket slave
Forbered MySQL-konfigurationsfilen til vores 15-minutters forsinkede slave:
$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf
Og tilføj følgende linjer:
[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
** Den server-id-værdi, vi brugte til denne slave, er 10015.
Derefter, under /storage/mysql-slave-15m/sql bibliotek, skal du oprette to SQL-filer, en til RESET MASTER (1reset_master.sql) og en anden for at etablere replikeringslinket ved hjælp af CHANGE MASTER-sætningen (3setup_slave.sql).
Opret en tekstfil 1reset_master.sql og tilføj følgende linje:
RESET MASTER;
Opret en tekstfil 3setup_slave.sql og tilføj følgende linjer:
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 er lig med 15 minutter (i sekunder). Kopier derefter backupfilen taget fra vores master (som er blevet overført til vores Docker-vært) til "sql"-mappen og omdøbt den til 2mysqldump_complete.sql.gz:
$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-15m/sql/2mysqldump_complete.tar.gz
Det endelige udseende af vores "sql"-mappe skulle være sådan her:
$ pwd
/storage/mysql-slave-15m/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql
Bemærk, at vi præfikser SQL-filnavnet med et heltal for at bestemme udførelsesrækkefølgen, når Docker initialiserer MySQL-beholderen.
Når alt er på plads, skal du køre MySQL-containeren til vores 15-minutters forsinkede slave:
$ 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ærdien skal være den samme som MySQL root-adgangskoden på masteren.
De følgende linjer er, hvad vi leder efter for at verificere, om MySQL kører korrekt og er forbundet som en slave til vores 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 derefter bekræfte replikeringsstatussen med følgende sætning:
$ 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
...
På dette tidspunkt replikerer vores 15-minutters forsinkede slavebeholder korrekt, og vores arkitektur ser nogenlunde sådan ud:
1-times forsinket slave
Forbered MySQL-konfigurationsfilen til vores 1-times forsinkede slave:
$ vim /storage/mysql-slave-1h/mysql.conf.d/my.cnf
Og tilføj følgende linjer:
[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
** Den server-id-værdi, vi brugte til denne slave, er 10060.
Derefter, under /storage/mysql-slave-1h/sql-biblioteket, skal du oprette to SQL-filer, en til RESET MASTER (1reset_master.sql) og en anden for at etablere replikeringslinket ved hjælp af CHANGE MASTER-sætningen (3setup_slave.sql).
Opret en tekstfil 1reset_master.sql og tilføj følgende linje:
RESET MASTER;
Opret en tekstfil 3setup_slave.sql og tilføj følgende linjer:
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 er lig med 1 time (i sekunder). Kopier derefter backupfilen taget fra vores master (som er blevet overført til vores Docker-vært) til "sql"-mappen og omdøbt den til 2mysqldump_complete.sql.gz:
$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-1h/sql/2mysqldump_complete.tar.gz
Det endelige udseende af vores "sql"-mappe skulle være sådan her:
$ pwd
/storage/mysql-slave-1h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql
Bemærk, at vi præfikser SQL-filnavnet med et heltal for at bestemme udførelsesrækkefølgen, når Docker initialiserer MySQL-beholderen.
Når alt er på plads, skal du køre MySQL-containeren til vores 1-times forsinkede slave:
$ 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ærdien skal være den samme som MySQL root-adgangskoden på masteren.
De følgende linjer er, hvad vi leder efter for at verificere, om MySQL kører korrekt og er forbundet som en slave til vores 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 derefter bekræfte replikeringsstatussen med følgende sætning:
$ 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
...
På dette tidspunkt kopierer vores 15-minutters og 1-times MySQL-forsinkede slavebeholdere fra masteren, og vores arkitektur ser sådan her ud:
6-timers forsinket slave
Forbered MySQL-konfigurationsfilen til vores 6-timers forsinkede slave:
$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf
Og tilføj følgende linjer:
[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ærdien, vi brugte til denne slave, er 10006.
Derefter, under mappen /storage/mysql-slave-6h/sql, skal du oprette to SQL-filer, en til RESET MASTER (1reset_master.sql) og en anden for at etablere replikeringslinket ved hjælp af CHANGE MASTER-sætningen (3setup_slave.sql).
Opret en tekstfil 1reset_master.sql og tilføj følgende linje:
RESET MASTER;
Opret en tekstfil 3setup_slave.sql og tilføj følgende linjer:
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 er lig med 6 timer (i sekunder). Kopier derefter backupfilen taget fra vores master (som er blevet overført til vores Docker-vært) til "sql"-mappen og omdøbt den til 2mysqldump_complete.sql.gz:
$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-6h/sql/2mysqldump_complete.tar.gz
Det endelige udseende af vores "sql"-mappe skulle være sådan her:
$ pwd
/storage/mysql-slave-6h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql
Bemærk, at vi præfikser SQL-filnavnet med et heltal for at bestemme udførelsesrækkefølgen, når Docker initialiserer MySQL-beholderen.
Når alt er på plads, skal du køre MySQL-containeren til vores 6-timers forsinkede slave:
$ 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ærdien skal være den samme som MySQL root-adgangskoden på masteren.
De følgende linjer er, hvad vi leder efter for at verificere, om MySQL kører korrekt og er forbundet som en slave til vores 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 derefter bekræfte replikeringsstatussen med følgende sætning:
$ 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
...
På dette tidspunkt replikerer vores 5 minutter, 1 time og 6 timers forsinkede slavecontainere korrekt, og vores arkitektur ser nogenlunde sådan her ud:
Cenario for gendannelse efter katastrofe
Lad os sige, at en bruger ved et uheld har tabt en forkert kolonne på et stort bord. Overvej, at følgende erklæring blev udført på masteren:
mysql> USE shop;
mysql> ALTER TABLE settings DROP COLUMN status;
Hvis du er så heldig at indse det med det samme, kan du bruge den 15-minutters forsinkede slave til at indhente det øjeblik, før katastrofen sker, og fremme den til at blive master, eller eksportere de manglende data ud og gendanne dem på masteren.
For det første skal vi finde den binære logposition, før katastrofen skete. Grib tiden now() på masteren:
mysql> SELECT now();
+---------------------+
| now() |
+---------------------+
| 2018-12-04 14:55:41 |
+---------------------+
Hent derefter den aktive binære logfil på masteren:
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 |
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Brug det samme datoformat til at udtrække de oplysninger, vi ønsker, fra den binære log, binlog.000004. Vi estimerer starttidspunktet for at læse fra binlogen for omkring 20 minutter siden (2018-12-04 14:35:00) og filtrerer outputtet til at vise 25 linjer før "drop kolonne"-sætningen:
$ 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
I de nederste par linjer i mysqlbinlog-outputtet skulle du have den fejlagtige kommando, der blev udført ved position 19379556. Den position, som vi skal gendanne, er et trin før dette, som er i position 19379491. Dette er binlog-positionen, hvor vi vil have vores forsinket slave at være op til.
Stop derefter den forsinkede replikeringsslave på den valgte forsinkede slave og start slaven igen til en fast slutposition, som vi fandt ud af ovenfor:
$ 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;
Overvåg replikeringsstatus og vent, indtil Exec_Master_Log_Pos er lig med Until_Log_Pos-værdien. Dette kan tage noget tid. Når du er indhentet, bør du se følgende:
$ 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
...
Bekræft endelig, om de manglende data, som vi ledte efter, er der (kolonnen "status" eksisterer stadig):
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 | |
+--------+------------------+------+-----+---------+----------------+
Eksporter derefter tabellen fra vores slavebeholder og overfør den til masterserveren:
$ docker exec -it mysql-slave-1h mysqldump -uroot -ppassword --single-transaction shop settings > shop_settings.sql
Slip den problematiske tabel og gendan den tilbage på masteren:
$ mysql -uroot -p -e 'DROP TABLE shop.settings'
$ mysqldump -uroot -p -e shop < shop_setttings.sql
Vi har nu genoprettet vores bord til dets oprindelige tilstand før den katastrofale begivenhed. For at opsummere kan forsinket replikering bruges til flere formål:
- For at beskytte mod brugerfejl på masteren. En DBA kan rulle en forsinket slave tilbage til tiden lige før katastrofen.
- For at teste, hvordan systemet opfører sig, når der er forsinkelse. For eksempel kan en forsinkelse i en applikation være forårsaget af en stor belastning på slaven. Det kan dog være svært at generere dette belastningsniveau. Forsinket replikering kan simulere forsinkelsen uden at skulle simulere belastningen. Den kan også bruges til at fejlsøge forhold relateret til en haltende slave.
- For at inspicere, hvordan databasen så ud i fortiden, uden at skulle genindlæse en sikkerhedskopi. For eksempel, hvis forsinkelsen er en uge, og DBA skal se, hvordan databasen så ud før de seneste dages udvikling, kan den forsinkede slave inspiceres.
Sidste tanker
Med Docker kan kørsel af flere MySQL-instanser på en samme fysiske vært udføres effektivt. Du kan bruge Docker-orkestreringsværktøjer som Docker Compose og Swarm til at forenkle multi-container-implementeringen i modsætning til trinene vist i dette blogindlæg.