分散式服務高可用實現:複製

来源:https://www.cnblogs.com/Jcloud/archive/2023/08/02/17599763.html
-Advertisement-
Play Games

複製,即在不同的節點上保存相同的副本,提供數據冗餘。如果一些節點不可用,剩餘的節點仍然可以提供數據服務,這些節點可能部署在不同的地理位置,以此來改善系統性能 ...


1. 為什麼需要複製

我們可以考慮如下問題:

  1. 當數據量、讀取或寫入負載已經超過了當前伺服器的處理能力,如何實現負載均衡?

  2. 希望在單台伺服器出現故障時仍能繼續工作,這該如何實現?

  3. 當服務的用戶遍佈全球,並希望他們訪問服務時不會有較大的延遲,怎麼才能統一用戶的交互體驗?

這些問題其實都能通過“複製”來解決:複製,即在不同的節點上保存相同的副本,提供數據冗餘。如果一些節點不可用,剩餘的節點仍然可以提供數據服務,這些節點可能部署在不同的地理位置,以此來改善系統性能,針對以上三個問題的解決方案如下:

  1. 採用無共用架構(shared-nothing architecture),進行橫向擴展,將數據分散到多台伺服器上,進行有效的負載均衡,提高服務的伸縮性
  2. 部署多台伺服器,在一臺宕機時,其他伺服器能隨時接管,實現服務的高可用
  3. 在多地理位置上部署服務,使用戶能就近訪問,避免產生較大的延遲,統一用戶體驗

複製概述.png

2. 單主複製

單主節點複製是工作中最常見的複製解決方案。存儲了資料庫拷貝的每個節點被稱為副本(replica),每次向資料庫的寫入操作都需要傳播到所有副本上,否則副本數據就會不一致,它的工作原理如下:

  • 其中一個副本被指定為領導者,也稱為主庫,當客戶端要向資料庫寫入時,它必須將該請求發送給領導者
  • 其他副本被稱為追隨者,也被稱為從庫只讀副本,每當領導者將數據寫入本地存儲時,它會將數據變更以複製日誌變更流的形式推送給所有的追隨者,並且追隨者按照與領導者相同的處理順序來進行寫入

2.1 節點間的數據同步

數據的同步分同步複製非同步複製,同步複製的好處是從庫能夠保證與主庫有一致的數據,當主庫失效時,這些數據能夠在從庫上找到,但是它的缺點也很明顯:主庫需要等待從庫的數據同步結果,如果同步從庫沒有響應,主庫就無法再處理新的寫入操作,而是進入阻塞狀態。

讀多寫少的場景下,我們通常會增加從節點的數量來對讀請求進行負載均衡,但是如果此時所有從庫都是同步複製是不實際的且不可靠的,因為單個節點的故障或網路中斷都會影響數據的寫入。

事實上資料庫啟用同步複製時,通常表示有一個從庫是同步複製,其他從庫是非同步複製,當同步從庫失效時,非同步複製的副本會改為同步複製,這保證了至少有兩個節點擁有最新的數據副本,這種配置也被成為半同步

而通常情況下,基於領導者的複製都配置為完全非同步。如下圖所示,用戶1234修改picture_url信息時,從主庫同步到從庫是存在延遲的。

非同步複製.png

這意味著如果此時主庫失效而尚未複製給從庫的數據會丟失,導致已經向客戶端請求確認成功也不能保證寫入是持久的,而且如果在主節點寫入數據後,立即向Follower 2讀取數據,則會讀取到舊數據,給用戶的感覺就像是剛纔的寫入丟失了一樣,這對應了讀己之寫一致性問題,我們在後文會做具體解釋。

但是實際生產情況下都基於非同步複製,說明強一致性並不是必要的保證,而對保證系統吞吐量的需求更高。因為在這種機制下,即使從庫已經遠遠落後,主庫也不必等待從庫寫入完成就可以返回數據寫入成功。之後從庫會慢慢趕上並與主庫一致,這種弱一致性的保證被稱為最終一致性

2.2 複製延遲問題

從上一小節中,我們知道了非同步複製在寫入主庫到複製到從庫存在延遲,因此會產生一系列的問題,在這裡我們對這些存在的問題進行更具體的解釋。

  • 寫入完成後主節點失效,但從節點未完成數據同步

主節點失效,需要進行故障轉移,將一個從庫提升為主庫,主庫的最佳人選通常是擁有最新數據副本的從庫(zookeeper的事務ID比較過程遵從的這個原理),讓新主庫來繼續為客戶端服務,其他從庫從新的主庫節點進行數據同步。

