您好,登錄后才能下訂單哦!
本文主要給大家簡單講講排查mysql半同步復制問題詳細步驟,相關(guān)專業(yè)術(shù)語大家可以上網(wǎng)查查或者找一些相關(guān)書籍補充一下,這里就不涉獵了,我們就直奔主題吧,希望排查mysql半同步復制問題詳細步驟這篇文章可以給大家?guī)硪恍嶋H幫助。
1.問題背景
默認情況下,線上的mysql復制都是異步復制,因此在極端情況下,主備切換時,會有一定的概率備庫比主庫數(shù)據(jù)少,因此切換后,我們會通過工具進行回滾回補,確保數(shù)據(jù)不丟失。半同步復制則要求主庫執(zhí)行每一個事務,都要求至少一個備庫成功接收后,才真正執(zhí)行完成,因此可以保持主備庫的強一致性。為了確保主備庫數(shù)據(jù)強一致,減少數(shù)據(jù)丟失,嘗試在生產(chǎn)環(huán)境中開啟mysql的復制的半同步(semi-sync)特性。實際操作過程中,發(fā)現(xiàn)大部分實例半同步都可以正常運行,但有少部分實例始終開不起來(只能以普通復制方式運行),更奇葩的是同一個主機的兩個實例,一個能開啟,一個不能。最終定位的問題也很簡單,但排查出來還是花了一番功夫,下文將描述整個問題的排查過程。
2.半同步復制原理
mysql的主備庫通過binlog日志保持一致,主庫本地執(zhí)行完事務,binlog日志落盤后即返回給用戶;備庫通過拉取主庫binlog日志來同步主庫的操作。默認情況下,主庫與備庫并沒有嚴格的同步,因此存在一定的概率備庫與主庫的數(shù)據(jù)是不對等的。半同步特性的出現(xiàn),就是為了保證在任何時刻主備數(shù)據(jù)一致的問題。相對于異步復制,半同步復制要求執(zhí)行的每一個事務,都要求至少有一個備庫成功接收后,才返回給用戶。實現(xiàn)原理也很簡單,主庫本地執(zhí)行完畢后,等待備庫的響應消息(包含最新備庫接收到的binlog(file,pos)),接收到備庫響應消息后,再返回給用戶,這樣一個事務才算真正完成。在主庫實例上,有一個專門的線程(ack_receiver)接收備庫的響應消息,并以通知機制告知主庫備庫已經(jīng)接收的日志,可以繼續(xù)執(zhí)行。有關(guān)半同步的具體實現(xiàn),可以參考另外一篇文章,mysql半同步(semi-sync)源碼實現(xiàn)。
3.問題分析
前面簡單介紹了半同步復制的原理,現(xiàn)在來看看具體問題。在主備庫打開半同步開關(guān)后,問題實例的狀態(tài)變量"Rpl_semi_sync_master_status"始終是OFF,表示復制一直運行在普通復制的狀態(tài)。
(1).修改rpl_semi_sync_master_timeout參數(shù)。
半同步復制參數(shù)中有一個rpl_semi_sync_master_timeout參數(shù),用以控制主庫等待備庫響應消息的時間,如果超過該值,則認為備庫一直沒有收到(備庫可能掛了,也可能備庫執(zhí)行很慢,較主庫相差很遠),這個時候復制會切換為普通復制,避免主庫的執(zhí)行事務長時間等待。線上這個值默認是50ms,簡單想是不是這個值太小了,遂將其改到10s,但問題依然不解。
(2).打印日志
排查問題最簡單最笨的方法就是打日志,看看到底是哪個環(huán)節(jié)出了問題。主庫和備庫分別有rpl_semi_sync_master_trace_level和rpl_semi_sync_slave_trace_level參數(shù)來控制半同步復制打印日志。將兩個參數(shù)值設置為80(64+16),記錄詳細日志信息,以及進出的函數(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 enter2016-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后,仍然沒有收到響應,則認為超時,遂將半同步模式關(guān)閉,切換為普通模式。但從slave日志來看,在2016-01-04 18:00:30已經(jīng)將(mysql-bin.000006, 500717950)發(fā)送給主庫,表示已經(jīng)收到該日志。這就說明,master日志已經(jīng)打了semi-sync標,slave收到了日志,并且也回了包,master也確實等了10s,就是沒有收到包,所以就切換為普通復制?,F(xiàn)在問題就變成了,為什么master沒有收到?
(3)select函數(shù)
前面提到了,主庫實例上有一個專門接收響應包的線程(ack_receiver),它通過select函數(shù)監(jiān)聽socket,發(fā)現(xiàn)有slave的響應消息后,讀取消息,通知工作線程可以繼續(xù)執(zhí)行。那么問題是不是出現(xiàn)在select函數(shù)上面?因為select是一個系統(tǒng)調(diào)用,一直沒有懷疑,但已經(jīng)跟到這里來了,那就得看看。與select函數(shù)相關(guān)的有幾個重要的宏定義和說明。主要實現(xiàn)在/usr/include/bits/typesizes.h,/usr/include/bits/select.h和/usr/include/sys/select.h這三個文件中。
/ __NFDBITS]; /= ( __FD_SET_SIZE 1024 __fd_mask; __NFDBITS (8 * (int) sizeof (__fd_mask)) __FDMASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS)) __FDELT(d) ((d) / __NFDBITS) __FDS_BITS(set) ((set)->__fds_bits) __FD_SET(d, set) (__FDS_BITS (set)[__FDELT (d)] |= __FDMASK (d)) __FD_CLR(d, set) (__FDS_BITS (set)[__FDELT (d)] &= ~__FDMASK (d)) __FD_ISSET(d, set) \)[__FDELT (d)] & __FDMASK (d)) != )
通過FD_SET可以設置我們想要監(jiān)聽的句柄,句柄信息存儲在fd_set位數(shù)組中,數(shù)組元素的個數(shù)由__FD_SETSIZE/64決定,對于__FD_SETSIZE=1024而言,整個數(shù)組只有16個long int。每個句柄占有一個位,就是1024個位,可以存儲1024個句柄。假設句柄值為138,那么138/64=2,138%64=10,那么這個句柄在數(shù)組的標示在第2個long int的第10位置1。那么如果句柄值超出1024呢,這里不就溢出了?我仔細擼了擼代碼,發(fā)現(xiàn)根本就沒有容錯判斷,如果句柄值超過1024就一定會溢出。由于select函數(shù)是遍歷數(shù)組中的每個位,然后去判斷該句柄是否可讀可寫,因此對于超過1024的句柄,永遠也不會去判斷,因此主庫永遠不知道備庫是否發(fā)送了響應包。
(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。
p m_slaves
(5)如何解
我們看到了由于__FD_SETSIZE的定義,一般是1024,導致select函數(shù)最多只能監(jiān)聽1024個句柄,并且最大句柄值不超過1024。第一個方法是調(diào)大該參數(shù),但這種方法需要重新編譯linux內(nèi)核。而且由于select機制,每次都需要遍歷 的每一位來判斷句柄上是否有消息到來,因此如果設置很大,將導致效率非常低。select是一種比較老的IO復用機制,比較先進的poll,epoll都有類似的功能,并且更強大,也沒有句柄總數(shù)和最大句柄的限制,通過poll或者epoll實現(xiàn)監(jiān)聽這部分功能,就可以徹底解決問題。有關(guān)select,poll,epoll等機制,大家可以去網(wǎng)上查資料,這里不展開討論。
臨時解決方法,前面提到的方法要么需要重新編譯linux內(nèi)核,要么需要改mysql內(nèi)核代碼,這里提供一種臨時的解決方法??梢栽趕lave端執(zhí)行stop slave,start slave命令,重建主庫與備庫的socket連接,只要1-1024的fd沒有被全部使用,新建的socket fd就有機會小于1024,這樣select機制不會出問題,半同步也就能正常運行。但如果1-1024的fd全部被長連接使用,那么這種方法就無能為力了。
(6)官方版本
看了最新oracle官方版本git上5.7的源代碼,這塊也是用select來實現(xiàn)的,所以也存在類似的問題。當然,由于句柄號有復用機制,當實例上連接數(shù)很少,或者長連接不多時,不容易出現(xiàn)fd>1024的情況,所以這個bug不是很容易出現(xiàn),但問題是普遍存在的。
(7)問題延伸
問題定位后,另外一個問題還困擾我了半天。因為mysql內(nèi)核中有監(jiān)聽的部分有3塊,1是監(jiān)聽端口的select,2是線程池的監(jiān)聽epoll,3是半同步的select監(jiān)聽。slave binlog dump的線程就是普通的工作線程,而工作線程的socket會受epoll的監(jiān)聽,這樣一來,binlog dump的socket會同時受半同步的select監(jiān)聽和線程池的epoll監(jiān)聽,這不亂了嗎?后來仔細看了看代碼,才發(fā)現(xiàn)線程池的epoll監(jiān)聽采用的是EPOLLONESHOT模式,每次接收消息后會解綁,需要重新注冊,因此不會出現(xiàn)同一個句柄被兩種監(jiān)聽機制同時監(jiān)聽的情況。
到此,排查問題過程就結(jié)束了,結(jié)論是比較簡單的,但定位這個問題確實花費了一些功夫。由于select一種比較通用的多路IO復用機制,因此有用到select函數(shù)的童鞋,可能要注意下它的限制。
排查mysql半同步復制問題詳細步驟就先給大家講到這里,對于其它相關(guān)問題大家想要了解的可以持續(xù)關(guān)注我們的行業(yè)資訊。我們的板塊內(nèi)容每天都會捕捉一些行業(yè)新聞及專業(yè)知識分享給大家的。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。