《Mysql應(yīng)用Mysql半同步復(fù)制原理及問(wèn)題排查》要點(diǎn):
本文介紹了Mysql應(yīng)用Mysql半同步復(fù)制原理及問(wèn)題排查,希望對(duì)您有用。如果有疑問(wèn),可以聯(lián)系我們。
MYSQL教程?
MYSQL教程mysql半同步復(fù)制和異步復(fù)制的差別如上述架構(gòu)圖所示:在mysql異步復(fù)制的情況下,Mysql Master Server將自己的Binary Log通過(guò)復(fù)制線程傳輸出去以后,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)制日志的完整性.
MYSQL教程1.問(wèn)題背景
MYSQL教程默認(rè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ò)程.
MYSQL教程2.半同步復(fù)制原理
MYSQL教程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)的線程(ack_receiver)接收備庫(kù)的響應(yīng)消息,并以通知機(jī)制告知主庫(kù)備庫(kù)已經(jīng)接收的日志,可以繼續(xù)執(zhí)行.有關(guān)半同步的具體實(shí)現(xiàn),可以參考另外一篇文章,mysql半同步(semi-sync)源碼實(shí)現(xiàn).
MYSQL教程3.問(wèn)題分析
MYSQL教程前面簡(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).
MYSQL教程(1).修改rpl_semi_sync_master_timeout參數(shù).
MYSQL教程半同步復(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í)間等待.線上這個(gè)值默認(rèn)是50ms,簡(jiǎn)單想是不是這個(gè)值太小了,遂將其改到10s,但問(wèn)題依然不解.
MYSQL教程(2).打印日志
MYSQL教程排查問(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)用.
MYSQL教程master:
MYSQL教程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.
MYSQL教程slave:
MYSQL教程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)
MYSQL教程從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ù)制.現(xiàn)在問(wèn)題就變成了,為什么master沒(méi)有收到?
MYSQL教程(3)select函數(shù)
MYSQL教程前面提到了,主庫(kù)實(shí)例上有一個(gè)專(zhuān)門(mén)接收響應(yīng)包的線程(ack_receiver),它通過(guò)select函數(shù)監(jiān)聽(tīng)socket,發(fā)現(xiàn)有slave的響應(yīng)消息后,讀取消息,通知工作線程可以繼續(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è)文件中.
MYSQL教程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ě).
MYSQL教程
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)
MYSQL教程通過(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)包.
MYSQL教程(4)驗(yàn)證
MYSQL教程上面只是理論分析,如果實(shí)際運(yùn)行的實(shí)例句柄確實(shí)是超過(guò)了1024,那么問(wèn)題就定位到了.
MYSQL教程1.得到mysql進(jìn)程mysql-pid
MYSQL教程ps Caux | grep mysqld | grep port
MYSQL教程2.gdb attach到該進(jìn)程
MYSQL教程gdb Cp mysql-pid
MYSQL教程3.找到ack_receive線程,并切換
MYSQL教程info thread
thread thread_id
MYSQL教程4.打印socket的值,這里fd值為2344.
MYSQL教程
MYSQL教程(5)如何解
MYSQL教程我們看到了由于__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)討論.
MYSQL教程(6)官方版本
MYSQL教程看了最新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)題是普遍存在的.
MYSQL教程(7)問(wèn)題延生
MYSQL教程問(wèn)題定位后,另外一個(gè)問(wèn)題還困擾我了半天.因?yàn)閙ysql內(nèi)核中有監(jiān)聽(tīng)的部分有3塊,1是監(jiān)聽(tīng)端口的select,2是線程池的監(jiān)聽(tīng)epoll,3是半同步的select監(jiān)聽(tīng).slave binlog dump的線程就是普通的工作線程,而工作線程的socket會(huì)受epoll的監(jiān)聽(tīng),這樣一來(lái),binlog dump的socket會(huì)同時(shí)受半同步的select監(jiān)聽(tīng)和線程池的epoll監(jiān)聽(tīng),這不亂了嗎?后來(lái)仔細(xì)看了看代碼,才發(fā)現(xiàn)線程池的epoll監(jiān)聽(tīng)采用的是EPOLLONESHOT模式,每次接收消息后會(huì)解綁,需要重新注冊(cè),因此不會(huì)出現(xiàn)同一個(gè)句柄被兩種監(jiān)聽(tīng)機(jī)制同時(shí)監(jiān)聽(tīng)的情況.
MYSQL教程到此,排查問(wèn)題過(guò)程就結(jié)束了,結(jié)論是比較簡(jiǎn)單的,但定位這個(gè)問(wèn)題確實(shí)花費(fèi)了一些功夫.由于select一種比較通用的多路IO復(fù)用機(jī)制,因此有用到select函數(shù)的童鞋,可能要注意下它的限制.
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/5432.html