淺談Mysql讀寫分離的坑以及應對的方案

来源:https://www.cnblogs.com/jingdongkeji/archive/2023/09/04/17676520.html
-Advertisement-
Play Games

本文簡單介紹了讀寫分離架構,和出現主從延遲後,如果我們用的讀寫分離的架構,那麼我們應該怎麼處理這種情況,相信在日常我們的主從還是或多或少的存在延遲。上面介紹的幾種方案,有些方案看上去十分不靠譜,有些方案做了一些妥協,但是都有實際的應用場景,需要我們根據自身的業務情況,合理選擇對應的方案。 ...


一、主從架構

為什麼我們要進行讀寫分離?個人覺得還是業務發展到一定的規模,驅動技術架構的改革,讀寫分離可以減輕單台伺服器的壓力,將讀請求和寫請求分流到不同的伺服器,分攤單台服務的負載,提高可用性,提高讀請求的性能。

上面這個圖是一個基礎的Mysql的主從架構,1主1備3從。這種架構是客戶端主動做的負載均衡,資料庫的連接信息一般是放到客戶端的連接層,也就是說由客戶端來選擇資料庫進行讀寫

上圖是一個帶proxy的主從架構,客戶端只和proxy進行連接,由proxy根據請求類型和上下文決定請求的分發路由。

兩種架構方案各有什麼特點:

1.客戶端直連架構,由於少了一層proxy轉發,所以查詢性能會比較好點兒,架構簡單,遇到問題好排查。但是這種架構,由於要瞭解後端部署細節,出現主備切換,庫遷移的時候客戶端都會感知到,並且需要調整庫連接信息

2.帶proxy的架構,對客戶端比較友好,客戶端不需要瞭解後端部署細節,連接維護,後端信息維護都由proxy來完成。這樣的架構對後端運維團隊要求比較高,而且proxy本身也要求高可用,所以整體架構相對來說比較複雜

但是不論使用哪種架構,由於主從之間存在延遲,當一個事務更新完成後馬上發起讀請求,如果選擇讀從庫的話,很有可能讀到這個事務更新之前的狀態,我們把這種讀請求叫做過期讀。出現主從延遲的情況有多種,有興趣的同學可以自己瞭解一下,雖然出現主從延遲我們同樣也有應對策略,但是不能100%避免,這些不是我們本次討論的範圍,我們主要討論一下如果出現主從延遲,剛好我們的讀走的都是從庫,我們應該怎麼應對?

首先我把應對的策略總結一下:

  • 強制走主庫
  • sleep方案
  • 判斷主從無延遲
  • 等主庫位點
  • 等GTID方案

接下來基於上述的幾種方案,我們逐個討論一下怎麼實現和有什麼問題。

二、主從同步

在開始介紹主從延遲解決方案前先簡單的回顧一下主從的同步

上圖表示了一個update語句從節點A同步到節點B的完整過程

備庫B和主庫A維護了一個長連接,主庫A內部有一個線程,專門用來服務備庫B的連接。一個事務日誌同步的完整流程是:

1.在備庫 B 上通過 change master 命令,設置主庫 A 的 IP、埠、用戶名、密碼,以及要從哪個位置開始請求 binlog,這個位置包含文件名和日誌偏移量。

2.在備庫 B 上執行 start slave 命令,這時候備庫會啟動兩個線程,就是圖中的 io_thread 和 sql_thread。

3.其中 io_thread 負責與主庫建立連接。

4.主庫 A 校驗完用戶名、密碼後,開始按照備庫 B 傳過來的位置,從本地讀取 binlog,發給 B。備庫 B 拿到 binlog 後,寫到本地文件,稱為中轉日誌(relay log)。

5.sql_thread 讀取中轉日誌,解析出日誌里的命令,並執行。

上圖中紅色箭頭,如果用顏色深淺表示併發度的話,顏色越深併發度越高,所以主從延遲時間的長短取決於備庫同步線程執行中轉日誌(圖中的relay log)的快慢。總結一下可能出現主從延遲的原因:

1.主庫併發高,TPS大,備庫壓力大執行日誌慢

2.大事務,一個事務在主庫執行5s,那麼同樣的到備庫也得執行5s,比如一次性刪除大量的數據,大表DDL等都是大事務

