如何學習分布式系統?一文全Get!

如何學習分佈式系統?一文全Get!

分佈式系統在互聯網公司中的應用已經非常普遍,開源軟件層出不窮。hadoop生態系統,從hdfs到hbase,從mapreduce到spark,從storm到spark streaming, heron, flink等等,如何在開源的汪洋中不會迷失自己?本文將從基本概念、架構並結合自己學習工作中的感悟,闡述如何學習分佈式系統。由於分佈式系統理論體系非常龐大,知識面非常廣博,筆者能力有限,不足之處,歡迎討論交流。

常見的分佈式系統分為數據存儲系統如hdfs,hbase;數據處理計算系統如storm、spark、flink;數據存儲兼分析混合系統,這類系統在數據存儲的基礎上提供了複雜的數據搜索查詢功能,如elastic search、druid。對於存儲兼計算的系統,我們仍然可以分開分析,所以本文會從數據存儲和計算兩種系統來論述。

文章的大致結構:第一部分,分佈式系統的基本概念;第二、三部分分別詳細論述數據存儲和數據計算系統;最後一部分總結。

概念

  • 分佈式系統:每個人都在提分佈式系統,那麼什麼是分佈式系統?其基本概念就是組件分佈在網絡計算機上,組件之間僅僅通過消息傳遞來通信並協調行動。
  • A distributed system is one in which components located at networked computers communicate and coordinate their actions only by passing messages. (摘自分佈式系統概念和設計)
  • 節點:節點可以理解為上述概念提到的組件,其實完成一組完整邏輯的程序個體,對應於server上的一個獨立進程。一提到節點,就會考慮節點是有狀態還是無狀態的?判斷標準很簡單,該獨立節點是否維護著本地存儲的一些狀態信息,或者節點是不是可以隨時遷移到其他server上而保持節點的行為和以前一致,如果是的話,則該節點是無狀態,否則是有狀態的。
  • 異常:異常處理可以說是分佈式系統的核心問題,那麼分佈式異常處理相對於單機來說,有什麼不同呢?在單機系統中,對於程序的處理結果是可以預知的,要麼成功,要麼失敗,結果很明確。可在分佈式環境中,處理結果除了明確返回成功或失敗,還有另外一種狀態:超時,那超時意味著處理結果完全不確定,有可能成功執行,也有可能執行失敗,也有可能根本沒執行,這給系統開發帶來了很大的難度。其實各種各樣的分佈式協議就是保證系統在各種異常情形下仍能正常的工作,所以在學習分佈式系統時,要著重看一下文檔異常處理fault-tolerance章節。
  • CAP理論:學習分佈式系統中需要重要理解的理論,同時在架構設計中也可以用到這個理論,例如在一些情形下我們可以通過降低一致性來提高系統的可用性,將數據的每次數據庫更新操作變成批量操作就是典型的例子。
  • CAP理論,三個字母代表了系統中三個相互矛盾的屬性:
  • C(Consistency):強一致性,保證數據中的數據完全一致;
  • A(Available):在系統異常時,仍然可以提供服務,注:這兒的可用性,一方面要求系統可以正常的運行返回結果,另一方面同樣對響應速度有一定的保障;
  • P(Tolerance to the partition of network ):既然是分佈式系統,很多組件都是部署在不同的server中,通過網絡通信協調工作,這就要求在某些節點服發生網絡分區異常,系統仍然可以正常工作。
  • CAP 理論指出,無法設計一種分佈式協議同時完全具備CAP屬性。
  • 從以上CAP的概念我們得出一個結論,在技術選型時,根據你的需求來判斷是需要AP高可用性的系統(容忍返回不一致的數據)還是CP強一致性的系統,或者根據系統提供的參數在AC之間權衡。(可能會有讀者會問,為什麼一定需要P呢?既然是分佈式系統,在網絡分區異常情況下仍然正常提供服務是必須的。)

數據存儲系統

當數據量太大以及已經超過單機所能處理的極限時,就需要使用到數據存儲分佈式系統。無論是選擇開源系統還是自己設計,第一個要考慮的問題就是數據如何分佈式化。

數據分佈方式

哈希方式:哈希方式是最常見的數據分佈方式。可以簡單想象有一個大的hash表,其中每個桶對應的一臺存儲服務器,每條數據通過某種方式計算出其hash值分配到對應的桶中。 int serverId = data.hashcode % serverTotalNum 上面只是一個簡單的計算公式示例,通過這種方式就可以將數據分配到不同的服務器上。

  • 優點:不需要存儲數據和server映射關係的meta信息,只需記錄serverId和server ip映射關係即可。
  • 缺點:可擴展性不高,當集群規模需要擴展時,集群中所有的數據需要遷移,即使在最優情況下——集群規模成倍擴展,仍然需要遷移集群一半的數據(這個問題有時間可以考慮一下,為啥只需要遷移一半?);另一個問題:數據通過某種hash計算後都落在某臺服務器上,造成數據傾斜(data skew)問題。
  • 應用例子:ElasticSearch數據分佈就是hash方式,根據routingId取模映射到對應到不同node上。

