一:讀寫分離
- 概念
- 讀寫分離的主要目標就是分攤主庫的壓力。
- 基本架構
- -
二:兩種讀寫分離的架構特點
- 客戶端直連方案
- 因為少了一層 proxy 轉發,所以查詢性能稍微好一點兒,並且整體架構簡單,排查問題更方便。
- 但是這種方案,由於要了解后端部署細節,所以在出現主備切換、庫遷移等操作的時候,客戶端都會感知到,並且需要調整數據庫連接信息。
- 你可能會覺得這樣客戶端也太麻煩了,信息大量冗余,架構很丑。
- 其實也未必,一般采用這樣的架構,一定會伴隨一個負責管理后端的組件,比如 Zookeeper,盡量讓業務端只專注於業務邏輯開發。
- proxy 方案
- 帶 proxy 的架構,對客戶端比較友好。客戶端不需要關注后端細節,連接維護、后端信息維護等工作,都是由 proxy 完成的。
- 但這樣的話,對后端維護團隊的要求會更高。而且,proxy 也需要有高可用架構。
- 因此,帶 proxy 架構的整體就相對比較復雜。
三:什么是“過期讀” ?
- “在從庫上會讀到系統的一個過期狀態”的現象,暫且稱之為“過期讀”。
四:處理 “過期讀” 的方案?
- 強走主庫方案
- sleep 方案;
- 判斷主備無延遲方案;
- 配合 semi-sync 方案;
- 等主庫位點方案;
- 等 GTID 方案。
五:強走主庫方案
- 原理
- 強制走主庫方案其實就是,將查詢請求做分類。
- 對於必須要拿到最新結果的請求,強制將其發到主庫上。
- 比如,在一個交易平台上,賣家發布商品以后,馬上要返回主頁面,看商品是否發布成功。
- 那么,這個請求需要拿到最新的結果,就必須走主庫。
- 對於可以讀到舊數據的請求,才將其發到從庫上。
- 在這個交易平台上,買家來逛商鋪頁面,就算晚幾秒看到最新發布的商品,也是可以接受的。那么,這類請求就可以走從庫。
- 問題
- 這個方案最大的問題在於,有時候你會碰到“所有查詢都不能是過期讀”的需求,比如一些金融類的業務。
- 這樣的話,你就要放棄讀寫分離,所有讀寫壓力都在主庫,等同於放棄了擴展性。
六:sleep 方案
- 原理
- 主庫更新后,讀從庫之前先 sleep 一下。具體的方案就是,類似於執行一條 select sleep(1) 命令。
- 這個方案的假設是,大多數情況下主備延遲在 1 秒之內,做一個 sleep 可以有很大概率拿到最新的數據。
- 如如在客戶端在下單完成后做1s的loading,其實等於變相的等待了從庫1s。
- 問題
- sleep 方案確實解決了一定場景下的過期讀問題。
- 但,從嚴格意義上來說,這個方案存在的問題就是不精確。
- 這個不精確包含了兩層意思:
- 如果這個查詢請求本來 0.5 秒就可以在從庫上拿到正確結果,也會等 1 秒;
- 如果延遲超過 1 秒,還是會出現過期讀。
七:判斷主備無延遲方案
- 原理
- 通過 show slave status;結果
-
- 對比 seconds_behind_master 判斷主備無延遲(精度S)
- 每次從庫執行查詢請求前,先判斷 seconds_behind_master 是否已經等於 0。
- 如果還不等於 0 ,那就必須等到這個參數變為 0 才能執行查詢請求。
- 對比 點位 判斷主備無延遲
- 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
- 這兩組值完全相同,就表示接收到的日志已經同步完成。
- 對比 GTID 判斷主備無延遲
- Auto_Position=1 ,表示這對主備關系使用了 GTID 協議。
- Retrieved_Gtid_Set,是備庫收到的所有日志的 GTID 集合;
- Executed_Gtid_Set,是備庫所有已經執行完成的 GTID 集合。
- 如果這兩個集合相同,也表示備庫接收到的日志都已經同步完成。
- 小結
- 雖然等待無延遲是可以解決問題,但是可能存在主備一直不一致,導致備庫無法讀取的問題。
- 對比位點和對比 GTID 這兩種方法,都要比判斷 seconds_behind_master 是否為 0 更准確。
- 這幾種辦法並沒有達到 “精確” 的程度,可能存在 主庫已經執行,但是還沒有發送給備庫的情況,導致過期讀。(通過 semi-sync 解決)
八: 等主庫點位方案 / 等GTID方案
- 這兩個方案都是 在備庫執行,等待一定時間,如果在時間內 主庫點位 / GTID 同步,則在備庫執行,否則到主庫執行。
九:小結
- 這幾種方案中,有的方案看上去是做了妥協,有的方案看上去不那么靠譜兒,但都是有實際應用場景的,你需要根據業務需求選擇。
- 即使是最后等待位點和等待 GTID 這兩個方案,雖然看上去比較靠譜兒,但仍然存在需要權衡的情況。
- 如果所有的從庫都延遲,那么請求就會全部落到主庫上,這時候會不會由於壓力突然增大,把主庫打掛了呢?
- 其實,在實際應用中,這幾個方案是可以混合使用的。
- 比如,先在客戶端對請求做分類,區分哪些請求可以接受過期讀,而哪些請求完全不能接受過期讀;然后,對於不能接受過期讀的語句,再使用等 GTID 或等位點的方案。
- 但話說回來,過期讀在本質上是由一寫多讀導致的。
- 在實際應用中,可能會有別的不需要等待就可以水平擴展的數據庫方案,但這往往是用犧牲寫性能換來的,也就是需要在讀性能和寫性能中取權衡。