3.從庫的並行複製能力,Msyql5.6之前的版本是不支持並行複製的也就是上圖的模型。並行複製也比較複雜,就不在這兒贅述了,大家可以自行複習瞭解一下。

三、主從延遲解決方案

1.強制走主庫

這種方案就是要對我們的請求進行分類,通常可以將請求分成兩類:

1.對於必須要拿到最新結果的請求,可以強制走主庫

2.對於可以讀到舊數據的請求,可以分配到從庫

這種方案是最簡單的方案,但是這種方案有一個缺點就是,對於所有的請求都不能是過期讀的請求,那麼所有的壓力就又來到了主庫,就得放棄讀寫分離,放棄擴展性

2.sleep方案

sleep方案就是每次查詢從庫之前都先執行一下:select sleep(1),類似這樣的命令,這種方式有兩個問題:

1.如果主從延遲大於1s,那麼依然讀到的是過期狀態

2.如果這個請求可能0.5s就能在從庫拿到結果,仍然要等1s

這種方案看起來十分的不靠譜,不專業,但是這種方案確實也有使用的場景。

之前在做項目的時候,有這樣麽一種場景,就是我們先寫主庫,寫完後,發送一個MQ消息,然後消費方接到消息後,調用我們的查詢介面查數據,當然我們也是讀寫分離的模式,就出現了查不到數據的情況,這個時候建議消費方對消息進行一個延遲消費,比如延遲30ms,然後問題就解決了,這種方式類似sleep方案,只不過把sleep放到了調用方

3.判斷主從無延遲方案

  1. 命令判斷

show slave status,這個命令是在從庫上執行的,執行的結果裡面有個seconds_behind_master欄位,這個欄位表示主從延遲多少s,註意單位是秒。所以這種方案就是通過判斷當前這個值是否為0,如果為0則直接查詢獲取結果,如果不為0,則一直等待,直到主從延遲變為0

因為這個值是秒級的,但是我們的一些場景下是毫秒級的請求,所以通過這個方式判斷,不是特別精確

  1. 對比位點判斷主從無延遲

上圖是執行一次show slave status 部分結果

  • Master_Log_File和Read_Master_Log_Pos表示讀到的主庫的最新的位點
  • Relay_Master_Log_File和Exec_Master_Log_Pos表示備庫執行的最新的位點

如果Master_Log_File和Relay_Master_Log_File,Read_Master_Log_Pos和Exec_Master_Log_Pos這兩組值完全一致,表示主從之間是沒有延遲的

3)對比GTID判斷主從無延遲

  • Auto_Position:1表示這對主從之間啟用了GTID協議
  • Retrieved_Gtid_Set:表示從庫接收到的所有的GTID的集合
  • Executed_Gtid_Set:表示從庫執行完成的所有的GTID集合

通過比較Retrieved_Gtid_Set和Executed_Gtid_Set集合是否一致,來確定主從是否存在延遲。

可見對比位點和對比GTID集合,比sleep要準確一點兒,在查詢之前都可以先判斷一下是否接收到的日誌都執行完成了,雖然準確度提升了,但是還達不到精確,為啥這麼說呢?

先回顧一下binlog在一個事物下的狀態

1.主庫執行完成,寫入binlog,反饋給客戶端

2.binlog被從主庫發送到備庫,備庫接收到日誌

3.備庫執行binlog

我們上面判斷主備無延遲方案,都是判斷備庫收到的日誌都執行過了,但是從binlog在主備之間的狀態分析,可以看出,還有一部分日誌處於客戶端已經收到提交確認,但是備庫還沒有收到日誌的狀態

這個時候主庫執行了3個事物,trx1,trx2,trx3,其中

  • trx1,trx2已經傳到從庫,並且從庫已經執行完成
  • trx3主庫已經執行完成,並且已經給客戶端回覆,但是還沒有傳給從庫

這個時候如果在從庫B執行查詢,按照上面我們判斷位點的方式,這個時候主從是沒有延遲的,但是還查不到trx3,嚴格說就是出現了"過期讀"。那麼這個問題有什麼方法可以解決麽?