數據範圍分佈:將數據的某個特徵值按照值域分為不同區間。比如按時間、區間分割,不同時間範圍劃分到不同server上。

  • 優點:數據區間可以自由分割,當出現數據傾斜時,即某一個區間的數據量非常大,則可以將該區間split然後將數據進行重分配;集群方便擴展,當添加新的節點,只需將數據量多的節點數據遷移到新節點即可。
  • 缺點:需要存儲大量的元信息(數據區間和server的對應關係)。
  • 應用例子:Hbase的數據分佈則是利用data的rowkey進行區間劃分到不同的region server,而且支持region的split。

數據量分佈:按數據量分佈,可以考慮一個簡單例子:當使用log文件記錄一些系統運行的日誌信息時,當日志文件達到一定大小,就會生成新的文件開始記錄後續的日誌信息。這樣的存儲方式和數據的特徵類型沒有關係,可以理解成將一個大的文件分成固定大小的多個block。

  • 優點:不會有數據傾斜的問題,而且數據遷移時速度非常快(因為一個文件由多個block組成,block在不同的server上,遷移一個文件可以多個server並行複製這些block)。
  • 缺點: 需要存儲大量的meta信息(文件和block的對應關係,block和server的對應關係)。
  • 應用例子:Hdfs的文件存儲按數據量block分佈。

一致性哈希:前文剛提到的哈希方式,當添加刪除節點時候,所有節點都會參與到數據的遷移,整個集群都會受到影響。那麼一致性哈希可以很好的解決這個問題。一致性哈希和哈希的數據分佈方式大概一致,唯一不同的是一致性哈希hash的值域是個環。

  • 優點:集群可擴展性好,當增加刪除節點,隻影響相鄰的數據節點。
  • 缺點:上面的優點同時也是缺點,當一個節點掛掉時,將壓力全部轉移到相鄰節點,有可能將相鄰節點壓垮。
  • 應用例子:Cassandra數據分佈使用的是一致性hash,只不過使用的是一致性hash改良版:虛擬節點的一致性hash(有興趣的可以研究下)。

討論完數據分佈問題,接下來該考慮如何解決當某個節點服務不可達的時候系統仍然可以正常工作(分佈式系統CAP中網絡分區異常問題)?這個問題的解決方案說起來很簡單,就是將數據的存儲增加多個副本,而且分佈在不同的節點上,當某個節點掛掉的時候,可以從其他數據副本讀取。

引入多個副本後,引來了一系列問題:多個副本之間,讀取時以哪個副本的數據為準呢,更新時什麼才算更新成功,是所有副本都更新成功還是部分副本更新成功即可認為更新成功?這些問題其實就是CAP理論中可用性和一致性的問題。其中primary-secondary副本控制模型則是解決這類問題行之有效的方法。

primary-secondary控制模型

如何學習分佈式系統?一文全Get!

主從(primary-secondary )模型是一種常見的副本更新讀取模型,這種模型相對來說簡單,所有的副本相關控制都由中心節點控制,數據的併發修改同樣都由主節點控制,這樣問題就可以簡化成單機問題,極大的簡化系統複雜性。

注:常用的副本更新讀取架構有兩種:主從(primary-secondary)和去中心化(decentralized)結構,其中主從結構較為常見,而去中心化結構常採用paxos、raft、vector time等協議,這裡由於本人能力有限,就不再這兒敘述了,有興趣可以自己學習,歡迎補充。

其中涉及到主從副本操作有以下幾種:

副本的更新

副本更新基本流程:數據更新操作發到primary節點,由primary將數據更新操作同步到其他secondary副本,根據其他副本的同步結果返回客戶端響應。各類數據存儲分佈式系統的副本更新操作流程大體是一樣的,唯一不同的是primary副本更新操作完成後響應客戶端時機的不同,這與系統可用性和一致性要求密切相關。

以mysql的master slave簡單說明下,通常情況下,mysql的更新只需要master更新成功即可響應客戶端,slave可以通過binlog慢慢同步,這種情形讀取slave會有一定的延遲,一致性相對較弱,但是系統的可用性有了保證;另一種slave更新策略,數據的更新操作不僅要求master更新成功,同時要求slave也要更新成功,primary和secondray數據保持同步,系統保證強一致性,但可用性相對較差,響應時間變長。

上述的例子只有兩個副本,如果要求強一致性,所有副本都更新完成才認為更新成功,響應時間相對來說也可以接受,但是如果副本數更多,有沒有什麼方法在保證一定一致性同時滿足一定的可用性呢?這時就需要考慮Quorum協議,其理論可以用一個簡單的數學問題來說明:

