Mysql半同步复制原理及问题排查

mysql半同步复制和异步复制的差别如上述架构图所示:在mysql异步复制的情况下,Mysql Master Server将自己的Binary Log通过复制线程传输出去以后,Mysql Master Sever就自动返回数据给客户端,而不管slave上是否接受到了这个二进制日志。在半同步复制的架构下,当master在将自己binlog发给slave上的时候,要确保slave已经接受到了这个二进制日志以后,才会返回数据给客户端。对比两种架构:异步复制对于用户来说,可以确保得到快速的响应结构,但是不能确保二进制日志确实到达了slave上;半同步复制对于客户的请求响应稍微慢点,但是他可以保证二进制日志的完整性。

1.问题背景

默认情况下,线上的mysql复制都是异步复制,因此在极端情况下,主备切换时,会有一定的概率备库比主库数据少,因此切换后,我们会通过工具进行回滚回补,确保数据不丢失。半同步复制则要求主库执行每一个事务,都要求至少一个备库成功接收后,才真正执行完成,因此可以保持主备库的强一致性。为了确保主备库数据强一致,减少数据丢失,尝试在生产环境中开启mysql的复制的半同步(semi-sync)特性。实际操作过程中,发现大部分实例半同步都可以正常运行,但有少部分实例始终开不起来(只能以普通复制方式运行),更奇葩的是同一个主机的两个实例,一个能开启,一个不能。最终定位的问题也很简单,但排查出来还是花了一番功夫,下文将描述整个问题的排查过程。

2.半同步复制原理

mysql的主备库通过binlog日志保持一致,主库本地执行完事务,binlog日志落盘后即返回给用户;备库通过拉取主库binlog日志来同步主库的操作。默认情况下,主库与备库并没有严格的同步,因此存在一定的概率备库与主库的数据是不对等的。半同步特性的出现,就是为了保证在任何时刻主备数据一致的问题。相对于异步复制,半同步复制要求执行的每一个事务,都要求至少有一个备库成功接收后,才返回给用户。实现原理也很简单,主库本地执行完毕后,等待备库的响应消息(包含最新备库接收到的binlog(file,pos)),接收到备库响应消息后,再返回给用户,这样一个事务才算真正完成。在主库实例上,有一个专门的线程(ack_receiver)接收备库的响应消息,并以通知机制告知主库备库已经接收的日志,可以继续执行。有关半同步的具体实现,可以参考另外一篇文章,mysql半同步(semi-sync)源码实现。

3.问题分析

前面简单介绍了半同步复制的原理,现在来看看具体问题。在主备库打开半同步开关后,问题实例的状态变量"Rpl_semi_sync_master_status"始终是OFF,表示复制一直运行在普通复制的状态。

(1).修改rpl_semi_sync_master_timeout参数。

半同步复制参数中有一个rpl_semi_sync_master_timeout参数,用以控制主库等待备库响应消息的时间,如果超过该值,则认为备库一直没有收到(备库可能挂了,也可能备库执行很慢,较主库相差很远),这个时候复制会切换为普通复制,避免主库的执行事务长时间等待。线上这个值默认是50ms,简单想是不是这个值太小了,遂将其改到10s,但问题依然不解。

(2).打印日志

排查问题最简单最笨的方法就是打日志,看看到底是哪个环节出了问题。主库和备库分别有rpl_semi_sync_master_trace_level和rpl_semi_sync_slave_trace_level参数来控制半同步复制打印日志。将两个参数值设置为80(64+16),记录详细日志信息,以及进出的函数调用。

master:

2016-01-04 18:00:30 13212 [Note] ReplSemiSyncMaster::updateSyncHeader: server(-1721062019), (mysql-bin.000006, 500717950) sync(1), repl(1)
2016-01-04 18:00:40 13212 [Warning] Timeout waiting for reply of binlog (file: mysql-bin.000006, pos: 500717950), semi-sync up to file , position 0.
2016-01-04 18:00:40 13212 [Note] Semi-sync replication switched OFF.

slave:

2016-01-04 18:00:30 38932 [Note] ---> ReplSemiSyncSlave::slaveReply enter
2016-01-04 18:00:30 38932 [Note] ReplSemiSyncSlave::slaveReply: reply (mysql-bin.000006, 500717950)
2016-01-04 18:00:30 38932 [Note] <--- ReplSemiSyncSlave::slaveReply exit (0)

从master日志可以看到在2016-01-04 18:00:30时,主库设置了半同步标记,并开始等待备库的响应,等待10s后,仍然没有收到响应,则认为超时,遂将半同步模式关闭,切换为普通模式。但从slave日志来看,在2016-01-04 18:00:30已经将(mysql-bin.000006, 500717950)发送给主库,表示已经收到该日志。这就说明,master日志已经打了semi-sync标,slave收到了日志,并且也回了包,master也确实等了10s,就是没有收到包,所以就切换为普通复制。现在问题就变成了,为什么master没有收到?

