聊聊GitHub是怎麼做好 MySQL 高可用性的

2022-10-06 10:00:36
GitHub是怎麼做好 MySQL 高可用性的?下面本篇文章就來帶大家分享一下,希望對大家有所幫助。

程式設計師必備介面測試偵錯工具:

Github 使用 MySQL 資料庫作為所有非 git 事務的資料儲存。資料庫的可用性對 Github 的正常執行而言至關重要。無論是 Github 網站本身,還是 Github API,身份驗證服務等都需要存取資料庫。Github 執行了多個資料庫叢集用於支撐不同的服務於任務。資料庫的架構採用的是傳統的主從結構,叢集中一個節點(主庫)支援寫存取,其餘的節點(從庫)同步主庫的變更,支援讀服務。

主庫的可用性至關重要。一旦主庫宕機,叢集將不能夠支援資料寫入服務:任何需要儲存的資料都無法寫入到資料庫儲存。最終導致 Github 上任何變更,例如程式碼提交,提問,使用者建立,程式碼 review,建立倉庫等操作都無法完成。

為了保證業務的正常執行,我們自然需要在叢集中有一個可用的支援寫入的資料庫節點。同時,我們也必須能夠快速的發現可用的可寫入服務資料庫節點。

就是說,在異常情況下,假如主庫宕機的場景,我們必須確保新的主庫能夠立刻上線支援服務,同時保證叢集中其他節點能夠快速識別到新的主庫。故障檢測,主庫遷移以及叢集其他資料節點識別新主庫的總時間構成了服務中斷的總時間。

這篇文章說明了 GitHub 的 MySQL 高可用性和主庫服務發現解決方案,該解決方案使我們能夠可靠地執行跨資料中心的操作,能夠容忍資料中心的隔離,並縮短在出現故障時停機時間。

高可用性的實現

本篇文章描述的解決方案是在以前 Github 高可用方案上的改進版本。正如前面說到的一樣,MySQL 的高可用策略必須適應業務的變化。我們期望 MySQL 以及 GitHub 上其他的服務都有能夠應對變化的高可用解決方案。

當設計高可用以及服務發現系統方案的時候,從下面幾個問題出發,也許能夠幫助我們快速找到合適的解決方案:

  • 最大允許的服務中斷的時間是多少?
  • 服務中斷檢測的準確性怎麼樣?是否能夠允許服務中斷檢測誤報(會導致過早故障轉移)?
  • 故障轉移的可靠性怎麼樣?什麼情況會導致故障轉移失敗?
  • 這個方案能否在跨資料中心實現,以及如何實現的? 在不同的網路狀況下會怎麼樣,延遲高,或延遲低的情況會怎麼樣?
  • 這個解決方案能否承受整個資料中心(DC)的故障 或者網路隔離的情況?
  • 有什麼機制防止 HA 叢集腦裂情況(在一個整體的系統,聯絡著的兩個節點分裂為兩個獨立節點,這兩個節點爭搶共用資源寫入資料的情況)?
  • 能否容忍資料丟失?容忍丟失程度是多少?

為了說明上面的幾個問題,我們先來看一下我們之前的高可用方案,以及我們為什麼要改進它。

摒棄基於 VIP 和 DNS 的發現機制

在之前的方案中,應用了下面的技術方案:

  • 使用 orchestrator 作為故障檢測遷移方案。
  • 採用 VIP 和 DNS 方式作為主節點發現方案。

使用者端通過節點名稱,例如 mysql-writer-1.github.net,解析成主節點的虛擬 IP 地址 (VIP),從而找到主節點。

因此,正常情況下,使用者端可以通過對節點名稱的解析,連線到對應 IP 的主節點上。

考慮誇三個資料中心的拓撲結構的情況:

1.png

一旦主庫異常,必須將其中的一個資料副本伺服器更新為主庫伺服器。

orchestrator 會檢測異常,選出新的主庫,然後重新分配資料庫的名稱以及虛擬 IP (VIP)。使用者端本身不知道主庫的變更,使用者端有的資訊只是主庫的名稱,因此這個名稱必須能夠解析到新的主庫伺服器。考慮下面的問題:

