大家好我是鹹魚了大半年的一灰灰,終於放暑假了,把小孩送回老家,作為鹹魚的我也可以翻翻身了,接下來將趁著暑假的這段時間,將準備一個全新的分散式專欄,為了給大家提供更好的閱讀體驗,可以再我的個人站點上檢視系列的專欄內容:
天天說分散式分散式,那麼我們是否知道什麼是分散式,分散式會遇到什麼問題,有哪些理論支撐,有哪些經典的應對方案,業界是如何設計並保證分散式系統的高可用呢?
這一節將從一些經典的開源系統架構設計出發,來看一下,如何設計一個高質量的分散式系統;
而一般的設計出發點,無外乎
給現有的服務搭建一個備用的服務,兩者功能完全一致,區別在於平時只有主應用對外提供服務能力;而備應用則只需要保證與主應用能力一致,隨時待機即可,並不用對外提供服務;當主應用出現故障之後,將備應用切換為主應用,原主應用下線;迅速的主備切換可以有效的縮短故障時間
基於上面的描述,主備架構特點比較清晰
其次就是這個架構模型最需要考慮的則是如何實現主備切換?
主從一般又叫做讀寫分離,主提供讀寫能力,而從則只提供讀能力
鑑於當下的網際網路應用,絕大多數都是讀多寫少的場景;讀更容易成為效能瓶頸,所以採用讀寫分離,可以有效的提高整個叢集的響應能力
主從架構可以區分為:一主多從 + 一主一從再多從,以mysql的主從架構模型為例進行說明
主從模式的主要特點在於
關鍵問題則在於
一主多從面臨單主節點的瓶頸問題,那就考慮多主多從的策略,同樣是主負責提供讀寫,從提供讀;
但是這裡有一個核心點在於多主之間的資料同步,如何保證資料的一致性是這個架構模型的重點
如MySql的雙主雙從可以說是一個典型的應用場景,在實際使用的時候除了上面的一致性之外,還需要考慮主鍵id衝突的問題
無主節點,叢集中所有的應用職能對等,沒有主次之分(當下絕大多數的業務服務都屬於這種),一個請求可以被叢集中任意一個服務響應;
這種也可以叫做去中心化的設計模式,如redis的叢集模式,eureka註冊中心,以可用性為首要目標
對於普通叢集模式而言,重點需要考慮的點在於
這個分片模型的描述可能並不準確,大家看的時候重點理解一下這個思想
前面幾個的架構中,採用的是資料冗餘的方式,即所有的範例都有一個全量的資料,而這裡的資料分片,則從資料拆分的思路來處理,將全量的資料,通過一定規則拆分到多個系統中,每個系統包含部分的資料,減小單個節點的壓力,主要用於解決資料量大的場景
比如redis的叢集方式,通過hash槽的方式進行分割區
如es的索引分片儲存
這一節主要從架構設計層面對當前的分散式系統所採用的方案進行了一個簡單的歸類與小結,並不一定全面,歡迎各位大佬留言指正
基於冗餘的思想:
基於拆分的思想:
對於拆分這一塊,我們常說的分庫分表也體現的是這一思想
這一小節將介紹分散式系統中的經典理論,如廣為流程的CAP/BASE理論,一致性理論基礎paxios,raft,資訊交換的Gossip協定,兩階段、三階段等
本節主要內容參考自
CAP 定理指出,分散式系統 不可能 同時提供下面三個要求:
通常來講P很難不保證,當服務部署到多臺範例上時,節點異常、網路故障屬於常態,根據不同業務場景進行選擇
對於服務有限的應用而言,首選AP,保證高可用,即使部分機器異常,也不會導致整個服務不可用;如絕大多數的前臺應用都是這種
對於資料一致性要求高的場景,如涉及到錢的支付結算,CP可能更重要了
對於CAP的三種組合說明如下
選擇 | 說明 |
---|---|
CA | 放棄分割區容錯性,加強一致性和可用性,其實就是傳統的單機場景 |
AP | 放棄一致性(這裡說的一致性是強一致性),追求分割區容錯性和可用性,這是很多分散式系統設計時的選擇,例如很多NoSQL系統就是如此 |
CP | 放棄可用性,追求一致性和分割區容錯性,基本不會選擇,網路問題會直接讓整個系統不可用 |
base理論作為cap的延伸,其核心特點在於放棄強一致性,追求最終一致性
基於上面的描述,可以看到BASE理論適用於大型高可用可延伸的分散式系統
注意其不同於ACID的強一致性模型,而是通過犧牲強一致性 來獲得可用性,並允許資料在一段時間內是不一致的,但最終達到一致狀態
這個真沒聽說過,以下內容來自:
定理(PAC)的第一部分與CAP定理相同,ELC是擴充套件。整個論點假設我們通過複製來保持高可用性。因此,當失敗時,CAP定理佔上風。但如果沒有,我們仍然必須考慮複製系統的一致性和延遲之間的權衡。
Paxos演演算法解決的問題是分散式共識性問題,即一個分散式系統中的各個程序如何就某個值(決議)通過共識達成一致
基於上面這個描述,可以看出它非常適用於選舉;其工作流程
角色劃分:
推薦有興趣的小夥伴,檢視
為了解決paxos的複雜性,raft演演算法提供了一套更易理解的演演算法基礎,其核心流程在於:
leader接受請求,並轉發給follow,當大部分follow響應之後,leader通知所有的follow提交請求、同時自己也提交請求並告訴呼叫方ok
角色劃分:
ZAB(Zookeeper Atomic Broadcast) 協定是為分散式協調服務ZooKeeper專門設計的一種支援崩潰恢復的一致性協定,基於該協定,ZooKeeper 實現了一種 主從模式的系統架構來保持叢集中各個副本之間的資料一致性。
主要用於zk的資料一致性場景,其核心思想是Leader再接受到事務請求之後,通過給Follower,當半數以上的Follower返回ACK之後,Leader提交提案,並向Follower傳送commit資訊
角色劃分
two-phase commit protocol,兩階段提交協定,主要是為了解決強一致性,中心化的強一致性協定
角色劃分
執行流程
協調節點接收請求,然後向參與者節點提交 precommit
,當所有的參與者都回復ok之後,協調節點再給所有的參與者節點提交commit
,所有的都返回ok之後,才表明這個資料確認提交
當第一個階段,有一個參與者失敗,則所有的參與者節點都回滾
特點
優點在於實現簡單
缺點也很明顯
在兩階段的基礎上進行擴充套件,將第一階段劃分兩部,cancommit + precommit,第三階段則為 docommit
第一階段 cancommit
該階段協調者會去詢問各個參與者是否能夠正常執行事務,參與者根據自身情況回覆一個預估值,相對於真正的執行事務,這個過程是輕量的
第二階段 precommit
本階段協調者會根據第一階段的詢盤結果採取相應操作,若所有參與者都返回ok,則協調者向參與者提交事務執行(單不提交)通知;否則通知參與者abort回滾
第三階段 docommit
如果第二階段事務未中斷,那麼本階段協調者將會依據事務執行返回的結果來決定提交或回滾事務,若所有參與者正常執行,則提交;否則協調者+參與者回滾
在本階段如果因為協調者或網路問題,導致參與者遲遲不能收到來自協調者的 commit 或 rollback 請求,那麼參與者將不會如兩階段提交中那樣陷入阻塞,而是等待超時後繼續 commit,相對於兩階段提交雖然降低了同步阻塞,但仍然無法完全避免資料的不一致
特點
Gossip 協定,顧名思義,就像流言蜚語一樣,利用一種隨機、帶有傳染性的方式,將資訊傳播到整個網路中,並在一定時間內,使得系統內的所有節點資料一致。Gossip 協定通過上面的特性,可以保證系統能在極端情況下(比如叢集中只有一個節點在執行)也能執行
主要用在分散式資料庫系統中各個副本節點同步資料之用,這種場景的一個最大特點就是組成的網路的節點都是對等節點,是非結構化網路
工作流程
特點
缺點
本節主要介紹的是分散式系統設計中的一些常見的理論基石,如分散式中如何保障一致性,如何對一個提案達成共識
這一節將主要介紹下分散式系統中的經典的演演算法,比如常用於分割區的一致性hash演演算法,適用於一致性的Quorum NWR演演算法,PBFT拜占庭容錯演演算法,區塊鏈中大量使用的工作量證明PoW演演算法等
一致性hash演演算法,主要應用於資料分片場景下,有效降低服務的新增、刪除對資料複製的影響
通過對資料項的鍵進行雜湊處理對映其在環上的位置,然後順時針遍歷環以查詢位置大於該項位置的第一個節點,將每個由鍵標識的資料分配給hash環中的一個節點
一致雜湊的主要優點是增量穩定性; 節點新增刪除,對整個叢集而言,僅影響其直接鄰居,其他節點不受影響。
注意:
用來保證資料冗餘和最終一致性的投票演演算法,其主要數學思想來源於鴿巢原理
Quorum NWR演演算法要求每個資料拷貝物件 都可以投1票,而每一個操作的執行則需要獲取最小的讀票數,寫票數;通常來講寫票數W一般需要超過N/2,即我們通常說的得到半數以上的票才表示資料寫入成功
事實上當W=N、R=1時,即所謂的WARO(Write All Read One)。就是CAP理論中CP模型的場景
拜占庭演演算法主要針對的是分散式場景下無響應,或者響應不可信的情況下的容錯問題,其核心分三段流程,如下
假設叢集節點數為 N,f個故障節點(無響應)和f個問題節點(無響應或錯誤響應),f+1個正常節點,即 3f+1=n
相比 Raft 演演算法完全不適應有人作惡的場景,PBFT 演演算法能容忍 (n 1)/3 個惡意節點 (也可以是故障節點)。另外,相比 PoW 演演算法,PBFT 的優點是不消耗算 力。PBFT 演演算法是O(n ^ 2) 的訊息複雜度的演演算法,所以以及隨著訊息數 的增加,網路時延對系統執行的影響也會越大,這些都限制了執行 PBFT 演演算法的分散式系統 的規模,也決定了 PBFT 演演算法適用於中小型分散式系統
工作量證明 (Proof Of Work,簡稱 PoW),同樣應用於分散式下的一致性場景,區別於前面的raft, pbft, paxos採用投票機制達成共識方案,pow採用工作量證明
使用者端需要做一定難度的工作才能得出一個結果,驗證方卻很容易通過結果來檢查出使用者端是不是做了相應的工作,通過消耗一定工作浪,增加訊息偽造的成本,PoW以區塊鏈中廣泛應用而廣為人知,下面以區塊鏈來簡單說一下PoW的演演算法應用場景
以BTC的轉賬為例,A轉n個btc給B,如何保證不會同時將這n個幣轉給C?
PoW的演演算法,主要應用在上面的區塊提交驗證,通過hash值計算來消耗算力,以此證明礦工確實有付出,得到多數認可的可以達成共識
本節主要介紹了下當前分散式下常見的演演算法,
這一節的內容相對前面幾個而言,並不太容易進行清晰的分類;主要包含一些高質量的分散式系統的實踐中,值得推薦的設計思想、技術細節
Command Query Responsibility Segregation 即我們通俗理解的讀寫分離,其核心思想在於將兩類不同操作進行分離,在獨立的服務中實現
用途在於將領域模型與查詢功能進行分離,讓一些複雜的查詢擺脫領域模型的限制,以更為簡單的 DTO 形式展現查詢結果。同時分離了不同的資料儲存結構,讓開發者按照查詢的功能與要求更加自由的選擇資料儲存引擎
複製負載平衡服務(Replication Load Balancing Service, RLBS),可以簡單理解為我們常說的負載均衡,多個相同的服務範例構建一個叢集,每個服務都可以響應請求,負載均衡器負責請求的分發到不同的範例上,常見的負載演演算法
演演算法 | 說明 | 特點 |
---|---|---|
輪詢 | 請求按照順序依次分發給對應的伺服器 | 優點簡單,缺點在於未考慮不同伺服器的實際效能情況 |
加權輪詢 | 權重高的被分發更多的請求 | 優點:充分利用機器的效能 |
最少連線數 | 找連線數最少的伺服器進行請求分發,若所有伺服器相同的連線數,則找第一個選擇的 | 目的是讓優先讓空閒的機器響應請求 |
少連線數慢啟動時間 | 剛啟動的伺服器,在一個時間段內,連線數是有限制且緩慢增加 | 避免剛上線導致大量的請求分發過來而超載 |
加權最少連線 | 平衡服務效能 + 最少連線數 | |
基於代理的自適應負載均衡 | 載主機包含一個自適用邏輯用來定時監測伺服器狀態和該伺服器的權重 | |
源地址雜湊法 | 獲取使用者端的IP地址,通過雜湊函對映到對應的伺服器 | 相同的來源請求都轉發到相同的伺服器上 |
隨機 | 隨機演演算法選擇一臺伺服器 | |
固定權重 | 最高權重只有在其他伺服器的權重值都很低時才使用。然而,如果最高權重的伺服器下降,則下一個最高優先順序的伺服器將為使用者端服務 | 每個真實伺服器的權重需要基於伺服器優先順序來設定 |
加權響應 | 伺服器響應越小其權重越高,通常是基於心跳來判斷機器的快慢 | 心跳的響應並不一定非常準確反應服務情況 |
在分散式環境裡中,如何判斷一個服務是否存活,當下最常見的方案就是心跳
比如raft演演算法中的leader向所有的follow傳送心跳,表示自己還健在,避免發生新的選舉;
比如redis的哨兵機制,也是通過ping/pong的心跳來判斷節點是否下線,是否需要選新的主節點;
再比如我們日常的業務應用得健康監測,判斷服務是否正常
租約就像一個鎖,但即使使用者端離開,它也能工作。使用者端請求有限期限的租約,之後租約到期。如果使用者端想要延長租約,它可以在租約到期之前續訂租約。
租約主要是了避免一個資源長久被某個物件持有,一旦對方掛了且不會主動釋放的問題;在實際的場景中,有兩個典型的應用
case1 分散式鎖
業務獲取的分散式鎖一般都有一個有效期,若有效期內沒有主動釋放,這個鎖依然會被釋放掉,其他業務也可以搶佔到這把鎖;因此對於持有鎖的業務方而言,若發現在到期前,業務邏輯還沒有處理完,則可以續約,讓自己繼續持有這把鎖
典型的實現方式是redisson的看門狗機制
case2 raft演演算法的任期
在raft演演算法中,每個leader都有一個任期,任期過後會重新選舉,而Leader為了避免重新選舉,一般會定時傳送心跳到Follower進行續約
這個比較好理解,上面很多系統都採用了這種方案,特別是在共識演演算法中,由領導者負責代表整個叢集做出決策,並將決策傳播到所有其他伺服器
領導者選舉在伺服器啟動時進行。每個伺服器在啟動時都會啟動領導者選舉,並嘗試選舉領導者。除非選出領導者,否則系統不接受任何使用者端請求
在領導者-追隨者模式中,當領導者失敗時,不可能確定領導者已停止工作,如慢速網路或網路分割區可能會觸發新的領導者選舉,即使前一個領導者仍在執行並認為它仍然是活動的領導者
Fencint是指在以前處於活動狀態的領導者周圍設定圍欄,使其無法存取叢集資源,從而停止為任何讀/寫請求提供服務
法定人數,常見於選舉、共識演演算法中,當超過Quorum的節點數確認之後,才表示這個提案通過(資料更新成功),通常這個法定人數為 = 半數節點 + 1
高水位線,跟蹤Leader(領導者)上的最後一個紀錄檔條目,且該條目已成功複製到>quorum(法定人數)的Follow(跟誰者),即表示這個紀錄檔被整個叢集接受
紀錄檔中此條目的索引稱為高水位線索引。領導者僅公開到高水位線索引的資料。
如Kafka:為了處理非可重複讀取並確保資料一致性,Kafka broker會跟蹤高水位線,這是特定分割區的最大偏移量。使用者只能看到高水位線之前的訊息。
Phi Accrual Failure Detection,使用歷史檢測訊號資訊使閾值自適應
通用的應計故障檢測器不會判斷伺服器是否處於活動狀態,而是輸出有關伺服器的可疑級別。
如Cassandra(Facebook開源的分散式NoSql資料庫)使用 Phi 應計故障檢測器演演算法來確定群集中節點的狀態
預寫紀錄檔記錄是解決作業系統中檔案系統不一致的問題的高階解決方案,當我們提交寫到作業系統的檔案快取,此時業務會認為已經提交成功;但是在檔案快取與實際寫盤之間會有一個時間差,若此時機器宕機,會導致快取中的資料丟失,從而導致完整性缺失
為了解決這個問題,如mysql,es等都採用了預寫紀錄檔的機制來避免這個問題
MySql:
ElasticSearch:
將紀錄檔拆分為多個較小的檔案,而不是單個大檔案,以便於操作。
單個紀錄檔檔案在啟動時讀取時可能會增長併成為效能瓶頸。較舊的紀錄檔會定期清理,並且很難對單個大檔案執行清理操作。
單個紀錄檔拆分為多個段。紀錄檔檔案在指定的大小限制後捲動。使用紀錄檔分段,需要有一種將邏輯紀錄檔偏移量(或紀錄檔序列號)對映到紀錄檔段檔案的簡單方法。
這個其實也非常常見,比如我們實際業務應用設定的log,一般都是按天、固定大小進行拆分,並不會把所有的紀錄檔都放在一個紀錄檔檔案中
再比如es的分段儲存,一個段就是一個小的儲存檔案
在分散式系統中,在元件之間行動資料時,從節點獲取的資料可能會損壞。
計算校驗和並將其與資料一起儲存。
要計算校驗和,請使用 MD5、SHA-1、SHA-256 或 SHA-512 等加密雜湊函數。雜湊函數獲取輸入資料並生成固定長度的字串(包含字母和數位);此字串稱為校驗和。
當系統儲存某些資料時,它會計算資料的校驗和,並將校驗和與資料一起儲存。當用戶端檢索資料時,它會驗證從伺服器接收的資料是否與儲存的校驗和匹配。如果沒有,則使用者端可以選擇從另一個副本檢索該資料。
HDFS和Chubby將每個檔案的校驗和與資料一起儲存。
這一節很多內容來自下面這篇博文,推薦有興趣的小夥伴檢視原文
這一節主要簡單的介紹了下分散式系統中應用到的一些技術方案,如有對其中某個技術有興趣的小夥伴可以留言,後續會逐一進行補全
最後再介紹一些常見的分散式業務場景及對應的解決方案,比如全域性唯一的遞增ID-雪花演演算法,分散式系統的資源搶佔-分散式鎖,分散式事務-2pc/3pc/tcc ,分散式快取等
快取實際上並不是分散式獨有的,這裡把它加進來,主要是因為實在是應用得太廣了,無論是應用服務、基礎軟體工具還是作業系統,大量都可以見到快取的身影
快取的核心思想在於: 藉助更高效的IO方式,來替代代價昂貴的IO方式
如:
用好快取可以有效提高應用效能,下面以一個普通的java前臺應用為例說明
快取面臨的核心問題,則在於
在傳統的單體架構中,業務id基本上是依賴於資料庫的自增id來處理;當我們進入分散式場景時,如我們常說的分庫分表時,就需要我們來考慮如何實現全域性唯一的業務id了,避免出現在分表中出現衝突
全域性唯一ID解決方案:
常用於分散式系統中資源控制,只有持有鎖的才能繼續操作,確保同一時刻只會有一個範例存取這個資源
常見的分散式鎖有
事務表示一組操作,要麼全部成功,要麼全部不成功;單機事務通常說的是資料庫的事務;而分散式事務,則可以簡單理解為多個資料庫的操作,要麼同時成功,要麼全部不成功
更確切一點的說法,分散式事務主要是要求事務的參與方,可能涉及到多個系統、多個資料資源,要求它們的操作要麼都成功,要麼都回滾;
一個簡單的例子描述下分散式事務場景:
下單扣庫存
一個下單支付操作,涉及到三個系統,而分散式事務則是要求,若支付成功,則上面三個系統都應該更新成功;若有一個操作失敗,如支付失敗,則已經扣了庫存的要回滾(還庫存),生成的訂單資訊回滾(刪掉--注:現實中並不會去刪除訂單資訊,這裡只是用於說明分散式事務,請勿帶入實際的實現方案)
分散式事務實現方案:
分散式任務相比於我們常說單機的定時任務而言,可以簡單的理解為多臺範例上的定時任務,從應用場景來說,可以區分兩種
分散式任務實現方案:
Session一般叫做對談,Session技術是http狀態保持在伺服器端的解決方案,它是通過伺服器來保持狀態的。我們可以把使用者端瀏覽器與伺服器之間一系列互動的動作稱為一個 Session。是伺服器端為使用者端所開闢的儲存空間,在其中儲存的資訊就是用於保持狀態。因此,session是解決http協定無狀態問題的伺服器端解決方案,它能讓使用者端和伺服器端一系列互動動作變成一個完整的事務。
單機基於session/cookie來實現使用者認證,那麼在分散式系統的多範例之間,如何驗證使用者身份呢?這個就是我們說的分散式session
分散式session實現方案:
分散式鏈路追蹤也可以叫做全鏈路追中,而它可以說是每個開發者的福音,通常指的是一次前端的請求,將這個請求過程中,所有涉及到的系統、鏈路都串聯起來,可以清晰的知道這一次請求中,呼叫了哪些服務,有哪些IO互動,瓶頸點在哪裡,什麼地方丟擲了異常
當前主流的全鏈路方案大多是基於google的Dapper
論文實現的
全鏈路實現方案
Bloom過濾器是一種節省空間的概率資料結構,用於測試元素是否為某集合的成員。
布隆過濾器由一個長度為 m 位元的位陣列(bit array)與 k 個雜湊函數(hash function)組成的資料結構。
原理是當一個元素被加入集合時,通過 K 個雜湊函數將這個元素對映成一個位陣列中的 K 個點,把它們置為 1。
檢索時,我們只要看看這些點是不是都是 1 就大約知道集合中有沒有它了,也就是說,如果這些點有任何一個 0 ,則被檢元素一定不在;如果都是 1 ,則被檢元素很可能在。
關於布隆過濾器,請牢記一點
常見的應用場景,如
分散式系統的解決方案當然不侷限於上面幾種,比如分散式儲存、分散式計算等也屬於常見的場景,當然在我們實際的業務支援過程中,不太可能需要讓我們自己來支撐這種大活;而上面提到的幾個點,基本上或多或少會與我們日常工作相關,這裡列出來當然是好為了後續的詳情做鋪墊
這是一篇概括性的綜述類文章,可能並沒有很多的乾貨,當然也限於「一灰灰」我個人的能力,上面的總結可能並不準確,如有發現,請不吝賜教
全文總結如下
常見的分散式架構設計方案:
分散式系統中的理論基石:
分散式系統中的演演算法:
分散式系統解決方案:
最後總結一下這篇耗時兩週寫完的「心血鉅作」(有點自吹了哈),準備這篇文章確實花了很大的精力,首先我個人對於分散式這塊的理解並不能算深刻,其次分散式這塊的理論+實踐知識特別多,而且並不是特別容易上手理解,在輸出這篇文章的同時,遇到一些疑問點我也會去查閱相關資料去確認,整個過程並不算特別順利; 那麼為什麼還要去做這個事情呢?