如果此時新的主節點在舊的主節點失效前還未完成數據同步,那麼通常的做法是將原主節點未完成複製的數據丟棄,此時就會發生數據丟失的問題。

而且在舊的主庫恢復時,需要讓它意識到新主庫的存在,並使自己成為一個從庫。如果當集群中出現多個節點認為自己是主節點時,即"腦裂"現象,是非常危險的:因為多個主節點都可以進行寫操作,卻沒有衝突解決機制,數據就可能被破壞。

zookeeper出現腦裂時通過判斷epoch的大小(故障轉移完成新的一輪選舉之後它的epoch會遞增)來使從節點拒絕舊主節點的請求,保證數據不被破壞。


  • 寫後讀一致性(讀己之寫一致性)

寫後讀一致性.png

如上圖所示,如果用戶在寫入後馬上請求查看數據,則新數據可能尚未到達只讀從庫,看起來好像剛提交的數據丟失了,這種情況可以通過以下方式來解決

  • 對於用戶可能修改過的內容,總是從主庫讀取,這需要有辦法在不通過查詢的方式來知道用戶是否修改了某些數據。比如,社交網路的個人信息通常由個人來修改,因此可以定義總是從主庫來讀取自己的檔案信息,讀取其他人的信息則在從庫獲取
  • 如果應用中的大部分內容都能被用戶修改,那麼大部分查詢都從主庫讀取的話,讀伸縮性就沒有效果了。在這種情況下可以通過記錄上次更新的時間,比如在更新後的一分鐘內從主庫查詢,之後在從庫讀取,以此來保證讀伸縮性
  • 客戶端記錄最近一次的寫入時間戳,系統需要確保從庫在處理該用戶的讀請求時,該時間戳的變更已經在本從庫中記錄了,如果查詢的當前從庫不存在該記錄,那麼需要再從其他從庫讀取,或者等待從庫同步數據

  • 單調讀

單調讀.png

如上圖所示,用戶1234寫入了一條評論,用戶2345在讀取其他用戶添加的評論時,第一次請求到了Follower1,這時從庫已經完成了數據同步,那麼能讀取到該評論。但是第二次請求到了Follower2,而Follower2並沒有完成數據同步,導致看不到之前讀取到的評論,出現"時間倒流"現象。

避免這種現象需要保證單調讀,即當用戶讀取到較新的數據時,他不會再讀取到更舊的數據。實現單調讀的方式是使同一個用戶的讀請求都請求到同一個副本節點,我們可以根據ID的散列來分配副本而不是隨機分配。

2.3 新從庫的數據同步

通常為了增強系統的讀伸縮性,會添加新的從庫。但新從庫在與主庫做數據同步時,簡單地將數據文件複製到另一個節點通常是不夠的,因為數據總是在不斷的變化,當前的數據文件不能包含全量數據,所以一般情況下的流程如下:

  1. 獲取某個時刻的主庫一致性快照,並將該快照複製到新的從庫節點
  2. 從庫連接到主庫,並拉取數據快照之後發生的數據變更,這就要求快照與主庫複製日誌有精確的位置關聯,Mysql是通過binlog coordinates二進位日誌坐標來關聯的
  3. 從庫處理完快照之後的數據變更,那麼就說它趕上了主庫,現在它就可以及時處理主庫的數據變化了

如果發生從庫失效,在從庫重新啟動後會執行以上2,3步驟,通過日誌可以知道發生故障之前處理的最後一個事務,通過該記錄請求從庫斷開期間的所有數據變更,慢慢地追趕主庫。

3. 多主複製

基於單主節點的複製,每個寫請求都要經過主節點所在的數據中心,那麼隨著寫入請求的增加,單主節點伸縮性差的局限性就會顯現出來,而且在世界各地的用戶都需要請求到該主節點才能進行寫入,可能存在延時較長的問題。為瞭解決這些問題,在單主節點架構下進行延伸,自然是多主節點複製,在這種情況下,每個主節點又是其他主節點的從庫。

通常情況下,增加單主節點的伸縮性不會使用多主複製,而是通過數據分區來解決。因為前者導致的複雜性已經超過了它能帶來的好處,不過在某些情況下,也是可以採用多主複製的。

多數據中心的多主複製架構如下圖所示:

多主複製.png

資料庫的副本分散在多個數據中心,在每個數據中心都有主庫,在每個數據中心內都是主從複製,每個數據中心的寫請求都會在本地數據中心處理然後同步到其他數據中心的主節點,這樣數據中心間的網路延遲對用戶來說就變成了透明的,這意味著性能可能會更好,對網路問題的容忍度更高;多數據中心部署在不同的地理位置上,對用戶來說體驗更好;如果本地數據中心發生故障,能夠將請求轉移到其他數據中心,等本地數據中心恢復並複製趕上進度後,能繼續提供服務。

