基于docker搭建的mysql主从数据库模拟模拟宕机后数据恢复
生产环境中不可避免会出现MySQL服务宕机的情况,常见的场景如下:
- 对于单一服务器结构一般通过设置sync_binlog=1与innodb_flush_log_at_trx_commit = 1(这两个选项可在提交事务之前启用二进制日志到磁盘的同步,从而保证了数据的安全性,如果发生电源故障或操作系统崩溃,二进制日志中缺少的事务将仅处于准备状态。这允许自动恢复例程回滚事务,从而保证二进制日志中不会丢失任何事务),可保证数据不丢失。
- 对于主从结构的mysql而言,不但要保证master的数据完整性与安全,同时也要保证slave的数据完整与安全。如果其中任意一台服务器宕机,如何保证slave服务器数据的同步不受影响呢?
关于mysql主从结构数据会有以下问题:
(1)如何保证master的通知能有效到达slave?
(2)如何避免slave中继日志修改信息未刷新到磁盘时宕机,重新启动后slave不能正常工作?
通过以下测试流程模拟mysql宕机并查找问题:
事先启动搭建好的mysql主从服务
1、在master服务器的某个库中插入数据
INSERT INTO `rbac`.`think_user`(`u_id`, `u_name`, `u_password`, `register_time`, `is_delete`, `status`, `update_time`, `last_login_time`, `role_id`, `u_phone`, `u_email`) VALUES (26, 'aaaaaa', '123456', '2019-05-31 18:03:58', 1, 1, 1561384746, '2019-05-31 18:04:10', '1', NULL, NULL);
INSERT INTO `rbac`.`think_user`(`u_id`, `u_name`, `u_password`, `register_time`, `is_delete`, `status`, `update_time`, `last_login_time`, `role_id`, `u_phone`, `u_email`) VALUES (27, 'aaaaaa', '123456', '2019-05-31 18:03:58', 0, 1, 1561384674, '2019-05-31 18:04:10', '1', NULL, NULL);
INSERT INTO `rbac`.`think_user`(`u_id`, `u_name`, `u_password`, `register_time`, `is_delete`, `status`, `update_time`, `last_login_time`, `role_id`, `u_phone`, `u_email`) VALUES (28, 'aaaaaa', '123456', '2019-05-31 18:03:58', 0, 0, 1561384674, '2019-05-31 18:04:10', '1', NULL, NULL);
2、直接关闭slave服务的container模拟服务器宕机
docker stop my-app-mysql8-slave1
3、重新启用slave服务器,查看slave状态
# 开启slave container容器
docker start my-app-mysql8-slave1
# 进入容器并启动mysql服务
docker exec -it my-app-mysql8-slave1 /bin/bash
/usr/local/mysql/bin/mysqld_safe --user=mysql &
# 使用mysql客户端工具连接mysql-server
/usr/local/mysql/bin/mysql -uroot -p
# 查看slave状态
show slave status\G;
show slave 报错信息如下:
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 172.18.0.4
Master_User: slave1
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000015
Read_Master_Log_Pos: 156
Relay_Log_File: 29677a6fee42-relay-bin.000023
Relay_Log_Pos: 324
Relay_Master_Log_File: mysql-bin.000013
Slave_IO_Running: Yes
Slave_SQL_Running: No
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 1062
Last_Error: Error 'Duplicate entry '26' for key 'think_user.PRIMARY'' on query. Default database: 'rbac'. Query: 'INSERT INTO `rbac`.`think_user`(`u_id`, `u_name`, `u_password`, `register_time`, `is_delete`, `status`, `update_time`, `last_login_time`, `role_id`, `u_phone`, `u_email`) VALUES (NULL, 'ccc', '123456', '2019-05-31 18:03:58', 0, 0, 1561384674, '2019-05-31 18:04:10', '1', NULL, NULL)'
Skip_Counter: 0
Exec_Master_Log_Pos: 156
Relay_Log_Space: 2385
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 1062
Last_SQL_Error: Error 'Duplicate entry '26' for key 'think_user.PRIMARY'' on query. Default database: 'rbac'. Query: 'INSERT INTO `rbac`.`think_user`(`u_id`, `u_name`, `u_password`, `register_time`, `is_delete`, `status`, `update_time`, `last_login_time`, `role_id`, `u_phone`, `u_email`) VALUES (NULL, 'ccc', '123456', '2019-05-31 18:03:58', 0, 0, 1561384674, '2019-05-31 18:04:10', '1', NULL, NULL)'
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_UUID: 7f06a326-b845-11ea-85b9-0242ac110002
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State:
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp: 200629 02:22:37
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
ERROR:
No query specified
此时查看slave数据库think_user表中主键数据为26,27,28的数据已经存在,说名在slave宕机之前数据已经同步到了表中。但是为什么宕机重新启动后有重新插入相同的数据呢? 这里就要说一下mysql主从的过程中做了哪些操作了,关于mysql中复制原理可参考我之前的一篇文章:地址。在slave 的进程将master中的数据写入relay log再写入表中,涉及到relay log的更新和relay log信息的更新。影响salve 中继日志信息同步的选项与master-bin.log同步信息选项如下:
(1) sync_relay_log_info
默认值为 10000。设置此变量将立即对所有复制通道(包括正在运行的通道)生效。
此变量对副本的影响取决于服务器的 relay_log_info_repository 设置(FILE【relay-log.info】或 TABLE【`mysql`.`slave_relay_log_info` 】)。如果设置为 TABLE,则变量的效果还取决于副本的应用程序元数据存储库表使用的存储引擎是事务性的(例如 InnoDB)还是不是事务性的(MyISAM)。对于sync_relay_log_info零和大于零的值,这些因素对服务器行为的影响 如下:
-
sync_relay_log_info = 0
-
如果 relay_log_info_repository 设置为FILE,则MySQL服务器不执行relay-log.info文件与磁盘的同步 ;而是,服务器依赖操作系统来定期刷新其内容,就像处理其他任何文件一样。
-
如果 relay_log_info_repository 将设置为TABLE,并且该表的存储引擎是事务性的,则在每次事务处理后都会更新该表。(sync_relay_log_info在这种情况下,该 设置将被有效忽略。)
-
如果 relay_log_info_repository 设置为TABLE,并且该表的存储引擎不是事务性的,则该表永远不会更新。
-
-
sync_relay_log_info = N > 0
-
如果 relay_log_info_repository 将设置为FILE,则副本在每次交易后将其relay-log.info文件同步 到磁盘(使用fdatasync()) N。
-
如果 relay_log_info_repository 将设置为TABLE,并且该表的存储引擎是事务性的,则在每次事务处理后都会更新该表。(sync_relay_log_info在这种情况下,该设置将被有效忽略。)
-
如果 relay_log_info_repository 将设置为TABLE,并且该表的存储引擎不是事务性的,那么在每个N事件之后都会更新该表 。
-
(2) sync_relay_log
如果此变量的值大于0,则fdatasync()在将每个sync_relay_log事件写入中继日志后,MySQL服务器都会 使用将该服务器的中继日志同步到磁盘 。设置此变量将立即对所有复制通道(包括正在运行的通道)生效。
设置sync_relay_log为0将导致不对磁盘进行任何同步;在这种情况下,服务器依赖操作系统来不时刷新中继日志的内容,就像处理其他任何文件一样。
值1是最安全的选择,因为在发生崩溃的情况下,您从中继日志中最多丢失一个事件。但是,它也是最慢的选择(除非磁盘具有电池备份的高速缓存,这使得同步非常快)。
(2) sync_master_info
此变量对slave的影响取决于slave的 master_info_repository设置为FILE或TABLE,具体如下:
-
master_info_repository = FILE。
- 如果的值sync_master_info大于0,则副本在每个事件之后将其master.info文件同步 到磁盘(使用 fdatasync()) sync_master_info。
- 如果为0,则MySQL服务器不执行master.info文件与磁盘的同步;否则为0 。而是,服务器依赖操作系统来定期刷新其内容,就像处理其他任何文件一样。
-
master_info_repository = TABLE。
- 如果的值sync_master_info大于0,则副本在每个sync_master_info事件后都会更新其连接元数据存储库表 。
- 如果为0,则表永远不会更新。
综上所述:
通过合理的设置sync_relay_log_info、sync_relay_log、sync_master_info 可最大程度保证slave服务器的数据完整性,但同时slave同步性能也会有所下降。
以下为我的slave配置
# 设置relay-log
relay_log_info_repository=TABLE
sync_relay_log_info=1
sync_relay_log=1
# 设置 master_info 同步
master_info_repository=TABLE
sync_master_info=1
4、错误解决
通过循环执行以下语句,直到slave可以正常工作
mysql> STOP SLAVE;
mysql> SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1; # 跳过冲突的步数
mysql> START SLAVE;
mysql> SHOW SLAVE STATUS;