Mysql半同步復(fù)制原理及問(wèn)題排查
mysql半同步復(fù)制和異步復(fù)制的差別如上述架構(gòu)圖所示:在mysql異步復(fù)制的情況下,Mysql Master Server將自己的Binary Log通過(guò)復(fù)制線(xiàn)程傳輸出去以后,Mysql Master Sever就自動(dòng)返回?cái)?shù)據(jù)給客戶(hù)端,而不管slave上是否接受到了這個(gè)二進(jìn)制日志。在半同步復(fù)制的架構(gòu)下,當(dāng)master在將自己binlog發(fā)給slave上的時(shí)候,要確保slave已經(jīng)接受到了這個(gè)二進(jìn)制日志以后,才會(huì)返回?cái)?shù)據(jù)給客戶(hù)端。對(duì)比兩種架構(gòu):異步復(fù)制對(duì)于用戶(hù)來(lái)說(shuō),可以確保得到快速的響應(yīng)結(jié)構(gòu),但是不能確保二進(jìn)制日志確實(shí)到達(dá)了slave上;半同步復(fù)制對(duì)于客戶(hù)的請(qǐng)求響應(yīng)稍微慢點(diǎn),但是他可以保證二進(jìn)制日志的完整性。
1.問(wèn)題背景
默認(rèn)情況下,線(xiàn)上的mysql復(fù)制都是異步復(fù)制,因此在極端情況下,主備切換時(shí),會(huì)有一定的概率備庫(kù)比主庫(kù)數(shù)據(jù)少,因此切換后,我們會(huì)通過(guò)工具進(jìn)行回滾回補(bǔ),確保數(shù)據(jù)不丟失。半同步復(fù)制則要求主庫(kù)執(zhí)行每一個(gè)事務(wù),都要求至少一個(gè)備庫(kù)成功接收后,才真正執(zhí)行完成,因此可以保持主備庫(kù)的強(qiáng)一致性。為了確保主備庫(kù)數(shù)據(jù)強(qiáng)一致,減少數(shù)據(jù)丟失,嘗試在生產(chǎn)環(huán)境中開(kāi)啟mysql的復(fù)制的半同步(semi-sync)特性。實(shí)際操作過(guò)程中,發(fā)現(xiàn)大部分實(shí)例半同步都可以正常運(yùn)行,但有少部分實(shí)例始終開(kāi)不起來(lái)(只能以普通復(fù)制方式運(yùn)行),更奇葩的是同一個(gè)主機(jī)的兩個(gè)實(shí)例,一個(gè)能開(kāi)啟,一個(gè)不能。最終定位的問(wèn)題也很簡(jiǎn)單,但排查出來(lái)還是花了一番功夫,下文將描述整個(gè)問(wèn)題的排查過(guò)程。
2.半同步復(fù)制原理
mysql的主備庫(kù)通過(guò)binlog日志保持一致,主庫(kù)本地執(zhí)行完事務(wù),binlog日志落盤(pán)后即返回給用戶(hù);備庫(kù)通過(guò)拉取主庫(kù)binlog日志來(lái)同步主庫(kù)的操作。默認(rèn)情況下,主庫(kù)與備庫(kù)并沒(méi)有嚴(yán)格的同步,因此存在一定的概率備庫(kù)與主庫(kù)的數(shù)據(jù)是不對(duì)等的。半同步特性的出現(xiàn),就是為了保證在任何時(shí)刻主備數(shù)據(jù)一致的問(wèn)題。相對(duì)于異步復(fù)制,半同步復(fù)制要求執(zhí)行的每一個(gè)事務(wù),都要求至少有一個(gè)備庫(kù)成功接收后,才返回給用戶(hù)。實(shí)現(xiàn)原理也很簡(jiǎn)單,主庫(kù)本地執(zhí)行完畢后,等待備庫(kù)的響應(yīng)消息(包含最新備庫(kù)接收到的binlog(file,pos)),接收到備庫(kù)響應(yīng)消息后,再返回給用戶(hù),這樣一個(gè)事務(wù)才算真正完成。在主庫(kù)實(shí)例上,有一個(gè)專(zhuān)門(mén)的線(xiàn)程(ack_receiver)接收備庫(kù)的響應(yīng)消息,并以通知機(jī)制告知主庫(kù)備庫(kù)已經(jīng)接收的日志,可以繼續(xù)執(zhí)行。有關(guān)半同步的具體實(shí)現(xiàn),可以參考另外一篇文章,mysql半同步(semi-sync)源碼實(shí)現(xiàn)。
3.問(wèn)題分析
前面簡(jiǎn)單介紹了半同步復(fù)制的原理,現(xiàn)在來(lái)看看具體問(wèn)題。在主備庫(kù)打開(kāi)半同步開(kāi)關(guān)后,問(wèn)題實(shí)例的狀態(tài)變量"Rpl_semi_sync_master_status"始終是OFF,表示復(fù)制一直運(yùn)行在普通復(fù)制的狀態(tài)。
(1).修改rpl_semi_sync_master_timeout參數(shù)。
半同步復(fù)制參數(shù)中有一個(gè)rpl_semi_sync_master_timeout參數(shù),用以控制主庫(kù)等待備庫(kù)響應(yīng)消息的時(shí)間,如果超過(guò)該值,則認(rèn)為備庫(kù)一直沒(méi)有收到(備庫(kù)可能掛了,也可能備庫(kù)執(zhí)行很慢,較主庫(kù)相差很遠(yuǎn)),這個(gè)時(shí)候復(fù)制會(huì)切換為普通復(fù)制,避免主庫(kù)的執(zhí)行事務(wù)長(zhǎng)時(shí)間等待。線(xiàn)上這個(gè)值默認(rèn)是50ms,簡(jiǎn)單想是不是這個(gè)值太小了,遂將其改到10s,但問(wèn)題依然不解。
(2).打印日志
排查問(wèn)題最簡(jiǎn)單最笨的方法就是打日志,看看到底是哪個(gè)環(huán)節(jié)出了問(wèn)題。主庫(kù)和備庫(kù)分別有rpl_semi_sync_master_trace_level和rpl_semi_sync_slave_trace_level參數(shù)來(lái)控制半同步復(fù)制打印日志。將兩個(gè)參數(shù)值設(shè)置為80(64+16),記錄詳細(xì)日志信息,以及進(jìn)出的函數(shù)調(diào)用。
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時(shí),主庫(kù)設(shè)置了半同步標(biāo)記,并開(kāi)始等待備庫(kù)的響應(yīng),等待10s后,仍然沒(méi)有收到響應(yīng),則認(rèn)為超時(shí),遂將半同步模式關(guān)閉,切換為普通模式。但從slave日志來(lái)看,在2016-01-04 18:00:30已經(jīng)將(mysql-bin.000006, 500717950)發(fā)送給主庫(kù),表示已經(jīng)收到該日志。這就說(shuō)明,master日志已經(jīng)打了semi-sync標(biāo),slave收到了日志,并且也回了包,master也確實(shí)等了10s,就是沒(méi)有收到包,所以就切換為普通復(fù)制?,F(xiàn)在問(wèn)題就變成了,為什么master沒(méi)有收到?
(3)select函數(shù)
前面提到了,主庫(kù)實(shí)例上有一個(gè)專(zhuān)門(mén)接收響應(yīng)包的線(xiàn)程(ack_receiver),它通過(guò)select函數(shù)監(jiān)聽(tīng)socket,發(fā)現(xiàn)有slave的響應(yīng)消息后,讀取消息,通知工作線(xiàn)程可以繼續(xù)執(zhí)行。那么問(wèn)題是不是出現(xiàn)在select函數(shù)上面?因?yàn)閟elect是一個(gè)系統(tǒng)調(diào)用,一直沒(méi)有懷疑,但已經(jīng)跟到這里來(lái)了,那就得看看。與select函數(shù)相關(guān)的有幾個(gè)重要的宏定義和說(shuō)明。主要實(shí)現(xiàn)在/usr/include/bits/typesizes.h,/usr/include/bits/select.h和/usr/include/sys/select.h這三個(gè)文件中。
FD_ZERO(fd_set *fdset):清空f(shuō)dset與所有文件句柄的聯(lián)系。FD_SET(int fd, fd_set *fdset):建立文件句柄fd與fdset的聯(lián)系。FD_CLR(int fd, fd_set *fdset):清除文件句柄fd與fdset的聯(lián)系。FD_ISSET(int fd, fd_set *fdset):檢查fdset聯(lián)系的文件句柄fd是否可讀寫(xiě),當(dāng)>0表示可讀寫(xiě)。
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個(gè)字節(jié) #define __NFDBITS (8 * (int) sizeof (__fd_mask)) // 64位 #define __FDMASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS)) //fd%64=N,則在第N位設(shè)置為1 #define __FDELT(d) ((d) / __NFDBITS) //表示在第幾個(gè)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)
通過(guò)FD_SET可以設(shè)置我們想要監(jiān)聽(tīng)的句柄,句柄信息存儲(chǔ)在fd_set位數(shù)組中,數(shù)組元素的個(gè)數(shù)由__FD_SETSIZE/64決定,對(duì)于__FD_SETSIZE=1024而言,整個(gè)數(shù)組只有16個(gè)long int。每個(gè)句柄占有一個(gè)位,就是1024個(gè)位,可以存儲(chǔ)1024個(gè)句柄。假設(shè)句柄值為138,那么138/64=2,138%64=10,那么這個(gè)句柄在數(shù)組的標(biāo)示在第2個(gè)long int的第10位置1。那么如果句柄值超出1024呢,這里不就溢出了?我仔細(xì)擼了擼代碼,發(fā)現(xiàn)根本就沒(méi)有容錯(cuò)判斷,如果句柄值超過(guò)1024就一定會(huì)溢出。由于select函數(shù)是遍歷數(shù)組中的每個(gè)位,然后去判斷該句柄是否可讀可寫(xiě),因此對(duì)于超過(guò)1024的句柄,永遠(yuǎn)也不會(huì)去判斷,因此主庫(kù)永遠(yuǎn)不知道備庫(kù)是否發(fā)送了響應(yīng)包。
(4)驗(yàn)證
上面只是理論分析,如果實(shí)際運(yùn)行的實(shí)例句柄確實(shí)是超過(guò)了1024,那么問(wèn)題就定位到了。
1.得到mysql進(jìn)程mysql-pid
ps –aux | grep mysqld | grep port
2.gdb attach到該進(jìn)程
gdb –p mysql-pid
3.找到ack_receive線(xiàn)程,并切換
info thread
thread thread_id
4.打印socket的值,這里fd值為2344。
(5)如何解
我們看到了由于__FD_SETSIZE的定義,一般是1024,導(dǎo)致select函數(shù)最多只能監(jiān)聽(tīng)1024個(gè)句柄,并且最大句柄值不超過(guò)1024。第一個(gè)方法是調(diào)大該參數(shù),但這種方法需要重新編譯linux內(nèi)核。而且由于select機(jī)制,每次都需要遍歷 的每一位來(lái)判斷句柄上是否有消息到來(lái),因此如果設(shè)置很大,將導(dǎo)致效率非常低。select是一種比較老的IO復(fù)用機(jī)制,比較先進(jìn)的poll,epoll都有類(lèi)似的功能,并且更強(qiáng)大,也沒(méi)有句柄總數(shù)和最大句柄的限制。有關(guān)select,poll,epoll等機(jī)制,大家可以去網(wǎng)上查資料,這里不展開(kāi)討論。
(6)官方版本
看了最新oracle官方版本git上5.7的源代碼,這塊也是用select來(lái)實(shí)現(xiàn)的,所以也存在類(lèi)似的問(wèn)題。當(dāng)然,由于句柄號(hào)有復(fù)用機(jī)制,當(dāng)實(shí)例上連接數(shù)很少,或者長(zhǎng)連接不多時(shí),不容易出現(xiàn)fd>1024的情況,所以這個(gè)bug不是很容易出現(xiàn),但問(wèn)題是普遍存在的。
(7)問(wèn)題延生
問(wèn)題定位后,另外一個(gè)問(wèn)題還困擾我了半天。因?yàn)閙ysql內(nèi)核中有監(jiān)聽(tīng)的部分有3塊,1是監(jiān)聽(tīng)端口的select,2是線(xiàn)程池的監(jiān)聽(tīng)epoll,3是半同步的select監(jiān)聽(tīng)。slave binlog dump的線(xiàn)程就是普通的工作線(xiàn)程,而工作線(xiàn)程的socket會(huì)受epoll的監(jiān)聽(tīng),這樣一來(lái),binlog dump的socket會(huì)同時(shí)受半同步的select監(jiān)聽(tīng)和線(xiàn)程池的epoll監(jiān)聽(tīng),這不亂了嗎?后來(lái)仔細(xì)看了看代碼,才發(fā)現(xiàn)線(xiàn)程池的epoll監(jiān)聽(tīng)采用的是EPOLLONESHOT模式,每次接收消息后會(huì)解綁,需要重新注冊(cè),因此不會(huì)出現(xiàn)同一個(gè)句柄被兩種監(jiān)聽(tīng)機(jī)制同時(shí)監(jiān)聽(tīng)的情況。
到此,排查問(wèn)題過(guò)程就結(jié)束了,結(jié)論是比較簡(jiǎn)單的,但定位這個(gè)問(wèn)題確實(shí)花費(fèi)了一些功夫。由于select一種比較通用的多路IO復(fù)用機(jī)制,因此有用到select函數(shù)的童鞋,可能要注意下它的限制。
相關(guān)文章
mysql學(xué)習(xí)筆記之?dāng)?shù)據(jù)引擎
插件式存儲(chǔ)引擎是MySQL數(shù)據(jù)庫(kù)最重要的特征之一,用戶(hù)可以根據(jù)應(yīng)用的需要尋找如何存儲(chǔ)和索引數(shù)據(jù)、是否使用事務(wù)等。MySQL默認(rèn)支持多種存儲(chǔ)引擎,以適用于不同領(lǐng)域的數(shù)據(jù)庫(kù)應(yīng)用需求,用戶(hù)可以通過(guò)選擇選擇不同的存儲(chǔ)引擎提供應(yīng)用的效率,提供靈活的存儲(chǔ)2017-02-02Mysql中Row size too large (> 8126)&n
本文主要介紹了Mysql中Row size too large (> 8126) 錯(cuò)誤的問(wèn)題解決,原因?qū)嵅迦氲男袛?shù)據(jù)可能太大了,超過(guò)了設(shè)定的闕值,下面就來(lái)看一下如何解決2024-07-07MySql中使用INSERT INTO語(yǔ)句更新多條數(shù)據(jù)的例子
這篇文章主要介紹了MySql中使用INSERT INTO語(yǔ)句更新多條數(shù)據(jù)的例子,MySQL的特有語(yǔ)法,需要的朋友可以參考下2014-06-06MySQL5.7升級(jí)MySQL8.0的完整卸載與安裝及連接Navicat的步驟
因?yàn)橐粋€(gè)項(xiàng)目交接需要需要將mysql物理備份文件還原至MySQL5.7,并且將mysql5.7升級(jí)到MySQL8.0,下面這篇文章主要給大家介紹了關(guān)于MySQL5.7升級(jí)MySQL8.0的完整卸載與安裝及連接Navicat的相關(guān)資料,需要的朋友可以參考下2023-03-03Advanced Pagination for MySQL(mysql高級(jí)分頁(yè))
看到葉金榮的一篇關(guān)于mysql分頁(yè)的文章,結(jié)合雅虎之前發(fā)的一篇PDF 談?wù)勛约旱目捶?/div> 2016-08-08mysql使用物理備份安裝xtrabackup的詳細(xì)過(guò)程
這篇文章主要介紹了mysql使用物理備份安裝xtrabackup的詳細(xì)過(guò)程,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-05-05最新評(píng)論