3.1 多主複製的應用場景

  • 斷網後仍繼續工作的應用程式

如果你使用的手機和電腦是同一個生態的話,那麼一般情況下,備忘錄內容的修改能在設備之間進行同步。從架構的角度來看,每個設備都相當於是一個數據中心,每個數據中心都能進行寫入,它符合多主複製模型。數據中心間的網路是極度不可靠的,當手機離線,在電腦端對備忘錄進行修改後,那麼當手機再接入互聯網,需要完成設備間的數據同步,這就是非同步多主複製的過程。


  • 線上協同文檔

當有用戶對文檔進行編輯時,所做的更改將立即被非同步複製到伺服器和其他任何正在使用該文檔的用戶,每個用戶操作的文檔都相當於是一個數據中心,這種情況與我們上文所述的在離線設備上對備忘錄進行修改有相似之處。不過,在這種情況下,為了加速協同和提高文檔的使用體驗,需要解決同時編輯產生的寫入衝突問題。

3.2 解決寫入衝突

雖然我們在上文中提到了多主複製能帶來諸多好處(多主帶來的伸縮性、更好的容錯機制和減少地理位置造成的延時),但是相伴的配置複雜寫入衝突問題也是需要我們直面的。

如下圖所示,用戶1修改標題為B,用戶2修改標題為C,那麼此時就會發生寫入衝突,我們很難說得清楚將誰的結果指定為最終修改結果是合適的,但是我們還是不得不將多主資料庫的值收斂至一致的狀態。

多主複製衝突.png

最後寫入勝利(LWW,last write wins)是比較常用的方法,我們可以為每個請求增加時間戳或者唯一的ID,挑選其中較大的值作為最終結果,並將其他的值丟棄,不過這種情況容易造成數據丟失,比如在分散式服務中存在的不可靠的時鐘問題,可能後寫入的值反而攜帶的時間戳更靠前,那麼這種情況下就會將我們預期被寫入的結果丟棄。

另一種方法是可以為每個主庫分配一個ID編號,具有更高的ID編號的主庫具有更高的優先順序,但是這也會產生數據丟失問題。

如果不想發生數據丟失,可以以某種組合的方式將這些值組合在一起。以上圖中對標題的修改為例,可以將標題修改結果拼接成 B/C,不過這種情況需要用戶對結果進行修正。和該方式類似的,還可以考慮將所有對數據修改的衝突都顯示的記錄下來,之後提示用戶進行修改。

版本向量也是一種解決衝突的方式。以緩存為例,我們為每個鍵維護一個版本號,每次寫入時先進行讀取,並且必須將之前讀取的所有值合併在一起,其中刪除的值會被標記(墓碑),這樣就能夠避免在合併完成後仍然出現曾刪掉的值。在寫入完成後版本號遞增,將新版本號與寫入的值一起存儲。在多個副本併發接受寫入時,每個副本也需要維護版本號,每個副本在處理寫入時增加自己的版本號。所有副本的版本號集合稱為版本向量,版本向量會隨著讀取和寫入在客戶端和服務端之前來回傳遞,並且允許資料庫區分覆蓋寫入和併發寫入。版本向量能夠確保從一個副本讀取並隨後寫回到另一個副本是安全的

不過,雖然我們介紹了這麼多解決衝突的方式,但是實際上避免衝突是最好的方式。比如我們可以確保特定記錄的所有寫入都通過同一個主庫,那麼就不會發生衝突了。

關於併發的理解:如果是在單體服務中,我們可以通過時間戳來判斷兩個事件同時發生;如果是在分散式系統中,因為分散式系統存在不可靠的時鐘問題,所以在實際的系統中很難判斷兩個事件是否是同時發生,所以併發在字面時間上的重疊並不重要。實際上,併發強調的是兩個事件是否能意識到對方的存在,如果都意識不到對方的存在,即兩個事件都不在另一個之前發生,那麼這兩個事件是併發的,那麼它們存在需要被解決的併發寫入衝突。

4. 無主複製

無主複製與單主、多主複製採用不同的複製機制:它沒有主庫和從庫的職責差異,而是放棄了主庫的概念,每一個資料庫節點都可以處理寫入請求,因此它適用於高可用、低延時、且能夠容忍偶爾讀到陳舊值的應用場景。