VIP 需要協商:虛擬 IP 由資料庫本身所持有。 伺服器必須傳送 ARP 請求,才能夠佔有或釋放 VIP。 在新的資料庫分配新的 VIP 之前,舊的伺服器必須先釋放其佔有的 VIP。這個過程會產生一些異常問題:

  • 故障轉移的順序,首先是請求故障機器釋放 VIP,然後聯絡新的主庫機器分配 VIP。但是,如果故障機器本身不能存取,或者說拒絕釋放 VIP 呢? 考慮到機器故障的場景,故障機器不會立即響應或根本就不會響應釋放 VIP 的請求,整個過程有下面兩個問題:
    • 腦裂情況:如果有兩個主機持有相同的 VIP 的情況,不同的使用者端根據最短的網路鏈路將會連線到不同的主機上。
    • 整個 VIP 重新分配過程依賴兩個獨立伺服器的相互協調,而且設定過程是不可靠的。
  • 即使故障機器正常釋放 VIP,整個流程也是非常耗時的,因為切換過程還需要連線故障機器。
  • 即使 VIP 重新分配,使用者端已有的連線不會自動斷開舊的故障機器,從而使得整個系統產生腦裂的情況。

在我們實際設定 VIP 時,VIP 還受實際物理位置的約束。這主要取決於交換機或者路由器所在。因此,我們只能在同一本地伺服器上重新分配 VIP。特別是在某些情況下,我們無法將 VIP 分配給其他資料中心的伺服器,而必須進行 DNS 更改。

  • DNS 更改需要更長的時間傳播。使用者端快取 DNS 名稱會先設定時間。跨平臺故障轉移意味著需要更多的中斷時間:使用者端需要花費更多時間去識別新的主伺服器。

僅這些限制就足以推動我們尋找新的解決方案,但需要考慮的是:

  • 主伺服器使用 pt-heartbeat 服務去自注入存取心跳,目的是延遲測量和節流控制。該服務必須在新的主伺服器開始。如果可以,在更換主伺服器的同時會關閉舊的主伺服器這項服務。

  • 同樣地,Pseudo-GTID 是由伺服器自行管理的。它需要在新的主伺服器開始,最好在舊的主伺服器上停止。

  • 新的主伺服器將設定為可寫入。如果可以的話,舊的主伺服器將設定為 read_only(唯讀)。

這些額外的步驟是導致總停機時間的一個因素,並引入了它們自己的故障和摩擦。

解決方案是有效的,GitHub 已經成功地進行了 MySQL 故障轉移,但是我們希望 HA 在以下方面有所改進:

  • 與資料中心無關。
  • 容忍資料中心故障。
  • 刪除不可靠的共同作業工作流。
  • 減少總停機時間。
  • 儘可能的,有無失真的故障切換。

GitHub 的高可用解決方案:orchestrator ,Consul , GLB

新策略可以改進,解決或者優化上面提到的問題。現在高可用的組成如下:

2.png

新的結構移除了 VIP 和 DNS 。在引入更多的元件的同時,我們能夠解藕這些元件,並且簡化相關的任務,並易於利用可靠穩定的解決方案。詳情如下。

正常過程

正常情況,應用通過 GLB/HAProxy 連線到寫入節點。

應用感知不到 master 身份。之前,都是使用名稱。例如 cluster1 的 master 是 mysql-writer-1.github.net。現在的結構中,這個名稱被 anycast IP 取代。

通過 anycast,名稱被相同的 IP 取代,但流量由使用者端的位置來進行路由。特別的,當資料中心有 GLB 時,高可用負載均衡器部署在不同的盒子內。 通向 mysql-writer-1.github.net 的流量都被引流到原生的資料中心的 GLB 叢集。這樣所有的使用者端都由本地代理服務。

HAProxy 頂層使用 GLB。HAProxy 擁有 寫入池 :每個 MySQL 叢集都有一個。每個池都有一個後端服務:叢集 master 節點。資料中心的所有 GLB/HAProxy 盒子都有相同的池子,表示這些池子有相同的後端服務對應。所以如果應用期望寫 mysql-writer-1.github.net,不同關心連線哪個 GLB 服務。它會導向實際的 cluster1 master 節點。