(3)select函数

前面提到了,主库实例上有一个专门接收响应包的线程(ack_receiver),它通过select函数监听socket,发现有slave的响应消息后,读取消息,通知工作线程可以继续执行。那么问题是不是出现在select函数上面?因为select是一个系统调用,一直没有怀疑,但已经跟到这里来了,那就得看看。与select函数相关的有几个重要的宏定义和说明。主要实现在/usr/include/bits/typesizes.h,/usr/include/bits/select.h和/usr/include/sys/select.h这三个文件中。

FD_ZERO(fd_set *fdset):清空fdset与所有文件句柄的联系。FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系。FD_CLR(int fd, fd_set *fdset):清除文件句柄fd与fdset的联系。FD_ISSET(int fd, fd_set *fdset):检查fdset联系的文件句柄fd是否可读写,当>0表示可读写。

array
{
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS]; 1024/64=16 (long int)
}fd_set
#define __FD_SET_SIZE 1024
typedef long int __fd_mask; //8个字节
#define __NFDBITS (8 * (int) sizeof (__fd_mask)) // 64位
#define __FDMASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS)) //fd%64=N,则在第N位设置为1
#define __FDELT(d) ((d) / __NFDBITS) //表示在第几个long int
#define __FDS_BITS(set) ((set)->__fds_bits)
#define __FD_SET(d, set) (__FDS_BITS (set)[__FDELT (d)] |= __FDMASK (d))
#define __FD_CLR(d, set) (__FDS_BITS (set)[__FDELT (d)] &= ~__FDMASK (d))
#define __FD_ISSET(d, set) \
((__FDS_BITS (set)[__FDELT (d)] & __FDMASK (d)) != 0) 

通过FD_SET可以设置我们想要监听的句柄,句柄信息存储在fd_set位数组中,数组元素的个数由__FD_SETSIZE/64决定,对于__FD_SETSIZE=1024而言,整个数组只有16个long int。每个句柄占有一个位,就是1024个位,可以存储1024个句柄。假设句柄值为138,那么138/64=2,138%64=10,那么这个句柄在数组的标示在第2个long int的第10位置1。那么如果句柄值超出1024呢,这里不就溢出了?我仔细撸了撸代码,发现根本就没有容错判断,如果句柄值超过1024就一定会溢出。由于select函数是遍历数组中的每个位,然后去判断该句柄是否可读可写,因此对于超过1024的句柄,永远也不会去判断,因此主库永远不知道备库是否发送了响应包。

(4)验证

上面只是理论分析,如果实际运行的实例句柄确实是超过了1024,那么问题就定位到了。

1.得到mysql进程mysql-pid

ps –aux | grep mysqld | grep port

2.gdb attach到该进程

gdb –p mysql-pid

3.找到ack_receive线程,并切换

info thread
thread thread_id

4.打印socket的值,这里fd值为2344。

(5)如何解

我们看到了由于__FD_SETSIZE的定义,一般是1024,导致select函数最多只能监听1024个句柄,并且最大句柄值不超过1024。第一个方法是调大该参数,但这种方法需要重新编译linux内核。而且由于select机制,每次都需要遍历 的每一位来判断句柄上是否有消息到来,因此如果设置很大,将导致效率非常低。select是一种比较老的IO复用机制,比较先进的poll,epoll都有类似的功能,并且更强大,也没有句柄总数和最大句柄的限制。有关select,poll,epoll等机制,大家可以去网上查资料,这里不展开讨论。

(6)官方版本

看了最新oracle官方版本git上5.7的源代码,这块也是用select来实现的,所以也存在类似的问题。当然,由于句柄号有复用机制,当实例上连接数很少,或者长连接不多时,不容易出现fd>1024的情况,所以这个bug不是很容易出现,但问题是普遍存在的。

(7)问题延生

问题定位后,另外一个问题还困扰我了半天。因为mysql内核中有监听的部分有3块,1是监听端口的select,2是线程池的监听epoll,3是半同步的select监听。slave binlog dump的线程就是普通的工作线程,而工作线程的socket会受epoll的监听,这样一来,binlog dump的socket会同时受半同步的select监听和线程池的epoll监听,这不乱了吗?后来仔细看了看代码,才发现线程池的epoll监听采用的是EPOLLONESHOT模式,每次接收消息后会解绑,需要重新注册,因此不会出现同一个句柄被两种监听机制同时监听的情况。