要解決這個問題,可以引入半同步複製,也就是semi-sync repliacation(參考:https://dev.mysql.com/doc/refman/8.0/en/replication-semisync.html)。

可以通過

show variables like '%rpl_semi_sync_master_enabled%'
show variables like '%rpl_semi_sync_slave_enabled%'


這兩個命令來查看主從是否都開啟了半同步複製。

semi-sync做了這樣的設計:

1.事物提交的時候,主庫把binlog發給從庫

2.從庫接收到主庫發過來的binlog,給主庫一個ack確認,表示收到了

3.主庫收到這個ack確認後,才給客戶端返回一個事物完成的確認

也就是啟用了semi-sync,表示所有返回給客戶端已經確認完成的事物,從庫都收到了binlog日誌,這樣通過semi-sync配合判斷位點的方式,就可以確定在從庫上的查詢,避免了過期讀的出現。

但是semi-sync配合判斷位點的方式,只適用一主一備的情況,在一主多從的情況下,主庫只要收到一個從庫的ack確認,就給客戶端返回事物執行完成的確認,這個時候在從庫上執行查詢就有兩種情況

  • 如果查詢剛好是在給主庫響應ack確認的從庫上,那麼可以查詢到正確的數據
  • 但是如果請求落到其他的從庫上,他們可能還沒收到日誌,所以依然可能存在過期讀

其實通過判斷同步位點或者GTID集合的方案,還存在一個潛在的問題,就是業務高峰期,主庫的位點或者GITD集合更新的非常快,那麼兩個位點的判斷一直不相等,很可能出現從庫一直無法響應查詢請求的情況。

上面的兩種方案在靠譜程度和精確性上都差了一點兒,接下來介紹兩種相對靠譜和精確一點兒的方案

4.等主庫位點

要理解等主庫位點,先介紹一條命令

select master_pos_wait(file, pos[, timeout]);

這條命令執行的邏輯是:

1.首先是在從庫執行的

2.參數file和pos是主庫的binlog文件名和執行到的位置

3.timeout參數是非必須,設置為正整數N,表示這個函數最多等到N秒

這個命令執行結果M可能存在的情況:

  • M>0表示從命令執行開始,到應用完file和pos表示的binlog位置,一共執行了M個事務
  • 如果執行期間,備庫的同步線程發生異常,則返回null
  • 如果等待超過N秒,返回-1
  • 如果剛開始執行的時候,發現已經執行了過了這個pos,則返回0

當一個事務執行完成後,我們要馬上發起一個查詢請求,可以通過下麵的步驟實現:

1.當一個事務執行完成後,馬上執行show master status,獲取主庫的File和Position

2.選擇一個從庫執行查詢

3.在從庫上執行 select master_pos_wait(File,Poistion,1)

4.如果返回的值>=0,則在這個從庫上執行

5.否則回主庫查詢

這裡我們假設,這條查詢請求在從庫上最多等待1s,那麼如果1s內master_pos_wait返回一個大於等於0的數,那麼就能保證在這個從庫上能查到剛執行完的事務的最新的數據。

上述的步驟5是這類方案的兜底方案,因為從庫的延遲時間不可控,不能無限等待,所以如果超時,就應該放棄,到主庫查詢。

可能有同學會覺的,如果所有的延遲都超過1s,那麼所有的壓力都到了主庫,確實是這樣的,但是按照我們設定的不允許出現過期讀,那麼就只有兩種選擇,要麼超時放棄,要麼轉到主庫,具體選擇哪種,需要我們根據業務進行具體的分析。

5.等GTID方案

如果資料庫開啟的GTID模式,那麼相應的也有等GTID的方案

 select wait_for_executed_gtid_set(gtid_set, 1);


這條命令的邏輯是:

1.等待,直到這個庫執行的事務中包含傳入的giid_set集合,返回0

2.超時返回1

在前面等待主庫位點的方案中,執行完事務後,需要到主庫執行show master status。從mysql5.7.6開始,允許事務執行完成後,把這個事務執行的GTID返回給客戶端,這樣等待GTIID的方案就減少了一次查詢。

這時等GTID方案的流程就變成這樣:

1.事務執行完成後,從返回包解析獲取這個事務的GTID,記為gtid1

2.選定一個從庫執行查詢

3.在從庫上執行select wait_for_executed_gtid_set(gtid1,1)

4.如果返回0,則在這個從庫上執行查詢

5.否則回到主庫查詢

和等待主庫位點方案一樣,最後的兜底方案都是轉到主庫查詢了,需要綜合業務考慮確定方案

上面的事物執行完成後,從返回的包中解析GTID,mysql其實沒有提供對應的命令,可以參考Mysql提供的api(https://dev.mysql.com/doc/c-api/8.0/en/mysql-session-track-get-first.html),在我們的客戶端可以調用這個函數獲取GTID

四、總結

以上簡單介紹了讀寫分離架構,和出現主從延遲後,如果我們用的讀寫分離的架構,那麼我們應該怎麼處理這種情況,相信在日常我們的主從還是或多或少的存在延遲。上面介紹的幾種方案,有些方案看上去十分不靠譜,有些方案做了一些妥協,但是都有實際的應用場景,需要我們根據自身的業務情況,合理選擇對應的方案。

但話說回來,導致過期讀的本質還是一寫多讀導致的,在實際的應用中,可能有別的不用等待就可以水平擴展的資料庫方案,但這往往都是通過犧牲寫性能獲得的,也就是需要我們在讀性能和寫性能之間做個權衡。

文中有不太嚴謹或者錯誤的地方還望大家多多指正。

作者:京東零售 尚有智

來源:京東雲開發者社區 轉載請註明來源


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

-Advertisement-
Play Games
更多相關文章
  • 寶塔Linux面板是提升運維效率的伺服器管理軟體,目前使用免費的版本功能齊全,已經足夠使用了。 [西瓜程式猿]使用阿裡雲伺服器網以CentOS操作系統為例,安裝寶塔Linux面板,先遠程連接到雲伺服器,然後執行寶塔面板安裝命令,系統會自動安裝寶塔面板,安裝完成後會返回面板地址、賬號和密碼 。 ...
  • 本篇文章探索了文件系統的功能規劃,著重討論了文件存儲、索引節點和目錄項的管理、緩存策略以及文件數據的存儲等方面。文件系統作為電腦系統中重要的組成部分,對於實現高效、可靠的文件管理與訪問機制至關重要。通過深入瞭解文件系統的基本單位、元信息記錄和目錄結構,我們可以更好地理解文件系統的工作原理,本文旨在... ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202309/3076680-20230904164459431-1322523641.png) # 1. 儘管SQL標準指定了部分函數,但資料庫廠商並沒有遵循這些函數規範 # 2. 字元串 ## 2.1. c ...
  • 1. SQL語句類型 1. DDL(Data Definition Language,數據定義語言): DDL語句用於定義資料庫對象(如表、索引、視圖等)。常見的DDL語句包括: CREATE:用於創建資料庫對象,如創建表、索引、視圖等。 ALTER:用於修改資料庫對象的結構,如修改表的列、添加約束 ...
  • Redis OSS的邏輯資料庫,無論是自部署還是作為ElastiCache等托管服務啟動,其目的都是通過減少管理需求並提供一系列的預設設置來簡化開發人員的工作。然而,在實際生產中,當您的功能和操作需求發生變化時,單個Redis實例可能不再足夠。 ...
  • 如今隨著互聯網技術快速發展,業務越來越複雜,系統的高併發和關鍵數據的場景越來越多。在分散式系統中,機器宕機和消息丟失也是需要重點關註的問題,其中的一個典型就是冪等性問題。 ...
  • 伺服器顯卡在高性能計算和人工智慧應用中扮演著至關重要的角色。高性能計算廣泛應用於科學計算、工程設計、氣象預測等領域,而人工智慧應用則涵蓋了機器學習、深度學習、圖像識別等領域。這些應用需要大量的計算資源和高效的演算法來處理大規模的數據集,而伺服器顯卡正是在這樣的應用中發揮重要作用。 ...
  • 為解決用戶面臨的 MongoDB 遷移問題,玖章算術旗下的雲原生智能數據管理平臺 NineData 推出了 MongoDB 業務不停服數據遷移能力。NineData 實現了完全自動化的全量數據遷移,以及增量數據的採集複製能力。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...