就應用連線 GLB ,發現服務而言,不需要重新發現。GLB 負責全部流量導向正確的目的地。

GLB 是怎麼知道哪些服務是後端,以及如何告知 GLB 變化的呢?

Discovery via Consul

Consul 以服務發現解決方案而聞名,也提供 DNS 服務。 然而,在我們的解決方案中,我們將其用作高度可用的鍵值 (KV) 儲存。

在 Consul 的 KV 儲存中,我們寫入叢集主節點的身份。 對於每個叢集,都有一組 KV 條目指示叢集的主裝置 fqdn、埠、ipv4、ipv6。

每個 GLB/HAProxy 節點都執行 consul-template:一個監聽 Consul 資料變化的服務(在我們的例子中:叢集主資料的變化)。 consul-template 生成一個有效的組態檔,並且能夠在設定更改時重新載入 HAProxy。

因此,每個 GLB/HAProxy 機器都會觀察到 Consul 對 master 身份的更改,然後它會重新設定自己,將新的 master 設定為叢集後端池中的單個實體,並重新載入以反映這些更改。

在 GitHub,我們在每個資料中心都有一個 Consul 設定,並且每個設定都是高度可用的。 但是,這些設定彼此獨立。 它們不會在彼此之間複製,也不會共用任何資料。

Consul 如何獲知變化,資訊如何跨 DC 分發?

orchestrator/raft

我們執行一個 orchestrator/raft 設定: orchestrator 節點通過 raft 機制相互通訊。 每個資料中心有一個或兩個 orchestrator 節點。

orchestrator 負責故障檢測和 MySQL 故障切換,以及將 master 的變更傳達給 Consul 。 故障切換由單個 orchestrator/raft leader 節點操作,但是叢集現在擁有新 master 的訊息通過 raft 機制傳播到所有 orchestrator 節點。

orchestrator 節點收到 master 變更的訊息時,它們各自與本地 Consul 設定通訊:它們各自呼叫 KV 寫入。 具有多個 orchestrator 代理的 DC 將多次(相同)寫入 Consul

把流程組合起來

在 master 崩潰的情況下:

  • orchestrator 節點檢測故障.
  • orchestrator/raft leader 節點開始恢復,一個資料伺服器被更新為 master 。
  • orchestrator/raft 公告所有 raft 子叢集節點變更了 master 。
  • 每個 orchestrator/raft 成員收到一個 leader 節點 變更的通知。它們每個成員都把新的 master 更新到本地 Consul KV 儲存中。
  • 每個 GLB/HAProxy 都執行了 consul-template,該模版觀察 Consul KV 儲存中的更改,並重新設定和重新載入 HAProxy。
  • 使用者端流量被重定向到新的 master 。

每個元件都有明確的責任歸屬,整個設計既是解耦的,又是簡化的。orchestrator 不知道負載平衡器。 Consul 不需要知道訊息的來源。代理只關心 Consul。使用者端只關心代理。

此外:

  • 沒有更改要傳播的 DNS
  • 沒有 TTL。
  • 流沒有和故障的 master 合作,它很大程度被忽略了。

其他詳細資訊

為了進一步保護流量,我們還有以下內容:

  • HAProxy 設定了一個非常短的 hard-stop-after。當它重新載入寫入器池中的新後端伺服器時,它會自動終止與舊主伺服器的任何現有連線。
    • 使用 hard-stop-after,我們甚至不需要客戶的合作,這減輕了腦裂的情況。值得注意的是,這不是封閉的,並且在我們終止舊連線之前一段時間過去了。但是在某個時間點之後,我們會感到很舒服,並且不會期待任何令人討厭的驚喜。
  • 我們並不嚴格要求 Consul 隨時待命。事實上,我們只需要它在故障轉移時可用。如果 Consul 發生故障,GLB 將繼續使用最後的已知值進行操作,並且不會採取劇烈行動。
  • GLB 設定為驗證新提升的 master 的身份。與我們的 上下文感知 MySQL 池 類似,會在後端伺服器上進行檢查,以確認它確實是寫入器節點。如果我們碰巧在 Consul 中刪除了 master 的身份,沒問題;空條目被忽略。如果我們在 Consul 中錯誤地寫了一個非主伺服器的名字,沒有問題; GLB 將拒絕更新它並繼續以最後一個已知狀態執行。