有N個副本,其中在更新時有W個副本更新成功,那我們讀取R個副本,W、R在滿足什麼條件下保證我們讀取的R個副本一定有一個副本是最新數據(假設副本都有一個版本號,版本號大的即為最新數據)?

問題的答案是:W+R > N (有興趣的可以思考下)

通過quorum協議,在保證一定的可用性同時又保證一定的一致性的情形下,設置副本更新成功數為總副本數的一半(即N/2+1)性價比最高。(看到這兒有沒有想明白為什麼zookeeper server數最好為基數個?)

副本的讀取

副本的讀取策略和一致性的選擇有關,如果需要強一致性,我們可以只從primary副本讀取,如果需要最終一致性,可以從secondary副本讀取結果,如果需要讀取最新數據,則按照quorum協議要求,讀取相應的副本數。

副本的切換

當系統中某個副本不可用時,需要從剩餘的副本之中選取一個作為primary副本來保證後續系統的正常執行。這兒涉及到兩個問題:

  • 副本狀態的確定以及防止brain split問題:一般方法是利用zookeeper中的sesstion以及臨時節點,其基本原理則是lease協議和定期heartbeat。Lease協議可以簡單理解成參與雙方達成一個承諾,針對zookeeper,這個承諾就是在session有效時間內,我認為你的節點狀態是活的是可用的,如果發生session timeout,認為副本所在的服務已經不可用,無論誤判還是服務真的宕掉了,通過這種機制可以防止腦裂的發生。但這樣會引起另外一個問題:當在session timeout期間,primary 副本服務掛掉了,這樣會造成一段時間內的服務不可用。
  • primary副本的確定:這個問題和副本讀取最新數據其實是一個問題,可以利用quoram以及全局版本號確定primary副本。zookeeper在leader選舉的過程中其實利用了quoram以及全局事務id——zxid確定primary副本。

存儲架構模型

關於數據的分佈和副本的模型這些細節問題已經詳細敘述,那麼從系統整體架構來看,數據存儲的一般流程和主要模塊都有哪些呢?從元數據存儲以及節點之間的membership管理方面來看,主要分以下兩類:

中心化的節點membership管理架構

如何學習分佈式系統?一文全Get!

這類系統主要分為三個模塊:client模塊,負責用戶和系統內部模塊的通信;master節點模塊,負責元數據的存儲以及節點健康狀態的管理;data節點模塊,用於數據的存儲和數據查詢返回。

數據的查詢流程通常分兩步:1. 向master節點查詢數據對應的節點信息;2. 根據返回的節點信息連接對應節點,返回相應的數據。

分析一下目前常見的數據存儲系統,從hdfs,hbase再到Elastic Search,通過與上述通用系統對比,發現:master節點模塊具體對應hdfs的namenode、hbase的hMaster、Elastic Search的master節點;data節點對應hdfs的datanode、hbase的region server、Elastic Search的data node。

去中心化的節點membership管理架構

如何學習分佈式系統?一文全Get!

與上一模型比較,其最大的變化就是該架構中不存在任何master節點,系統中的每個節點可以做類似master的任務:存儲系統元信息以及管理集群節點。

數據的查詢方式也有所不同,client可以訪問系統中的任意節點,而不再侷限於master節點,具體查詢流程如下:1. 查詢系統中任意節點,如果該數據在此節點上則返回相應的數據,如果不在該節點,則返回對應數據的節點地址,執行第二步;2. 獲得數據對應的地址後向相關請求數據。

節點之間共享狀態信息是如何做到的呢?常用的方法是使用如gossip的協議以及在此基礎之上開發的serf框架,感興趣的話可以參考redis cluster 和 consul實現。

數據計算處理系統

常用的數據計算主要分為離線批量計算,可以是實時計算,也可以是準實時mini-batch計算,雖然開源的系統很多,且每個系統都有其側重點,但有些問題卻是共性相通的。

數據投遞策略

在數據處理中首先要考慮一個問題,我們的數據記錄在系統中會被處理幾次(包括正常情形和異常情形):

  • at most once:數據處理最多一次,這種語義在異常情況下會有數據丟失;
  • at least once:數據處理最少一次,這種語義會造成數據的重複;
  • exactly once:數據只處理一次,這種語義支持是最複雜的,要想完成這一目標需要在數據處理的各個環節做到保障。

如何做到exactly once, 需要在數據處理各個階段做些保證:

  • 數據接收:由不同的數據源保證。
  • 數據傳輸:數據傳輸可以保證exactly once。
  • 數據輸出:根據數據輸出的類型確定,如果數據的輸出操作對於同樣的數據輸入保證冪等性,這樣就很簡單(比如可以把kafka的offset作為輸出mysql的id),如果不是,要提供額外的分佈式事務機制如兩階段提交等等。

