\
上面的体系结构图中显示了mysql半同步复制和异步复制之间的区别:对于mysql异步复制,Mysql Master Server将在通过复制线程传输Binary Log之后, Mysql Master Sever将自动将数据返回给客户端,而不管从站上是否收到二进制日志。
在半同步复制体系结构中,当主服务器将其binlog发送到从服务器时,它必须确保从服务器已接收到二进制日志,然后才能将数据返回给客户端。比较这两种体系结构:异步复制可以确保用户的快速响应结构,但不能确保二进制日志确实到达从属服务器。半同步复制对客户请求的响应速度稍慢,但是他可以保证完整性。
1.问题背景
默认情况下,在线mysql复制是异步复制,因此在极端情况下,备用数据库中的数据可能少于在切换主数据库和备份数据库时,将主数据库切换到主数据库,因此在切换之后,我们将使用该工具进行回滚和补充以确保数据不会丢失。
半同步复制需要主库执行每个事务,并且在实际完成之前至少需要成功接收至少一个备用数据库,因此它可以保持主数据库和备用数据库之间的强一致性。为了确保主数据库和备用数据库中数据的强一致性并减少数据丢失,请尝试在生产环境中启用MySQL复制的半同步功能。在实际的操作过程中,发现大多数实例可以半同步正常运行,但是少数实例不能总是打开(只能通过普通复制操作)。更奇怪的是,可以打开同一主机的两个实例。 ,一个不能。
最终的定位问题也很简单,但是要花很多时间才能找出来。下面将描述整个问题的故障排除过程。 br/>
2.半同步复制的原理
mysql的主数据库和备用数据库通过binlog日志保持一致,主库在本地执行事务,
默认情况下,主数据库和备用数据库会拉回主库的binlog日志以同步主库的操作。数据库没有严格同步,因此备用数据库和主数据库中的数据很可能不相等,半同步功能的出现是为了确保主数据和备份数据在任何时候都保持一致。异步复制,半同步所需的每个事务同步复制至少需要成功接收一个备用数据库,然后才能将其返回给用户。实现原理也很简单。主库的本地执行完成后,等待备用数据库的响应消息(包括备用数据库收到的最新binlog(文件,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),记录详细的日志信息,并传入和传出函数调用。
主服务器:
2016-01-04 18:00:30 13212 [注意] ReplSemiSyncMaster::updateSyncHeader:服务器(-1721062019) ,(mysql-bin。
000006,500717950)sync(1),repl(1)
2016-01-04 18:00:40 13212 [警告]超时等待binlog的回复(文件:mysql-bin。
000006,pos:500717950),半同步到文件,位置0。
2016-01-04 18:00:40 13212 [注意]半同步复制已关闭。
奴隶:
2016-01-04 18:00:30 38932 [Note]--> ReplSemiSyncSlave :: slaveReply输入
2016-01-04 18:00:30 38932 [Note] ReplSemiSyncSlave :: slaveReply:回复(mysql-bin。
000006,500717950)
2016-01-04 18:00:30 38932 [注] 0表示可读可写。
数组{__fd_mask __fds_bits [__ FD_SETSIZE/__NFDBITS]; 1024/64 = 16(long int)} fd_set#定义__FD_SET_SIZE 1024 typedef long int __fd_mask;//8个字节#define __NFDBITS(8 *(int)sizeof(__fd_mask))//64位#define __FDMASK(d)((__fd_mask)1个__fds_bits)#define __FD_SET(d,set)(__FDS_BITS(set)( __ FDELT(d))(d,set)(__FDS_BITS(set)[__ FDELT(d)] \ =__FDMASK(d))#定义__FD_ISSET(d,set)((__FDS_BITS(set)(__ FDELT( d))\ __FDMASK(d))!= 0)
可以通过FD_SET设置要监视的句柄,该句柄信息存储在fd_set位数组中,数组元素的数量由__FD_SETSIZE确定/64。对于__FD_SETSIZE = 1024,整个数组只有16个长整数。
每个句柄占用一位,即1024位,并且可以存储1024个句柄。如果句柄值是138,则138/64 = 2,138d = 10,然后将句柄标记在数组中第二个long int的第十个位置1上,因此,如果句柄值超过1024,它会在这里溢出吗?扫描了代码,发现在二。如果句柄值超过1024,则肯定会溢出。
因为select函数遍历数组中的每个位,然后判断该句柄是否可读和可写,对于超过1024个的句柄,它将永远不会判断,因此主库将永远不会知道备用数据库是否已发送响应包。
(4)验证
以上只是理论分析。如果实际实例句柄实际运行的数目超过1024,则可以找到问题所在。
1.获取mysql进程mysql-pid
ps –aux | grep mysqld | grep端口
2.
\ gdb附加到进程
gdb –p mysql-pid
3.找到ack_receive线程并切换到
信息线程
线程thread_id
4.
打印套接字的值,其中fd值为2344。
(5)如何解决
我们看到由于__FD_SETSIZE的定义,通常为1024,选择函数最多只能侦听1024个句柄,并且最大句柄值不超过1024。
第一种方法是增加参数,但是该方法需要重新编译linux内核。而且由于选择机制的原因,每次都需要遍历每个位来确定句柄上是否有消息,因此如果设置较大,将导致效率非常低。 Select是较旧的IO多路复用机制。更高级的民意调查和民意调查具有相似的功能,并且功能更强大。对句柄总数和最大句柄没有限制。
有关选择,轮询,epoll等的机制,您可以联机检查信息,这里不再讨论。
(6)正式版
我看到了最新的oracle正式版git5。
7源代码,这也是用select实现的,所以也存在类似的问题。当然,由于句柄号具有多路复用机制,因此当实例上的连接数少或长连接数不大时,fd> 1024并不容易,所以这个bug并不是很容易出现,但问题很普遍。
(7)问题已扩展
找到问题后,另一个问题困扰了我很长时间。
因为mysql内核中有3个块,其中有多个侦听器,所以1是侦听端口选择,2是线程池监视器epoll,3是半同步选择侦听器。从站binlog转储的线程是普通的工作线程,该工作线程的套接字将由epoll监视。这样,将通过线程池的半同步选择监视和epoll监视同时监视binlog转储的套接字。 ?稍后,在仔细查看代码之后,我发现线程池的epoll监视使用EPOLLONESHOT模式。收到每条消息后,它将取消绑定,并且需要重新注册。因此,两个监视机制将不会同时监视同一句柄。
至此,故障排除过程已经结束。结论是相对简单的,但是确实需要花费一些精力来找出问题所在。由于选择了更通用的IO复用机制,使用选择功能的童鞋可能需要注意其局限性。
/> pre>
所有