到此,排查问题过程就结束了,结论是比较简单的,但定位这个问题确实花费了一些功夫。由于select一种比较通用的多路IO复用机制,因此有用到select函数的童鞋,可能要注意下它的限制。

(0)

相关推荐

  • 简单谈谈MySQL的半同步复制

    简介 MySQL通过复制(Replication)实现存储系统的高可用.目前,MySQL支持的复制方式有: 异步复制(Asynchronous Replication):原理最简单,性能最好.但是主备之间数据不一致的概率很大. 半同步复制(Semi-synchronous Replication):相比异步复制,半同步复制牺牲了一定的性能,提升了主备之间数据的一致性(有一些情况还是会出现主备数据不一致). 组复制(Group Replication):基于Paxos算法实现分布式数据复制的强一致

  • MySQL主从复制的原理及配置方法(比较详细)

    一.复制的原理 MySQL 复制基于主服务器在二进制日志中跟踪所有对数据库的更改(更新.删除等等).每个从服务器从主服务器接收主服务器已经记录到其二进制日志的保存的更新,以便从服务器可以对其数据拷贝执行相同的更新. 将主服务器的数据拷贝到从服务器的一个途径是使用LOAD DATA FROM MASTER语句.请注意LOAD DATA FROM MASTER目前只在所有表使用MyISAM存储引擎的主服务器上工作.并且,该语句将获得全局读锁定. MySQL 使用3个线程来执行复制功能,其中1个在主服

  • mysql中复制表结构的方法小结

    mysql中用命令行复制表结构的方法主要有一下几种: 1.只复制表结构到新表 CREATE TABLE 新表 SELECT * FROM 旧表 WHERE 1=2 或者 CREATE TABLE 新表 LIKE 旧表 2.复制表结构及数据到新表 CREATE TABLE 新表 SELECT * FROM 旧表 3.复制旧表的数据到新表(假设两个表结构一样)  INSERT INTO 新表 SELECT * FROM 旧表 4.复制旧表的数据到新表(假设两个表结构不一样) INSERT INTO

  • MySQL数据表字段内容的批量修改、清空、复制等更新命令

    最近遇到一点麻烦事,新安装的PHPwind6.0正式版社区在导入之前的会员帐号资料时,发现很多会员的mail地址貌似胡乱填写的,之前的PHPwind5.5版本没有开启mail地址验证功能,所以估计很多用户胡乱填写了email地址,所以我就想要求所有正式会员重新验证邮件地址来重新激活会员帐号,结果发现社区根本没有这项功能,挣扎了N久,数据库的会员资料数据表被反复安装=删除了好几遍,总算找到了一个批量修改的方法. 不过这样操作会连社区创建者的账号都改成未激活,所以要是不清楚还真的不敢动手. 在PHP

  • MySQL复制表结构和内容到另一张表中的SQL语句

    1.复制表结构及数据到新表 复制代码 代码如下: CREATE TABLE 新表 SELECT * FROM 旧表 2.只复制表结构到新表 复制代码 代码如下: CREATE TABLE 新表 SELECT * FROM 旧表 WHERE 1=2 即:让WHERE条件不成立. 方法二:(低版本的mysql不支持,mysql4.0.25 不支持,mysql5已经支持了) 复制代码 代码如下: CREATE TABLE 新表 LIKE 旧表 3.复制旧表的数据到新表(假设两个表结构一样) 复制代码

  • 深入解析半同步与异步的MySQL主从复制配置

    简单来讲MySQL的主从复制就是一个C/S架构的应用.master可以认为是我们通常意义上所认为的server,slave可以当作是一台client.slave上的I/O线程去请求master上数据,而master验证通过slave的信息后就允许slave接入,然后进行数据变化信息的发送. 一.MySQL主从复制原理 这里我以MySQL5.5为例来说一下MySQL的主从复制的原理: 首先由备节点的I/O线程负责向主节点请求数据,主节点验证通过以后会由dump线程把数据发送给备用节点.备用节点的I

  • mysql把一个表某个字段的内容复制到另一张表的某个字段的SQL语句写法

    需求:把一个表某个字段内容复制到另一张表的某个字段. 实现sql语句1: 复制代码 代码如下: UPDATE file_manager_folder f1LEFT OUTER JOIN file_manager_folder f2     ON f1.name = f2.name AND f2.parentId = 54SET f1.parentId = 54 WHERE f2.name IS NULL AND f1.id IN (1,2,3); 实现sql语句2: 复制代码 代码如下: upd

  • Mysql半同步复制原理及问题排查

    mysql半同步复制和异步复制的差别如上述架构图所示:在mysql异步复制的情况下,Mysql Master Server将自己的Binary Log通过复制线程传输出去以后,Mysql Master Sever就自动返回数据给客户端,而不管slave上是否接受到了这个二进制日志.在半同步复制的架构下,当master在将自己binlog发给slave上的时候,要确保slave已经接受到了这个二进制日志以后,才会返回数据给客户端.对比两种架构:异步复制对于用户来说,可以确保得到快速的响应结构,但是

  • MySQL半同步复制原理配置与介绍详解

    环境介绍: Ubuntu Server 16.04.2+MySQL 5.7.17 Community Server (GPL) MySQL安装 通过APT的方式安装,官方指导文档地址: https://dev.mysql.com/downloads/repo/apt/ 1.下载mysql-apt-config_0.8.3-1_all.deb 2.安装deb A Quick Guide to Using the MySQL APT Repository: https://dev.mysql.com

  • Mysql主从三种复制模式(异步复制,半同步复制,组复制)

    目录 MySQL异步复制 半同步复制 组复制 MGR的解决方案现在具备的特性 MGR的解决方案目前的影响 MySQL异步复制 MySQL异步复制是主从复制过程中默认的复制模式.主从复制涉及三个线程,master I/O线程.slave I/O线程.slave sql线程.因为是异步复制,所以master事务的提交,不需要经过slave的确认,即master I/O线程提交事务后,不需要等待slave I/O线程的回复确认,master并不保证binlog一定写入到了relay log中:而sla

  • 详解MySQL双活同步复制四种解决方案

    对于数据实时同步,其核心是需要基于日志来实现,是可以实现准实时的数据同步,基于日志实现不会要求数据库本身在设计和实现中带来任何额外的约束. 基于MySQL原生复制主主同步方案  这是常见的方案,一般来说,中小型规模的时候,采用这种架构是最省事的. 两个节点可以采用简单的双主模式,并且使用专线连接,在master_A节点发生故障后,应用连接快速切换到master_B节点,反之也亦然.有几个需要注意的地方,脑裂的情况,两个节点写入相同数据而引发冲突,同时把两个节点的auto_increment_in

  • mariadb的主从复制、主主复制、半同步复制配置详解

    主从服务器的时间要同步,数据库版本最好是一致的,以免造成函数处理.日志读取.日志解析等发生异常. 以下三个主从复制的设置是独立的. 注意防火墙和selinux的影响. 1.简单主从复制的实现 (1)主服务器的配置 1)安装mariadb-server [root@localhost ~]# yum -y install mariadb-server 2)编辑/etc/my.cnf文件 [root@localhost ~]# vim /etc/my.cnf 在[mysqld]段的最后添加以下内容

  • mysql主从同步复制错误解决一例

    蚊子今天下午搭了一主三从的mysql复制,结果所有服务器都配置好后,发现从上报如下的错误 复制代码 代码如下: Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have equal MySQL server ids; these ids must be different for replication to work (or the --replicate-same-server-i

  • 详解MySQL的半同步

    前言 年后在进行腾讯二面的时候,写完算法的后问的第一个问题就是,MySQL的半同步是什么?我当时直接懵了,我以为是问的MySQL的两阶段提交的问题呢?结果确认了一下后不是两阶段提交,然后面试官看我连问的是啥都不知道,就直接跳过这个问题,直接聊下一个问题了.所以这次总结一下这部分的知识内容,文字内容比较多,可能会有些枯燥,但对于这方面感兴趣的人来说还是比较有意思的. MySQL的主从复制 我们的一般在大规模的项目上,都是使用MySQL的复制功能来创建MySQL的主从集群的.主要是可以通过为服务器配

  • MySQL主从复制之半同步semi-sync replication

    目录 一.半同步简介 二.两种模式介绍 三.两种方式比较 四.如何开启半同步 五.查看插件开启情况 六.开启半同步功能 七.查看半同步是否运行 八.半同步参数信息 九.半同步状态信息 十.测试一下半同步的同步情况 1.从节点暂时先关掉IO线程 2.主节点写入几条测试数据 3.等待10s后查看复制状态,半同步已经关掉了 4.从节点开启IO线程 5.再次查看复制状态,半同步复制自动开启了 一.半同步简介 MASTER节点在执行完客户端提交的事务后不是立刻返回结果给客户端,而是等待至少一个SLAVE节

  • MYSQL 完全备份、主从复制、级联复制、半同步小结

    mysql 完全备份 1,启用二进制日志,并于数据库分离,单独存放 vim /etc/my.cnf 添加 log_bin=/data/bin/mysql-bin 创建/data/bin文件夹并授权 chown mysql.mysql /data/bin 2,完成备份数据库 mysqldump -A --single-transaction --master-data=2 | xz > /data/all.sql.xz 3,对数据库进行增删改 INSERT hellodb.students(stu

随机推荐