我們將在以下部分進一步解決問題並追求 HA 目標。

Orchestrator/RAFT 故障檢測

orchestrator 使用 整體方法 來檢測故障,因此非常可靠。我們沒有觀察到誤報:我們沒有過早的故障轉移,因此不會遭受不必要的停機時間。

orchestrator/raft 進一步解決了完整的 DC 網路隔離(又名 DC 圍欄)的情況。 DC 網路隔離可能會導致混亂:該 DC 內的伺服器可以相互通訊。是它們與其他 DC 網路隔離,還是其他 DC 被網路隔離?

orchestrator/raft 設定中,raft 領導節點是執行故障轉移的節點。領導者是獲得組中大多數人(法定人數)支援的節點。我們的協調器節點部署是這樣的,沒有一個資料中心佔多數,任何 n-1 資料中心都可以。

在 DC 網路完全隔離的情況下,該 DC 中的 orchestrator 節點會與其他 DC 中的對等節點斷開連線。因此,孤立 DC 中的 orchestrator 節點不能成為 raft 叢集的領導者。如果任何這樣的節點恰好是領導者,它就會下臺。將從任何其他 DC 中分配新的領導者。該領導者將得到所有其他能夠相互通訊的 DC 的支援。

因此,發號施令的 orchestrator 節點將是網路隔離資料中心之外的節點。如果一個獨立的 DC 中有一個主控,orchestrator 將啟動故障轉移,用其中一個可用 DC 中的伺服器替換它。我們通過將決策委託給非隔離 DC 中的法定人數來緩解 DC 隔離。

更快的廣而告之

通過更快地公佈主要更改可以進一步減少總停機時間。如何實現這一點?

orchestrator 開始故障轉移時,它觀察可用於提升的伺服器佇列。理解複製規則並遵守提示和限制,它能夠對最佳操作過程做出有根據的決策。

它可能認識到,一個可用於促銷的伺服器也是一個理想的候選人,例如:

  • 沒有什麼可以阻止伺服器的升級 (使用者可能已經暗示這樣的伺服器是首選的升級) ,以及
  • 預計伺服器能夠將其所有兄弟伺服器作為副本。

在這種情況下, orchestrator 首先將伺服器設定為可寫的,然後立即宣傳伺服器的推廣 (在我們的例子裡是寫到 Consul KV 儲存中) ,即使非同步開始修復複製樹,這個操作通常需要幾秒鐘。

很可能在 GLB 伺服器完全重新載入之前,複製樹已經完好無失真,但是並不嚴格要求它。伺服器很好接收寫!

半同步複製

在 MySQL 的半同步複製中,主伺服器不會確認事務提交,直到已知更改已傳送到一個或多個副本。它提供了一種實現無失真故障轉移的方法:應用於主伺服器的任何更改要麼應用於主伺服器,要麼等待應用於其中一個副本。

一致性伴隨著成本:可用性的風險。如果沒有副本確認收到更改,主伺服器將阻塞並停止寫操作。幸運的是,有一個超時設定,超時後主伺服器可以恢復到非同步複製模式,從而使寫操作再次可用。

我們已經將超時設定為一個合理的低值: 500ms。將更改從主 DC 副本傳送到本地 DC 副本,通常也傳送到遠端 DC,這已經足夠了。通過這個超時,我們可以觀察到完美的半同步行為 (沒有退回到非同步複製) ,並且在確認失敗的情況下可以很輕鬆地使用非常短的阻塞週期。

我們在本地 DC 副本上啟用半同步,在主伺服器死亡的情況下,我們期望 (儘管不嚴格執行) 無失真故障轉移。完全直流故障的無失真故障轉移是昂貴的,我們並不期望它。

