MariaDB
 sql >> Base de données >  >> RDS >> MariaDB

Plusieurs esclaves à réplication retardée pour la reprise après sinistre avec un faible RTO

La réplication retardée permet à un esclave de réplication d'être délibérément en retard sur le maître d'au moins une durée spécifiée. Avant d'exécuter un événement, l'esclave attendra d'abord, si nécessaire, que le temps imparti se soit écoulé depuis la création de l'événement sur le maître. Le résultat est que l'esclave reflétera l'état du maître il y a quelque temps dans le passé. Cette fonctionnalité est prise en charge depuis MySQL 5.6 et MariaDB 10.2.3. Cela peut s'avérer utile en cas de suppression accidentelle de données et devrait faire partie de votre plan de reprise après sinistre.

Le problème lors de la configuration d'un esclave à réplication retardée est le délai à appliquer. Trop peu de temps et vous risquez que la mauvaise requête atteigne votre esclave retardé avant que vous ne puissiez y accéder, perdant ainsi le point d'avoir l'esclave retardé. En option, vous pouvez faire en sorte que votre temps de retard soit si long qu'il faut des heures à votre esclave retardé pour rattraper l'endroit où se trouvait le maître au moment de l'erreur.

Heureusement avec Docker, l'isolation des processus est sa force. L'exécution de plusieurs instances MySQL est assez pratique avec Docker. Cela nous permet d'avoir plusieurs esclaves retardés au sein d'un seul hôte physique pour améliorer notre temps de récupération et économiser les ressources matérielles. Si vous pensez qu'un délai de 15 minutes est trop court, nous pouvons avoir une autre instance avec un délai de 1 heure ou 6 heures pour un instantané encore plus ancien de notre base de données.

Dans cet article de blog, nous allons déployer plusieurs esclaves retardés MySQL sur un seul hôte physique avec Docker et montrer quelques scénarios de récupération. Le schéma suivant illustre notre architecture finale que nous souhaitons construire :

Notre architecture consiste en une réplication MySQL à 2 nœuds déjà déployée s'exécutant sur des serveurs physiques (bleu) et nous aimerions configurer trois autres esclaves MySQL (vert) avec le comportement suivant :

  • 15 minutes de retard
  • 1 heure de retard
  • 6 heures de retard

Prenez note que nous allons avoir 3 copies des mêmes données exactes sur le même serveur physique. Assurez-vous que notre hôte Docker dispose de l'espace de stockage requis, allouez donc suffisamment d'espace disque au préalable.

Maîtrise MySQL

Tout d'abord, connectez-vous au serveur maître et créez l'utilisateur de réplication :

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

Créez ensuite une sauvegarde compatible PITR sur le maître :

$ 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

Si vous utilisez ClusterControl, vous pouvez facilement effectuer une sauvegarde compatible PITR. Allez dans Sauvegardes > Créer une sauvegarde et sélectionnez "Compatible PITR complet" dans la liste déroulante "Type de vidage" :

Enfin, transférez cette sauvegarde vers l'hôte Docker :

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

Ce fichier de sauvegarde sera utilisé par les conteneurs esclaves MySQL pendant le processus d'amorçage de l'esclave, comme indiqué dans la section suivante.

Déploiement retardé de l'esclave

Préparez nos répertoires de conteneurs Docker. Créez 3 répertoires (mysql.conf.d, datadir et sql) pour chaque conteneur MySQL que nous allons lancer (vous pouvez utiliser loop pour simplifier les commandes ci-dessous) :

$ 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

Le répertoire "mysql.conf.d" stockera notre fichier de configuration MySQL personnalisé et sera mappé dans le conteneur sous /etc/mysql.conf.d. "datadir" est l'endroit où nous voulons que Docker stocke le répertoire de données MySQL, qui correspond à /var/lib/mysql du conteneur et le répertoire "sql" stocke nos fichiers SQL - fichiers de sauvegarde au format .sql ou .sql.gz à mettre en scène l'esclave avant la réplication ainsi que des fichiers .sql pour automatiser la configuration et le démarrage de la réplication.

Esclave retardé de 15 minutes

Préparez le fichier de configuration MySQL pour notre esclave retardé de 15 minutes :

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

Et ajoutez les lignes suivantes :