異常任務的處理

異常處理相對數據存儲系統來說簡單很多,因為數據計算的節點都是無狀態的,只要啟動任務副本即可。

注意:異常任務除了那些失敗、超時的任務,還有一類特殊任務——straggler(拖後腿)任務,一個大的Job會分成多個小task併發執行,發現某一個任務比同類型的其他任務執行要慢很多(忽略數據傾斜導致執行速度慢的因素)。

其中任務恢復策略有以下幾種:

  • 簡單暴力,重啟任務重新計算相關數據,典型應用:storm,當某個數據執行超時或失敗,則將該數據從源頭開始在拓撲中重新計算。
  • 根據checkpoint重試出錯的任務,典型應用:mapreduce,一個完整的數據處理是分多個階段完成的,每個階段(map 或者reduce)的輸出結果都會保存到相應的存儲中,只要重啟任務重新讀取上一階段的輸出結果即可繼續開始運行,不必從開始重新執行該任務。

背壓——Backpressure

在數據處理中,經常會擔心這樣一個問題:數據處理的上游消費數據速度太快,會不會壓垮下游數據輸出端如mysql等。 通常的解決方案:上線前期我們會做詳細的測試,評估數據下游系統承受的最大壓力,然後對數據上游進行限流的配置,比如限制每秒最多消費多少數據。其實這是一個常見的問題,現在各個實時數據處理系統都提供了背壓的功能,包括spark streaming、storm等,當下遊的數據處理速度過慢,系統會自動降低上游數據的消費速度。

對背壓感興趣朋友們,或者有想法自己實現一套數據處理系統,可以參考Reactive Stream,該項目對通用數據處理提供了一種規範,採用這種規範比較有名的是akka。

數據處理通用架構

數據處理的架構大抵是相似的,通常包含以下幾個模塊:

  • client: 負責計算任務的提交。
  • scheduler : 計算任務的生成和計算資源的調度,同時還包含計算任務運行狀況的監控和異常任務的重啟。
  • worker:計算任務會分成很多小的task, worker負責這些小task的執行同時向scheduler彙報當前node可用資源及task的執行狀況。
如何學習分佈式系統?一文全Get!

上圖是通用的架構模型圖,有些人會問這是hadoop v1版本的mapreduce計算框架圖,現在都已經yarn模式的新的計算框架圖,誰還用這種模式?哈哈,說的對,但是現在仍然有些處理框架就是這種模型————storm。

不妨把圖上的一些概念和storm的概念映射起來:Job tracker 對應於 nimbus,task tracker 對應於 supervisor,每臺supervisor 同樣要配置worker slot,worker對應於storm中的worker。 這樣一對比,是不是就覺得一樣了?

這種框架模型有它的問題,責任不明確,每個模塊幹著多樣工作。例如Job tracker不僅要監控任務的執行狀態,還要負責任務的調度。TaskTracker也同樣,不僅要監控task的狀態、執行,同樣還要監控節點資源的使用。

如何學習分佈式系統?一文全Get!

針對以上問題,基於yarn模式的新的處理架構模型,將任務執行狀態的監控和任務資源的調度分開。原來的Job tracker分為resource manger 負責資源的調度,任務執行的監控則交給每個appMaster來負責,原來的task tracker,變為了node manager,負責資源的監控和task的啟動,而task的執行狀態和異常處理則交給appMaster處理。

同樣的,twitter 根據storm架構方面的一些問題,推出了新的處理框架heron,其解決的問題也是將任務的調度和任務的執行狀態監控責任分離,引入了新的概念Topology Master,類似於這兒的appMaster。

總結

分佈式系統涵蓋的內容非常多,本篇文章主要從整體架構以及概念上介紹如何入門,學習過程有一些共性的問題,在這兒總結一下:

  • 先分析該系統是數據存儲還是計算系統。
  • 如果是數據存儲系統,從數據分佈和副本策略開始入手;如果是數據處理問題,從數據投遞策略入手。
  • 讀對應系統架構圖,對應著常用的架構模型,每個組件和已有的系統進行類比,想一下這個組件類似於hdfs的namenode等等,最後在腦海裡梳理下數據流的整個流程。
  • 在瞭解了系統的大概,著重看下文檔中fault tolerence章節,看系統如何容錯,或者自己可以預先問些問題,比如如果一個節點掛了、一個任務掛了系統是如何處理這些異常的,帶著問題看文檔。
  • 文檔詳細讀了一遍,就可以按照官方文檔寫些hello world的例子了,詳細查看下系統配置項,隨著工作的深入就可以看些系統的細節和關鍵源碼了。

這次分享的文章內容就這麼多,中間難免有些紕漏,有任何問題歡迎隨時指正交流,大家共同進步,謝謝大家。


分享到:


相關文章: