MariaDB
 sql >> Database >  >> RDS >> MariaDB

Più slave di replica ritardata per il ripristino di emergenza con RTO basso

La replica ritardata consente a uno slave di replica di rimanere deliberatamente indietro rispetto al master di almeno un determinato periodo di tempo. Prima di eseguire un evento, lo slave attenderà prima, se necessario, che sia trascorso il tempo stabilito dalla creazione dell'evento sul master. Il risultato è che lo slave rifletterà lo stato del master qualche tempo fa. Questa funzionalità è supportata da MySQL 5.6 e MariaDB 10.2.3. Può tornare utile in caso di eliminazione accidentale dei dati e dovrebbe far parte del tuo piano di ripristino di emergenza.

Il problema quando si imposta uno slave di replica ritardato è quanto ritardo dovremmo mettere. Troppo poco tempo e rischi che la domanda sbagliata arrivi al tuo schiavo ritardato prima che tu possa raggiungerlo, sprecando così il punto di avere lo schiavo ritardato. Facoltativamente, puoi fare in modo che il tuo ritardo sia così lungo da impiegare ore prima che lo slave ritardato raggiunga il punto in cui si trovava il master al momento dell'errore.

Fortunatamente con Docker, l'isolamento dei processi è il suo punto di forza. L'esecuzione di più istanze MySQL è piuttosto conveniente con Docker. Ci consente di avere più slave ritardati all'interno di un singolo host fisico per migliorare i nostri tempi di ripristino e risparmiare risorse hardware. Se ritieni che un ritardo di 15 minuti sia troppo breve, possiamo avere un'altra istanza con un ritardo di 1 ora o 6 ore per uno snapshot ancora più vecchio del nostro database.

In questo post del blog, implementeremo più slave ritardati MySQL su un singolo host fisico con Docker e mostreremo alcuni scenari di ripristino. Il diagramma seguente illustra la nostra architettura finale che vogliamo costruire:

La nostra architettura consiste in una replica MySQL a 2 nodi già implementata in esecuzione su server fisici (blu) e vorremmo configurare altri tre slave MySQL (verde) con il seguente comportamento:

  • 15 minuti di ritardo
  • 1 ora di ritardo
  • 6 ore di ritardo

Prendi nota che avremo 3 copie degli stessi identici dati sullo stesso server fisico. Assicurati che il nostro host Docker disponga dello spazio di archiviazione richiesto, quindi alloca in anticipo spazio su disco sufficiente.

Preparazione MySQL Master

Innanzitutto, accedi al server principale e crea l'utente di replica:

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

Quindi, crea un backup compatibile con PITR sul master:

$ 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

Se si utilizza ClusterControl, è possibile eseguire facilmente un backup compatibile con PITR. Vai su Backup -> Crea backup e seleziona "Completa compatibile con PITR" nell'elenco a discesa "Tipo dump":

Infine, trasferisci questo backup sull'host Docker:

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

Questo file di backup verrà utilizzato dai container slave MySQL durante il processo di bootstrap degli slave, come mostrato nella sezione successiva.

Distribuzione slave ritardata

Prepara le nostre directory dei container Docker. Crea 3 directory (mysql.conf.d, datadir e sql) per ogni container MySQL che stiamo per lanciare (puoi usare loop per semplificare i comandi seguenti):

$ 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

La directory "mysql.conf.d" memorizzerà il nostro file di configurazione MySQL personalizzato e verrà mappata nel contenitore in /etc/mysql.conf.d. "datadir" è il punto in cui vogliamo che Docker memorizzi la directory dei dati MySQL, che esegue il mapping a /var/lib/mysql del contenitore e la directory "sql" memorizza i nostri file SQL - file di backup in formato .sql o .sql.gz per lo stage lo slave prima della replica e anche i file .sql per automatizzare la configurazione della replica e l'avvio.

Slave ritardato di 15 minuti

Prepara il file di configurazione MySQL per il nostro slave ritardato di 15 minuti:

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

E aggiungi le seguenti righe:

[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

** Il valore server-id che abbiamo utilizzato per questo slave è 10015.

Quindi, nella directory /storage/mysql-slave-15m/sql, crea due file SQL, uno per RESET MASTER (1reset_master.sql) e un altro per stabilire il collegamento di replica utilizzando l'istruzione CHANGE MASTER (3setup_slave.sql).

Crea un file di testo 1reset_master.sql e aggiungi la seguente riga:

RESET MASTER;

Crea un file di testo 3setup_slave.sql e aggiungi le seguenti righe:

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 è pari a 15 minuti (in secondi). Quindi copia il file di backup preso dal nostro master (che è stato trasferito nel nostro host Docker) nella directory "sql" e rinominalo come 2mysqldump_complete.sql.gz:

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

L'aspetto finale della nostra directory "sql" dovrebbe essere simile a questo:

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

Tieni presente che anteponiamo al nome del file SQL un numero intero per determinare l'ordine di esecuzione quando Docker inizializza il contenitore MySQL.

Una volta che tutto è a posto, esegui il contenitore MySQL per il nostro slave ritardato di 15 minuti:

$ 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

** Il valore MYSQL_ROOT_PASSWORD deve essere uguale alla password di root MySQL sul master.

Le seguenti righe sono ciò che stiamo cercando per verificare se MySQL funziona correttamente e connesso come slave al nostro 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

È quindi possibile verificare lo stato della replica con la seguente istruzione:

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

A questo punto, il nostro container slave ritardato di 15 minuti si sta replicando correttamente e la nostra architettura è simile a questa:

Slave ritardato di 1 ora

Prepara il file di configurazione MySQL per il nostro slave ritardato di 1 ora:

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

E aggiungi le seguenti righe:

[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

** Il valore server-id che abbiamo utilizzato per questo slave è 10060.

Quindi, nella directory /storage/mysql-slave-1h/sql, crea due file SQL, uno per RESET MASTER (1reset_master.sql) e un altro per stabilire il collegamento di replica utilizzando l'istruzione CHANGE MASTER (3setup_slave.sql).

Crea un file di testo 1reset_master.sql e aggiungi la seguente riga:

RESET MASTER;

Crea un file di testo 3setup_slave.sql e aggiungi le seguenti righe:

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 è uguale a 1 ora (in secondi). Quindi copia il file di backup preso dal nostro master (che è stato trasferito nel nostro host Docker) nella directory "sql" e rinominalo come 2mysqldump_complete.sql.gz:

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

L'aspetto finale della nostra directory "sql" dovrebbe essere simile a questo:

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

Tieni presente che anteponiamo al nome del file SQL un numero intero per determinare l'ordine di esecuzione quando Docker inizializza il contenitore MySQL.

Una volta che tutto è a posto, esegui il contenitore MySQL per il nostro slave ritardato di 1 ora:

$ 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

** Il valore MYSQL_ROOT_PASSWORD deve essere uguale alla password di root MySQL sul master.

Le seguenti righe sono ciò che stiamo cercando per verificare se MySQL funziona correttamente e connesso come slave al nostro 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

È quindi possibile verificare lo stato della replica con la seguente istruzione:

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

A questo punto, i nostri container MySQL slave ritardati di 15 minuti e 1 ora si stanno replicando dal master e la nostra architettura è simile a questa:

Slave ritardato di 6 ore

Prepara il file di configurazione MySQL per il nostro slave ritardato di 6 ore:

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

E aggiungi le seguenti righe:

[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

** Il valore server-id che abbiamo utilizzato per questo slave è 10006.

Quindi, nella directory /storage/mysql-slave-6h/sql, crea due file SQL, uno per RESET MASTER (1reset_master.sql) e un altro per stabilire il collegamento di replica utilizzando l'istruzione CHANGE MASTER (3setup_slave.sql).

Crea un file di testo 1reset_master.sql e aggiungi la seguente riga:

RESET MASTER;

Crea un file di testo 3setup_slave.sql e aggiungi le seguenti righe:

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 è pari a 6 ore (in secondi). Quindi copia il file di backup preso dal nostro master (che è stato trasferito nel nostro host Docker) nella directory "sql" e rinominalo come 2mysqldump_complete.sql.gz:

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

L'aspetto finale della nostra directory "sql" dovrebbe essere simile a questo:

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

Tieni presente che anteponiamo al nome del file SQL un numero intero per determinare l'ordine di esecuzione quando Docker inizializza il contenitore MySQL.

Una volta che tutto è a posto, esegui il contenitore MySQL per il nostro slave ritardato di 6 ore:

$ 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

** Il valore MYSQL_ROOT_PASSWORD deve essere uguale alla password di root MySQL sul master.

Le seguenti righe sono ciò che stiamo cercando per verificare se MySQL funziona correttamente e connesso come slave al nostro 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

È quindi possibile verificare lo stato della replica con la seguente istruzione:

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

A questo punto, i nostri container slave ritardati di 5 minuti, 1 ora e 6 ore si stanno replicando correttamente e la nostra architettura è simile a questa:

Scenario di ripristino di emergenza

Diciamo che un utente ha accidentalmente eliminato una colonna sbagliata su un grande tavolo. Considera che la seguente istruzione è stata eseguita sul master:

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

Se sei abbastanza fortunato da realizzarlo immediatamente, puoi utilizzare lo slave ritardato di 15 minuti per recuperare il momento prima che si verifichi il disastro e promuoverlo a diventare master, oppure esportare i dati mancanti e ripristinarli sul master.

In primo luogo, dobbiamo trovare la posizione del log binario prima che si verificasse il disastro. Prendi il tempo ora() sul master:

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

Quindi, ottieni il file di registro binario attivo sul master:

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

Utilizzando lo stesso formato della data, estrai le informazioni che desideriamo dal log binario, binlog.000004. Stimiamo l'ora di inizio per leggere dal binlog circa 20 minuti fa (2018-12-04 14:35:00) e filtriamo l'output per mostrare 25 righe prima dell'istruzione "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

Nelle poche righe inferiori dell'output di mysqlbinlog, dovresti avere il comando errato che è stato eseguito nella posizione 19379556. La posizione che dovremmo ripristinare è un passaggio prima di questo, che è nella posizione 19379491. Questa è la posizione binlog in cui vogliamo il nostro slave ritardato da rispettare.

Quindi, sullo slave ritardato scelto, interrompere lo slave di replica ritardata e riavviare lo slave su una posizione finale fissa che abbiamo calcolato sopra:

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

Monitorare lo stato della replica e attendere fino a quando Exec_Master_Log_Pos è uguale al valore Until_Log_Pos. Questo potrebbe richiedere del tempo. Una volta raggiunto, dovresti vedere quanto segue:

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

Infine verifica se i dati mancanti che stavamo cercando sono presenti (la colonna "status" esiste ancora):

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

Quindi esporta la tabella dal nostro container slave e trasferiscila al server master:

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

Rilascia la tabella problematica e ripristinala sul master:

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

Ora abbiamo riportato la nostra tavola al suo stato originale prima dell'evento disastroso. Per riassumere, la replica ritardata può essere utilizzata per diversi scopi:

  • Per proteggere dagli errori dell'utente sul master. Un DBA può riportare uno slave ritardato all'ora precedente al disastro.
  • Per testare come si comporta il sistema in caso di ritardo. Ad esempio, in un'applicazione, un ritardo potrebbe essere causato da un carico pesante sullo slave. Tuttavia, può essere difficile generare questo livello di carico. La replica ritardata può simulare il ritardo senza dover simulare il carico. Può anche essere utilizzato per eseguire il debug di condizioni relative a uno slave in ritardo.
  • Per ispezionare l'aspetto del database in passato, senza dover ricaricare un backup. Ad esempio, se il ritardo è di una settimana e il DBA ha bisogno di vedere come appariva il database prima del periodo di sviluppo degli ultimi giorni, lo slave ritardato può essere ispezionato.

Pensieri finali

Con Docker, l'esecuzione di più istanze MySQL su uno stesso host fisico può essere eseguita in modo efficiente. Puoi utilizzare strumenti di orchestrazione Docker come Docker Compose e Swarm per semplificare la distribuzione multi-container al contrario dei passaggi illustrati in questo post del blog.