MariaDB
 sql >> Base de Dados >  >> RDS >> MariaDB

Vários escravos de replicação atrasada para recuperação de desastres com baixo RTO


A replicação atrasada permite que um escravo de replicação fique deliberadamente atrasado em relação ao mestre por pelo menos um período de tempo especificado. Antes de executar um evento, o escravo primeiro aguardará, se necessário, até que o tempo determinado tenha passado desde que o evento foi criado no mestre. O resultado é que o escravo refletirá o estado do mestre em algum tempo no passado. Este recurso é suportado desde o MySQL 5.6 e MariaDB 10.2.3. Ele pode ser útil em caso de exclusão acidental de dados e deve fazer parte do seu plano de recuperação de desastres.

O problema ao configurar um escravo de replicação atrasada é quanto atraso devemos colocar. Muito pouco tempo e você corre o risco de a consulta ruim chegar ao seu escravo atrasado antes que você possa chegar a ele, desperdiçando assim o ponto de ter o escravo atrasado. Opcionalmente, você pode fazer com que seu tempo de atraso seja tão longo que leve horas para que seu escravo atrasado alcance onde o mestre estava no momento do erro.

Felizmente com o Docker, o isolamento do processo é sua força. A execução de várias instâncias do MySQL é bastante conveniente com o Docker. Ele nos permite ter vários escravos atrasados ​​em um único host físico para melhorar nosso tempo de recuperação e economizar recursos de hardware. Se você achar que um atraso de 15 minutos é muito curto, podemos ter outra instância com atraso de 1 hora ou 6 horas para um instantâneo ainda mais antigo do nosso banco de dados.

Nesta postagem do blog, implantaremos vários escravos atrasados ​​do MySQL em um único host físico com o Docker e mostraremos alguns cenários de recuperação. O diagrama a seguir ilustra nossa arquitetura final que queremos construir:

Nossa arquitetura consiste em uma replicação MySQL de 2 nós já implantada rodando em servidores físicos (azul) e gostaríamos de configurar outros três escravos MySQL (verde) com o seguinte comportamento:
  • 15 minutos de atraso
  • 1 hora de atraso
  • 6 horas de atraso

Observe que teremos 3 cópias exatamente dos mesmos dados no mesmo servidor físico. Certifique-se de que nosso host do Docker tenha o armazenamento necessário, portanto, aloque espaço em disco suficiente com antecedência.

Preparação do mestre do MySQL


Primeiramente, faça login no servidor mestre e crie o usuário de replicação:
mysql> GRANT REPLICATION SLAVE ON *.* TO [email protected]'%' IDENTIFIED BY 'YlgSH6bLLy';

Em seguida, crie um backup compatível com PITR no mestre:
$ 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 você estiver usando o ClusterControl, poderá fazer um backup compatível com PITR facilmente. Vá para Backups -> Criar Backup e escolha "Complete PITR-compatível" no menu suspenso "Tipo de despejo":

Por fim, transfira esse backup para o host do Docker:
$ scp mysqldump_complete.sql.gz [email protected]:~

Este arquivo de backup será usado pelos contêineres escravos do MySQL durante o processo de inicialização do escravo, conforme mostrado na próxima seção.

Implantação de escravo atrasada


Prepare nossos diretórios de contêiner do Docker. Crie 3 diretórios (mysql.conf.d, datadir e sql) para cada container MySQL que vamos lançar (você pode usar loop para simplificar os comandos abaixo):
$ 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

O diretório "mysql.conf.d" armazenará nosso arquivo de configuração MySQL personalizado e será mapeado para o contêiner em /etc/mysql.conf.d. "datadir" é onde queremos que o Docker armazene o diretório de dados do MySQL, que mapeia para /var/lib/mysql do contêiner e o diretório "sql" armazena nossos arquivos SQL - arquivos de backup no formato .sql ou .sql.gz para stage o escravo antes de replicar e também arquivos .sql para automatizar a configuração e inicialização da replicação.

Escravo Atrasado de 15 minutos


Prepare o arquivo de configuração do MySQL para nosso escravo atrasado de 15 minutos:
$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf

E adicione as seguintes linhas:
[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

** O valor do server-id que usamos para este slave é 10015.

Em seguida, no diretório /storage/mysql-slave-15m/sql, crie dois arquivos SQL, um para RESET MASTER (1reset_master.sql) e outro para estabelecer o link de replicação usando a instrução CHANGE MASTER (3setup_slave.sql).

Crie um arquivo de texto 1reset_master.sql e adicione a seguinte linha:
RESET MASTER;

Crie um arquivo de texto 3setup_slave.sql e adicione as seguintes linhas:
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 é igual a 15 minutos (em segundos). Em seguida, copie o arquivo de backup obtido de nosso mestre (que foi transferido para nosso host Docker) para o diretório "sql" e renomeado como 2mysqldump_complete.sql.gz:
$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-15m/sql/2mysqldump_complete.tar.gz

A aparência final do nosso diretório "sql" deve ser algo assim:
$ pwd
/storage/mysql-slave-15m/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql

Observe que prefixamos o nome do arquivo SQL com um número inteiro para determinar a ordem de execução quando o Docker inicializa o contêiner MySQL.

Quando tudo estiver no lugar, execute o contêiner MySQL para nosso escravo atrasado de 15 minutos:
$ 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

** O valor MYSQL_ROOT_PASSWORD deve ser o mesmo que a senha de root do MySQL no master.

As linhas a seguir são o que procuramos para verificar se o MySQL está rodando corretamente e conectado como escravo ao nosso mestre (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

Você pode então verificar o status de replicação com a seguinte instrução:
$ 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
...

Neste ponto, nosso contêiner escravo atrasado de 15 minutos está replicando corretamente e nossa arquitetura está parecida com isto:

Escravo com atraso de 1 hora


Prepare o arquivo de configuração do MySQL para nosso escravo atrasado de 1 hora:
$ vim /storage/mysql-slave-1h/mysql.conf.d/my.cnf

E adicione as seguintes linhas:
[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

** O valor do server-id que usamos para este slave é 10060.

Em seguida, no diretório /storage/mysql-slave-1h/sql, crie dois arquivos SQL, um para RESET MASTER (1reset_master.sql) e outro para estabelecer o link de replicação usando a instrução CHANGE MASTER (3setup_slave.sql).

Crie um arquivo de texto 1reset_master.sql e adicione a seguinte linha:
RESET MASTER;

Crie um arquivo de texto 3setup_slave.sql e adicione as seguintes linhas:
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 é igual a 1 hora (em segundos). Em seguida, copie o arquivo de backup obtido de nosso mestre (que foi transferido para nosso host Docker) para o diretório "sql" e renomeado como 2mysqldump_complete.sql.gz:
$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-1h/sql/2mysqldump_complete.tar.gz

A aparência final do nosso diretório "sql" deve ser algo assim:
$ pwd
/storage/mysql-slave-1h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql

Observe que prefixamos o nome do arquivo SQL com um número inteiro para determinar a ordem de execução quando o Docker inicializa o contêiner MySQL.

Quando tudo estiver no lugar, execute o contêiner MySQL para nosso escravo atrasado de 1 hora:
$ 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

** O valor MYSQL_ROOT_PASSWORD deve ser o mesmo que a senha de root do MySQL no master.

As linhas a seguir são o que procuramos para verificar se o MySQL está rodando corretamente e conectado como escravo ao nosso mestre (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

Você pode então verificar o status de replicação com a seguinte instrução:
$ 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
...

Neste ponto, nossos contêineres escravos atrasados ​​do MySQL de 15 minutos e 1 hora estão replicando do mestre e nossa arquitetura está parecida com isto:

escravo atrasado de 6 horas


Prepare o arquivo de configuração do MySQL para nosso escravo atrasado de 6 horas:
$ vim /storage/mysql-slave-15m/mysql.conf.d/my.cnf

E adicione as seguintes linhas:
[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

** O valor do server-id que usamos para este slave é 10006.

Em seguida, no diretório /storage/mysql-slave-6h/sql, crie dois arquivos SQL, um para RESET MASTER (1reset_master.sql) e outro para estabelecer o link de replicação usando a instrução CHANGE MASTER (3setup_slave.sql).

Crie um arquivo de texto 1reset_master.sql e adicione a seguinte linha:
RESET MASTER;

Crie um arquivo de texto 3setup_slave.sql e adicione as seguintes linhas:
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 é igual a 6 horas (em segundos). Em seguida, copie o arquivo de backup obtido de nosso mestre (que foi transferido para nosso host Docker) para o diretório "sql" e renomeado como 2mysqldump_complete.sql.gz:
$ cp ~/mysqldump_complete.tar.gz /storage/mysql-slave-6h/sql/2mysqldump_complete.tar.gz

A aparência final do nosso diretório "sql" deve ser algo assim:
$ pwd
/storage/mysql-slave-6h/sql
$ ls -1
1reset_master.sql
2mysqldump_complete.sql.gz
3setup_slave.sql

Observe que prefixamos o nome do arquivo SQL com um número inteiro para determinar a ordem de execução quando o Docker inicializa o contêiner MySQL.

Quando tudo estiver no lugar, execute o contêiner MySQL para nosso escravo atrasado de 6 horas:
$ 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

** O valor MYSQL_ROOT_PASSWORD deve ser o mesmo que a senha de root do MySQL no master.

As linhas a seguir são o que procuramos para verificar se o MySQL está rodando corretamente e conectado como escravo ao nosso mestre (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

Você pode então verificar o status de replicação com a seguinte instrução:
$ 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
...

Neste ponto, nossos contêineres escravos com atraso de 5 minutos, 1 hora e 6 horas estão replicando corretamente e nossa arquitetura está parecida com isto:

Cenário de recuperação de desastres


Digamos que um usuário acidentalmente deixou cair uma coluna errada em uma grande tabela. Considere que a seguinte instrução foi executada no mestre:
mysql> USE shop;
mysql> ALTER TABLE settings DROP COLUMN status;

Se você tiver a sorte de perceber isso imediatamente, poderá usar o escravo atrasado de 15 minutos para acompanhar o momento antes do desastre acontecer e promovê-lo para se tornar mestre, ou exportar os dados ausentes e restaurá-los no mestre.

Em primeiro lugar, temos que encontrar a posição do log binário antes do desastre acontecer. Pegue o tempo agora () no mestre:
mysql> SELECT now();
+---------------------+
| now()               |
+---------------------+
| 2018-12-04 14:55:41 |
+---------------------+

Em seguida, obtenha o arquivo de log binário ativo no mestre:
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 |
+---------------+----------+--------------+------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

Usando o mesmo formato de data, extraia as informações que queremos do log binário, binlog.000004. Estimamos o horário de início da leitura do binlog cerca de 20 minutos atrás (2018-12-04 14:35:00) e filtramos a saída para mostrar 25 linhas antes da instrução "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

Nas últimas linhas da saída do mysqlbinlog, você deve ter o comando errôneo que foi executado na posição 19379556. A posição que devemos restaurar é um passo antes disso, que está na posição 19379491. Esta é a posição do binlog onde queremos nosso escravo atrasado para ser até.

Então, no escravo atrasado escolhido, pare o escravo de replicação atrasada e inicie novamente o escravo para uma posição final fixa que descobrimos acima:
$ 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;

Monitore o status de replicação e aguarde até que Exec_Master_Log_Pos seja igual ao valor de Until_Log_Pos. Isso pode levar algum tempo. Uma vez capturado, você deve ver o seguinte:
$ 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
...

Por fim, verifique se os dados ausentes que estávamos procurando estão lá (a coluna "status" ainda existe):
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       |                |
+--------+------------------+------+-----+---------+----------------+

Em seguida, exporte a tabela do nosso container slave e transfira-a para o servidor master:
$ docker exec -it mysql-slave-1h mysqldump -uroot -ppassword --single-transaction shop settings > shop_settings.sql

Solte a tabela problemática e restaure-a de volta no mestre:
$ mysql -uroot -p -e 'DROP TABLE shop.settings'
$ mysqldump -uroot -p -e shop < shop_setttings.sql

Agora recuperamos nossa tabela de volta ao seu estado original antes do evento desastroso. Para resumir, a replicação atrasada pode ser usada para várias finalidades:
  • Para proteger contra erros do usuário no mestre. Um DBA pode reverter um escravo atrasado para o momento anterior ao desastre.
  • Para testar como o sistema se comporta quando há um atraso. Por exemplo, em um aplicativo, um atraso pode ser causado por uma carga pesada no escravo. No entanto, pode ser difícil gerar esse nível de carga. A replicação atrasada pode simular o atraso sem ter que simular a carga. Ele também pode ser usado para depurar condições relacionadas a um escravo atrasado.
  • Para inspecionar a aparência do banco de dados no passado, sem precisar recarregar um backup. Por exemplo, se o atraso for de uma semana e o DBA precisar ver a aparência do banco de dados antes dos últimos dias de desenvolvimento, o escravo atrasado poderá ser inspecionado.

Considerações finais


Com o Docker, a execução de várias instâncias do MySQL em um mesmo host físico pode ser feita com eficiência. Você pode usar ferramentas de orquestração do Docker, como Docker Compose e Swarm, para simplificar a implantação de vários contêineres, em oposição às etapas mostradas nesta postagem do blog.