mysql半同步複製問題排查

来源:http://www.cnblogs.com/cchust/archive/2016/01/05/5100067.html
-Advertisement-
Play Games

1.問題背景 預設情況下,線上的mysql複製都是非同步複製,因此在極端情況下,主備切換時,會有一定的概率備庫比主庫數據少,因此切換後,我們會通過工具進行回滾回補,確保數據不丟失。半同步複製則要求主庫執行每一個事務,都要求至少一個備庫成功接收後,才真正執行完成,因此可以保持主備庫的強一致性。為了確.....


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
p m_slaves

(5)如何解
     我們看到了由於__FD_SETSIZE的定義,一般是1024,導致select函數最多只能監聽1024個句柄,並且最大句柄值不超過1024。第一個方法是調大該參數,但這種方法需要重新編譯linux內核。而且由於select機制,每次都需要遍歷 的每一位來判斷句柄上是否有消息到來,因此如果設置很大,將導致效率非常低。select是一種比較老的IO復用機制,比較先進的poll,epoll都有類似的功能,並且更強大,也沒有句柄總數和最大句柄的限制,通過poll或者epoll實現監聽這部分功能,就可以徹底解決問題。有關select,poll,epoll等機制,大家可以去網上查資料,這裡不展開討論。

      臨時解決方法,前面提到的方法要麼需要重新編譯linux內核,要麼需要改mysql內核代碼,這裡提供一種臨時的解決方法。可以在slave端執行stop slave,start slave命令,重建主庫與備庫的socket連接,只要1-1024的fd沒有被全部使用,新建的socket fd就有機會小於1024,這樣select機制不會出問題,半同步也就能正常運行。但如果1-1024的fd全部被長連接使用,那麼這種方法就無能為力了。

(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函數的童鞋,可能要註意下它的限制。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 01.簡述資料庫完整性及其作用?解析:數據的準確性,保證數據中數據的準確性。 02.基本操作語句(DML DDL DCL)有哪些?語法是?DML(data manipulation language):自動提交的資料庫操作語言它們是SELECT、UPDATE、INSERT、DELETE,就象它的名....
  • 本文通過java代碼使用jdbc連接hive,詳細解釋了其操作步驟與問題,適用於hive1.2版本
  • 今天Linux伺服器機子重啟了下,Oracle啟動不起來,提示:解決ORA-00824: cannot set sga_target due to existing看了很多解決方法,發現下麵這個特別好,特別簡單轉:http://blog.chinaunix.net/uid-22448653-id-3...
  • 統計分析中Type I Error與Type II Error的區別 ============== 在統計分析中,經常提到Type I Error和Type II Error。他們的基本概念是什麼?有什麼區別? 下麵的表格顯示 between truth/falseness of the null ...
  • 實體類:using MongoDB.Bson;namespace WindowsFormsApp{ class User { //public ObjectId _id; //BsonType.ObjectId 這個對應了 MongoDB.Bson.ObjectId ...
  • 一、業務場景我們在實際生產環境中遇到了這樣一種需求,即需要檢索一個父子關係的子樹數據估計大家也遇到過類似的場景,最典型的就是省市數據,其中path欄位是按層級關係生成的行政區路徑:如果我們已知某市名,想查出同級和高一級的省名,如保定市同級和上級河北省,那麼我們有什麼實現方式呢給大家10秒鐘,快速搶答...
  • SQL Server無法收縮日誌文件 2 因為邏輯日誌文件的總數不能少於 2問題最近伺服器執行收縮日誌文件大小的job老是報錯我所用的一個批量收縮日誌腳本USE [master]GO/****** Object: StoredProcedure [dbo].[ShrinkUser_DATABASE....
  • 標簽:主從概述本篇文章主要介紹mysql主從的搭建過程和中間涉及的一些概念知識,希望能最全面的將mysql主從所涉及到的知識都概況進來;環境已經安裝好了mysql,這裡就不介紹mysql的安裝方法。測試環境:主:mysql(5.6.21),linux:redhat 6.0,ip:192.168.1....
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...