mysql 互为主备 宕机 数据丢失_MySQL主库02设置宕机导致的主库数据丢失解决方法和原因...

线上环境中,MySQL半同步主从配置因sync_binlog和innodb_flush_log_at_trx_commit设置不当导致主库宕机后数据丢失。解决办法包括修改老主库server_id,使其能从从库拉取数据。从库io线程和sql线程处理主库binlog,通过判断server_id决定是否写入relaylog。开启--replicate-same-server-id或修改server_id可避免数据丢失。
摘要由CSDN通过智能技术生成

背景

在线上环境中,使用MySQL搭建了一个半同步的主从架构,并且主从设置成了无损复制,即变量rpl_semi_sync_master_wait_point设置成了AFTER_SYNC,但是由于性能的问题,将主库设置成了02,即变量sync_binlog设置为0,innodb_flush_log_at_trx_commit设置成了2。导致主库宕机后,主库上的数据丢失,即从库数据多于主库的数据,怎么将主库产生的数据从从库上拉取过来呢?直接将老主库挂在原来的从库下面,发现并没有将丢失的这部分老主库产生的数据的binlog发送并且写入到relaylog里面。最后通过修改老主库的server_id得以解决这个问题。为什么需要修改老主库的server_id从库才会将这部分数据发送写入到relaylog里面呢?

关于sync_binlog和innodb_flush_log_at_trx_commit

对于sync_binlog和innodb_flush_log_at_trx_commit分别是SQL层和存储层的变量,即在什么情况下进行刷盘,这面不详细的介绍。最安全的情况就是将这两个值设置为11,即每个事务提交都会进行binlog和引擎层日志的刷盘。双一保证数据的最大程度的不丢失或者最多丢失最后一个语句或者事务,但是双一的设置会导致服务频繁的IO,影响性能,所以上面我们就设置成了02,即将sync_binlog设置为0,由操作系统控制将文件缓存刷盘,innodb_flush_log_at_trx_commit设置为2,每次事务提交会写入日志文件,但并不会立即刷写到磁盘,日志文件会每秒刷写一次到磁盘,这种情况下,服务的性能会大大的提升,但是也会导致如果发生了主库宕机,会丢失较多的数据。

关于从库的io线程和sql线程

从库用于处理主库日志的两个线程分别为io线程和sql线程,io线程负责获取主库的binlog文件里面的一个个event,然后写入到relaylog文件中,sql线程负责回放这些event。sql/http://rpl_slave.cc文件里面的函数handle_slave_io和handle_slave_sql分别是两个线程的回调函数,首先io线程的处理函数主干流程为:

handle_slave_io

| init_slave_thread // 初始化线程

| safe_connect // 连接到主库

| register_slave_on_master // 注册到主库上

| while (!io_slave_killed(thd,mi)) // 进入循环

| request_dump // 向master请求event,告诉主库自己需要的日志的起始位置

| while (!io_slave_killed(thd,mi)) // 进入获取日志的循环

| read_event // 读取event

| queue_event // 将读取到的event写入到relaylog, 这面会对event的server_id进行判断,下面将详细介绍

| append_buffer // 将event写入到缓存,根据条件进行刷盘

| my_b_append // 写入到文件缓存

| after_append_to_relay_log // 根据条件进行刷盘和生成新的relaylog文件

| flush_and_sync // 根据条件进行刷盘

| sync_binlog_file // 将文件刷盘,下面详细介绍

| signal_update // 通过条件变量告诉sql线程有新的event写入

| RUN_HOOK(binlog_relay_io, after_queue_event,(thd, mi, event_buf, event_len, synced))) // 半同步发送Ack包

| flush_master_info // 更新master_info文件

上面大概介绍了io线程的流程,从线程初始化,到注册到主库,最后获取binlog的event进行条件判断后写入到relaylog。所以对于relaylog和binlog,两者基本是一样的,只是在的地方不同而已,一个在主库一个在从库,主库上的binlog到从库上变成了relaylog,所以有时候可以直接把主库上的binlog物理拷贝到从库上命名为relaylog,让从库进行回放。所以在MySQL的代码里面,在处理binlog和relaylog基本上代码基本上都是复用的,有些不同的地方,通过一个bool变量is_relay_log来进行区分。下面通过函数sync_binlog_file说明一下,函数主要代码如下:

unsigned int sync_period= get_sync_period(); // 获取sync_binlog或者sync_relay_log的值

if (force || (sync_period && ++sync_counter >= sync_period))

{

// 刷到磁盘

}

get_sync_period函数为:

inline uint get_sync_period()

{

return *sync_period_ptr;

}

而变量sync_period_ptr的定义上面也有说明

/* pointer to the sync period variable, for binlog this will be

sync_binlog_period, for relay log this will be

sync_relay_log_period

*/

uint *sync_period_ptr;

所以get_sync_period函数在binlog里面获得就是参数sync_binlog的值,在relaylog里面获取的就是sync_relay_log的值。当我们上面的将sync_binlog设置为0时,mysqld服务不会将binlog文件缓存刷新到磁盘里面去的,所以出现了上文出现的问题,老主库丢失了许多的binlog文件。如果将sync_binlog设置为1,则会每一个事务或者语句提交后都会刷新到磁盘,最大程度的保证了数据的安全性。对于relaylog也是一样,对于从库获得的一个个event是否会写入到relaylog文件,会进行判断,相关代码如下:

sql/rpl_slave.cc queue_event函数

s_id= uint4korr(buf + SERVER_ID_OFFSET);

if ((s_id == ::server_id && !mi->rli->replicate_same_server_id) || // server_id一致的情况

(mi->ignore_server_ids->dynamic_ids.size() > 0 &&

mi->shall_ignore_server_id(s_id) &&

/* everything is filtered out from non-master */

(s_id != mi->master_id ||

/* for the master meta information is necessary */

(event_type != binary_log::FORMAT_DESCRIPTION_EVENT &&

event_type != binary_log::ROTATE_EVENT))))

{

if (!(s_id == ::server_id && !mi->rli->replicate_same_server_id) ||

(event_type != binary_log::FORMAT_DESCRIPTION_EVENT &&

event_type != binary_log::ROTATE_EVENT &&

event_type != binary_log::STOP_EVENT))

{

...

}

...

}

else

{

// 写入到relaylog缓存和文件里面去

}

从上面的代码可以发现首先从库会获得通过io线程读取过来的event的server_id,然后此event是否会写入到relaylog文件里面去,会进行判断,判断分为两类:event的server_id是否和本地的server_id是相同的,如果相同本地服务是否开启了--replicate-same-server-id,这个参数如果开启了,则即使获取的event的server_id和本地的server_id相同的,也会将此event写入到relaylog。注意--replicate-same-server-id参数和--log-slave-updates参数是冲突的,因为这两个都开启的话,在双向复制的场景下,有可能导致日志复制的死循环。

对于server_id屏蔽的情况,如果从库设置了屏蔽某server_id的日志,这面会检查此event的server_id是否在屏蔽的设置里面,如果在里面,也会将从库的master_id产生的FORMAT_DESCRIPTION_EVENT和ROTATE_EVENT写入到relaylog里面去。

所以,对于上面的问题,想要从从库获取老主库自己产生的数据,需要设置复制参数--replicate-same-server-id或者直接修改自己的server_id,然后将老主库挂到从库下面进行数据复制即可。

上面的io线程会通过signal_update通过条件变量来唤醒等待中的sql线程,对于sql线程相关的处理流程如下:

handle_slave_sql

| init_slave_thread // 初始化线程

| slave_start_workers // 并行复制线程数,由参数slave_parallel_workers决定,下文会再介绍

| while (!sql_slave_killed(thd,rli)) // 进入循环

| request_dump // 向master请求event,告诉主库自己需要的日志的起始位置

| while (!io_slave_killed(thd,mi)) // 进入获取日志的循环

| exec_relay_log_event // 调用处理event

| next_event // 获取event

| while (!sql_slave_killed(thd,rli)) // 进入循环

| read_log_event // 读入event

| wait_for_update_relay_log // 等待relaylog更新

| mysql_cond_wait // 等待条件变量的变化

| apply_event_and_update_pos 回放event

上面列出了sql线程的主要的流程,无非就是获取event然后重放event,但是因为回放的速度比较慢,老版本的MySQL会出现ralaylog堆积的情况,为了解决这个问题,就出现了并行复制,即多个线程一起的回放,当然这个需要和MySQL GROUP COMMIT(MGC)一起使用才会有效果。对于并行复制的问题,参数slave_start_workers表示并行复制的线程数,如果大于0时,表示开启了并行复制,程序会有一个coordinator线程用于分配回放的任务给work线程。所以当slave_start_workers设置为1时,coordinator线程既要分配任务又要作为worker线程进行回放,影响回放的速度。

总结

本文通过在线上环境中遇到的MySQL主从数据不一致的问题,通过源码介绍了解决方法,并且进而简单介绍了MySQL的从库的io线程和sql线程的流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值