這種複製模式還有一個好處是不存在故障轉移,當某個節點宕機時,應用會將該請求轉發到其他正常工作的節點。等到宕機節點重新連接之後,該節點可以通過以下兩種方式趕上錯過的寫入:

  • 讀修複:適用於讀頻繁的值,客戶端並行獲取多個節點時,如果它檢測到陳舊的值,那麼將讀取到的新值把陳舊的值覆蓋掉
  • 反熵:開啟後臺進程,該進程不斷查找副本之間的數據差異,並將任何缺少的數據從一個副本複製到另一個副本

無主複製的每個資料庫節點都能處理讀寫請求,但是並不是在某單個節點寫入完成後就被認定為寫入成功或在單個節點讀取就認為該值是讀取結果。它的讀寫遵循法定人數原則,與zookeeper處理寫入請求使用的容錯共識演算法類似。

一般地說,如果有n個副本,每個寫入必須由 w 個節點確認才能被認為是成功的,並且每個讀取必須查詢 r 個節點。只要w + r > n,我們可以預期在讀取時獲得最新的值,因為在 r 個讀取中至少有一個節點是最新的,遵循這些 r 值和 w 值的讀寫被稱為法定人數讀寫。常見的配置是將n(節點數)配置成奇數,並設置w = r = (n + 1) / 2向上取整,這樣保證了寫入和讀取的節點集合必然有重疊,所以讀取的節點中必然至少有一個節點具有最新的值。

如下圖所示,用戶1234會將寫入請求發送到所有的3個資料庫副本,並且在其中兩個副本返回成功時即認為寫入成功,而忽略了宕機副本錯過寫入的事實;用戶2345在讀取數據時,也會將請求發送到所有副本,並將其中最新的值看作讀取的結果。

無主複製的讀寫.png

每種複製的模式都有優點和缺點,單主複製是比較流行的,它容易理解而且無需處理衝突問題(寫入只有主節點處理)。不過在節點故障或者網路出現較大的延時時,多主複製和無主複製可以更加健壯,但是它們只能提供較弱的一致性保證。


巨人的肩膀

作者:京東物流 王奕龍

來源:京東雲開發者社區 自猿其說Tech


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

-Advertisement-
Play Games
更多相關文章
  • # Unity BuildPlayerProcessor Unity BuildPlayerProcessor是Unity引擎中的一個非常有用的功能,它可以讓開發者在構建項目時自動執行一些操作。這個功能可以幫助開發者提高工作效率,減少手動操作的時間和錯誤率。在本文中,我們將介紹Unity Build ...
  • module_init是linux內核提供的一個巨集, 可以用來在編寫內核模塊時註冊一個初始化函數, 當模塊被載入的時候, 內核負責執行這個初始化函數. 在編寫設備驅動程式時, 使用這個巨集看起來理所應當, 沒什麼特別的, 但畢竟我還是一個有點追求的程式員嘛:P, 這篇文章是我學習module_init... ...
  • 第一種 每打開一次終端都輸入一次 source /etc/profile,這樣就可以載入配置文件,環境變數自然就有了 第二種 輸入vim ./bashrc,在底部添加配置在/etc/profile里的配置,這樣就可以無需使用source命令載入了 也可以在./bashrc底部添加 source /e... ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230802124620904-1514854566.png) # 1. 示例數據 ## 1.1. student ```sql insert into student val ...
  • 當企業的業務發展到一定的階段時,在系統中引入[監控告警系統](https://www.dtstack.com/dtengine/easymr?src=szsm)來對系統/業務進行監控是必備的流程。沒有監控或者沒有一個好的監控,會導致開發人員無法快速判斷系統是否健康;告警的實質則是“把人當服務用”,用 ...
  • 開發者的技術能力良莠不齊,DBA對資料庫知識的局限性導致爛SQL無處不在,而且隨著資料庫的不斷變更或演進,一些好的SQL也可能逐步變成需要優化的爛SQL, 我們要時刻不斷地找尋它們的蹤跡。 ...
  • ### 分享技術,用心生活 >背景:系統中有一個統計頁面載入特別慢,前端設置的40s超時時間都載入不出來數據,因為是個統計頁面,基本上一猜就知道是mysql的語句有問題,遺留了很久沒有解決,正好趁不忙的時候,下定決心一定把它給搞定! ## 1. 分析原因 (mysql5.7) 執行一下問題sql,可 ...
  • MySQL8_SQL語法 SQL 全稱 Structured Query Language,結構化查詢語言。操作關係型資料庫的編程語言,定義了一套操作關係型資料庫統一標準 。 一、SQL通用語法 在學習具體的SQL語句之前,先來瞭解一下SQL語言的同於語法。 1). SQL語句可以單行或多行書寫,以 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...