[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

** La valeur d'ID de serveur que nous avons utilisée pour cet esclave est 10015.

Ensuite, sous le répertoire /storage/mysql-slave-15m/sql, créez deux fichiers SQL, un pour RESET MASTER (1reset_master.sql) et un autre pour établir le lien de réplication à l'aide de l'instruction CHANGE MASTER (3setup_slave.sql).

Créez un fichier texte 1reset_master.sql et ajoutez la ligne suivante :

RESET MASTER;

Créez un fichier texte 3setup_slave.sql et ajoutez les lignes suivantes :

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 est égal à 15 minutes (en secondes). Ensuite, copiez le fichier de sauvegarde extrait de notre maître (qui a été transféré dans notre hôte Docker) dans le répertoire "sql" et renommez-le en 2mysqldump_complete.sql.gz :

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

L'aspect final de notre répertoire "sql" devrait ressembler à ceci :

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

Notez que nous préfixons le nom de fichier SQL avec un entier pour déterminer l'ordre d'exécution lorsque Docker initialise le conteneur MySQL.

Une fois que tout est en place, exécutez le conteneur MySQL pour notre esclave retardé de 15 minutes :

$ 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

** La valeur MYSQL_ROOT_PASSWORD doit être identique au mot de passe racine MySQL sur le maître.

Les lignes suivantes sont ce que nous recherchons pour vérifier si MySQL fonctionne correctement et connecté en tant qu'esclave à notre maître (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

Vous pouvez ensuite vérifier l'état de la réplication avec la déclaration suivante :

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

À ce stade, notre conteneur esclave retardé de 15 minutes se réplique correctement et notre architecture ressemble à ceci :

Esclave retardé d'une heure

Préparez le fichier de configuration MySQL pour notre esclave retardé d'une heure :

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

Et ajoutez les lignes suivantes :

[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

** La valeur d'ID de serveur que nous avons utilisée pour cet esclave est 10060.

Ensuite, sous le répertoire /storage/mysql-slave-1h/sql, créez deux fichiers SQL, un pour RESET MASTER (1reset_master.sql) et un autre pour établir le lien de réplication à l'aide de l'instruction CHANGE MASTER (3setup_slave.sql).

Créez un fichier texte 1reset_master.sql et ajoutez la ligne suivante :

RESET MASTER;

Créez un fichier texte 3setup_slave.sql et ajoutez les lignes suivantes :

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 est égal à 1 heure (en secondes). Ensuite, copiez le fichier de sauvegarde extrait de notre maître (qui a été transféré dans notre hôte Docker) dans le répertoire "sql" et renommez-le en 2mysqldump_complete.sql.gz :

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

L'aspect final de notre répertoire "sql" devrait ressembler à ceci :

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

Notez que nous préfixons le nom de fichier SQL avec un entier pour déterminer l'ordre d'exécution lorsque Docker initialise le conteneur MySQL.

Une fois que tout est en place, exécutez le conteneur MySQL pour notre esclave retardé d'une heure :

$ 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

** La valeur MYSQL_ROOT_PASSWORD doit être identique au mot de passe racine MySQL sur le maître.

Les lignes suivantes sont ce que nous recherchons pour vérifier si MySQL fonctionne correctement et connecté en tant qu'esclave à notre maître (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

Vous pouvez ensuite vérifier l'état de la réplication avec la déclaration suivante :

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

À ce stade, nos conteneurs esclaves retardés MySQL de 15 minutes et 1 heure se répliquent à partir du maître et notre architecture ressemble à ceci :

Esclave retardé de 6 heures

Préparez le fichier de configuration MySQL pour notre esclave retardé de 6 heures :

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

Et ajoutez les lignes suivantes :

[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

** La valeur d'ID de serveur que nous avons utilisée pour cet esclave est 10006.

Ensuite, sous le répertoire /storage/mysql-slave-6h/sql, créez deux fichiers SQL, un pour RESET MASTER (1reset_master.sql) et un autre pour établir le lien de réplication à l'aide de l'instruction CHANGE MASTER (3setup_slave.sql).

Créez un fichier texte 1reset_master.sql et ajoutez la ligne suivante :

RESET MASTER;

Créez un fichier texte 3setup_slave.sql et ajoutez les lignes suivantes :

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 est égal à 6 heures (en secondes). Ensuite, copiez le fichier de sauvegarde extrait de notre maître (qui a été transféré dans notre hôte Docker) dans le répertoire "sql" et renommez-le en 2mysqldump_complete.sql.gz :

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

L'aspect final de notre répertoire "sql" devrait ressembler à ceci :

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

Notez que nous préfixons le nom de fichier SQL avec un entier pour déterminer l'ordre d'exécution lorsque Docker initialise le conteneur MySQL.

Une fois que tout est en place, exécutez le conteneur MySQL pour notre esclave retardé de 6 heures :

$ 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

** La valeur MYSQL_ROOT_PASSWORD doit être identique au mot de passe racine MySQL sur le maître.

Les lignes suivantes sont ce que nous recherchons pour vérifier si MySQL fonctionne correctement et connecté en tant qu'esclave à notre maître (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

Vous pouvez ensuite vérifier l'état de la réplication avec la déclaration suivante :

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

À ce stade, nos conteneurs esclaves retardés de 5 minutes, 1 heure et 6 heures se répliquent correctement et notre architecture ressemble à ceci :

Scénario de reprise après sinistre

Disons qu'un utilisateur a accidentellement laissé tomber une mauvaise colonne sur une grande table. Considérez que l'instruction suivante a été exécutée sur le maître :

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

Si vous avez la chance de le réaliser immédiatement, vous pouvez utiliser l'esclave retardé de 15 minutes pour rattraper le moment avant que la catastrophe ne se produise et le promouvoir pour devenir maître, ou exporter les données manquantes et les restaurer sur le maître.

Tout d'abord, nous devons trouver la position du journal binaire avant que la catastrophe ne se produise. Saisissez l'heure maintenant() sur le maître :

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

Ensuite, récupérez le fichier journal binaire actif sur le maître :

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

En utilisant le même format de date, extrayez les informations souhaitées du journal binaire, binlog.000004. Nous estimons l'heure de début de la lecture du binlog il y a environ 20 minutes (2018-12-04 14:35:00) et filtrons la sortie pour afficher 25 lignes avant l'instruction "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

Dans les dernières lignes de la sortie mysqlbinlog, vous devriez avoir la commande erronée qui a été exécutée à la position 19379556. La position que nous devons restaurer est une étape avant cela, qui est en position 19379491. C'est la position binlog où nous voulons que notre esclave retardé jusqu'à.

Ensuite, sur l'esclave retardé choisi, arrêtez l'esclave de réplication retardée et redémarrez l'esclave jusqu'à une position finale fixe que nous avons déterminée ci-dessus :

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

Surveillez l'état de la réplication et attendez que Exec_Master_Log_Pos soit égal à la valeur Until_Log_Pos. Cela pourrait prendre du temps. Une fois rattrapé, vous devriez voir ceci :

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

Vérifiez enfin si la donnée manquante que nous recherchions s'y trouve (la colonne "statut" existe toujours) :

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

Exportez ensuite la table depuis notre conteneur esclave et transférez-la sur le serveur maître :

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

Supprimez la table problématique et restaurez-la sur le maître :

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

Nous avons maintenant récupéré notre table dans son état d'origine avant l'événement désastreux. Pour résumer, la réplication différée peut être utilisée à plusieurs fins :

  • Pour se protéger contre les erreurs de l'utilisateur sur le maître. Un administrateur de base de données peut restaurer un esclave retardé à l'heure juste avant le sinistre.
  • Pour tester le comportement du système en cas de décalage. Par exemple, dans une application, un décalage peut être causé par une forte charge sur l'esclave. Cependant, il peut être difficile de générer ce niveau de charge. La réplication différée peut simuler le décalage sans avoir à simuler la charge. Il peut également être utilisé pour déboguer les conditions liées à un esclave en retard.
  • Pour inspecter à quoi ressemblait la base de données dans le passé, sans avoir à recharger une sauvegarde. Par exemple, si le retard est d'une semaine et que le DBA a besoin de voir à quoi ressemblait la base de données avant les derniers jours de développement, l'esclave retardé peut être inspecté.

Réflexions finales

Avec Docker, l'exécution de plusieurs instances MySQL sur un même hôte physique peut être effectuée efficacement. Vous pouvez utiliser des outils d'orchestration Docker tels que Docker Compose et Swarm pour simplifier le déploiement multi-conteneurs par opposition aux étapes présentées dans cet article de blog.