在嘗試半同步超時的同時,我們還觀察到了一個對我們有利的行為:在主要失敗的情況下,我們能夠影響理想候選物件的身份。通過在指定的伺服器上啟用半同步,並將它們標記為候選伺服器,我們能夠通過影響故障的結果來減少總停機時間。在我們的實驗中,我們觀察到我們通常最終會得到理想的候選物件,從而快速地廣而告之。

注入心跳

我們沒有在升級 / 降級的主機上管理 pt-heart 服務的啟動 / 關閉,而是選擇在任何時候在任何地方執行它。這需要進行一些修補,以便使 pt-heart 能夠適應伺服器來回更改 read_only(唯讀狀態) 或完全崩潰的情況。

在我們當前的設定中,pt-heart 服務在主伺服器和副本上執行。在主機上,它們生成心跳事件。在副本上,他們識別伺服器是 read_only(唯讀) 的,並定期重新檢查它們的狀態。一旦伺服器被提升為主伺服器,該伺服器上的 pt-heart 就會將伺服器標識為可寫的,並開始注入心跳事件。

orchestrator 所有權委託

我們進一步 orchestrator:

  • 注入 Pseudo-GTID ,
  • 將提升的 master 設定為可寫,清除其複製狀態,以及,
  • 如果可能,將舊主伺服器設定為 read_only

在所有的新 master 的基礎上,這減少了摩擦。一個剛剛被提升的 master 顯然應該是有生命力,並且可以被接受的,否則我們不會提升它。因此,讓 orchestrator 直接講更改應用於提升的 msater 是有意義的。

orchestrator 所有權委託

我們進一步 orchestrator:

  • Pseudo-GTID 注入,
  • 將提升的 master 設定為可寫,清除其複製狀態,以及,
  • 如果可能,將舊的 master 設定為 read_only

在所有的新 master 的基礎上,這減少了摩擦。一個剛剛被提升的 master 顯然應該是有活力,並且可以被接受的,否則我們不會提升它。因此,讓 orchestrator 將更改直接應用於提升的 msater 是有意義的。

限制和缺點

代理層使應用程式不知道主伺服器的身份,但它也掩蓋了應用程式的主伺服器的身份。所有主要看到的都是來自代理層的連線,我們會丟失關於連線的實際來源的資訊。

隨著分散式系統的發展,我們仍然面臨著未處理的場景。

值得注意的是,在一個資料中心隔離場景中,假設主伺服器位於隔離的 DC 中,該 DC 中的應用程式仍然能夠寫入主伺服器。一旦網路恢復,這可能導致狀態不一致。我們正在努力減輕這種分裂的大腦實施一個可靠的 STONITH 從內部非常孤立的 DC。和以前一樣,在摧毀初選之前還需要一段時間,而且可能會有一段短時間的腦分裂。避免大腦分裂的操作成本非常高。

還存在更多的情況:在故障轉移時停止領事服務;部分直流隔離;其他情況。我們知道這種性質的分散式系統不可能堵住所有的漏洞,所以我們把重點放在最重要的案例上。

結論

我們的協調器 / GLB/Consul 為我們提供了:

  • 可靠的故障檢測,
  • 與資料中心無關的故障轉移,
  • 通常無失真的故障轉移,
  • 資料中心網路隔離支援,
  • 緩解裂腦 (更多工作成果),
  • 不依賴合作,
  • 在大多數情況下,總中斷時間在 10 and 13 seconds 之間。
    • 在不常見的情況下,我們最多可以看到 20 seconds 的總停機時間,在極端情況下則可以看到最多 25 seconds 的停機時間。

結論

業務流程 / 代理 / 服務發現範例在分離的體系結構中使用眾所周知和受信任的元件,這使得部署、操作和觀察變得更加容易,並且每個元件都可以獨立向上或向下擴充套件。我們能不斷的測試設定並尋求改進。

原文地址:https://github.blog/2018-06-20-mysql-high-availability-at-github/

譯文地址:https://learnku.com/mysql/t/36820

【相關推薦:】

以上就是聊聊GitHub是怎麼做好 MySQL 高可用性的的詳細內容,更多請關注TW511.COM其它相關文章!