分布式系統(tǒng)在互聯網公司中的應用已經非常普遍,開源軟件層出不窮。hadoop生態(tài)系統(tǒng),從hdfs到hbase,從mapreduce到spark,從storm到spark streaming, heron, flink等等,如何在開源的汪洋中不會迷失自己?本文將從基本概念、架構并結合自己學習工作中的感悟,闡述如何學習分布式系統(tǒng)。由于分布式系統(tǒng)理論體系非常龐大,知識面非常廣博,筆者能力有限,不足之處,歡迎討論交流。
常見的分布式系統(tǒng)分為數據存儲系統(tǒng)如hdfs,hbase;數據處理計算系統(tǒng)如storm、spark、flink;數據存儲兼分析混合系統(tǒng),這類系統(tǒng)在數據存儲的基礎上提供了復雜的數據搜索查詢功能,如elastic search、druid。對于存儲兼計算的系統(tǒng),我們仍然可以分開分析,所以本文會從數據存儲和計算兩種系統(tǒng)來論述。
文章的大致結構:第一部分,分布式系統(tǒng)的基本概念;第二、三部分分別詳細論述數據存儲和數據計算系統(tǒng);最后一部分總結。
概念
分布式系統(tǒng):每個人都在提分布式系統(tǒng),那么什么是分布式系統(tǒng)?其基本概念就是組件分布在網絡計算機上,組件之間僅僅通過消息傳遞來通信并協(xié)調行動。
A distributed system is one in which
components located at networked computers communicate and coordinate their
actions only by passing messages. (摘自分布式系統(tǒng)概念和設計)
節(jié)點:節(jié)點可以理解為上述概念提到的組件,其實完成一組完整邏輯的程序個體,對應于server上的一個獨立進程。一提到節(jié)點,就會考慮節(jié)點是有狀態(tài)還是無狀態(tài)的?判斷標準很簡單,該獨立節(jié)點是否維護著本地存儲的一些狀態(tài)信息,或者節(jié)點是不是可以隨時遷移到其他server上而保持節(jié)點的行為和以前一致,如果是的話,則該節(jié)點是無狀態(tài),否則是有狀態(tài)的。
異常:異常處理可以說是分布式系統(tǒng)的核心問題,那么分布式異常處理相對于單機來說,有什么不同呢?在單機系統(tǒng)中,對于程序的處理結果是可以預知的,要么成功,要么失敗,結果很明確。可在分布式環(huán)境中,處理結果除了明確返回成功或失敗,還有另外一種狀態(tài):超時,那超時意味著處理結果完全不確定,有可能成功執(zhí)行,也有可能執(zhí)行失敗,也有可能根本沒執(zhí)行,這給系統(tǒng)開發(fā)帶來了很大的難度。其實各種各樣的分布式協(xié)議就是保證系統(tǒng)在各種異常情形下仍能正常的工作,所以在學習分布式系統(tǒng)時,要著重看一下文檔異常處理fault-tolerance章節(jié)。
CAP理論:學習分布式系統(tǒng)中需要重要理解的理論,同時在架構設計中也可以用到這個理論,例如在一些情形下我們可以通過降低一致性來提高系統(tǒng)的可用性,將數據的每次數據庫更新操作變成批量操作就是典型的例子。
CAP理論,三個字母代表了系統(tǒng)中三個相互矛盾的屬性:
C(Consistency):強一致性,保證數據中的數據完全一致;
A(Available):在系統(tǒng)異常時,仍然可以提供服務,注:這兒的可用性,一方面要求系統(tǒng)可以正常的運行返回結果,另一方面同樣對響應速度有一定的保障;
P(Tolerance to the partition of network ):既然是分布式系統(tǒng),很多組件都是部署在不同的server中,通過網絡通信協(xié)調工作,這就要求在某些節(jié)點服發(fā)生網絡分區(qū)異常,系統(tǒng)仍然可以正常工作。
CAP 理論指出,無法設計一種分布式協(xié)議同時完全具備CAP屬性。
從以上CAP的概念我們得出一個結論,在技術選型時,根據你的需求來判斷是需要AP高可用性的系統(tǒng)(容忍返回不一致的數據)還是CP強一致性的系統(tǒng),或者根據系統(tǒng)提供的參數在AC之間權衡。(可能會有讀者會問,為什么一定需要P呢?既然是分布式系統(tǒng),在網絡分區(qū)異常情況下仍然正常提供服務是必須的。)
數據存儲系統(tǒng)
當數據量太大以及已經超過單機所能處理的極限時,就需要使用到數據存儲分布式系統(tǒng)。無論是選擇開源系統(tǒng)還是自己設計,第一個要考慮的問題就是數據如何分布式化。
數據分布方式
哈希方式:哈希方式是最常見的數據分布方式??梢院唵蜗胂笥幸粋€大的hash表,其中每個桶對應的一臺存儲服務器,每條數據通過某種方式計算出其hash值分配到對應的桶中。 int serverId = data.hashcode % serverTotalNum 上面只是一個簡單的計算公式示例,通過這種方式就可以將數據分配到不同的服務器上。
優(yōu)點:不需要存儲數據和server映射關系的meta信息,只需記錄serverId和server
ip映射關系即可。
缺點:可擴展性不高,當集群規(guī)模需要擴展時,集群中所有的數據需要遷移,即使在最優(yōu)情況下——集群規(guī)模成倍擴展,仍然需要遷移集群一半的數據(這個問題有時間可以考慮一下,為啥只需要遷移一半?);另一個問題:數據通過某種hash計算后都落在某臺服務器上,造成數據傾斜(data skew)問題。
應用例子:ElasticSearch數據分布就是hash方式,根據routingId取模映射到對應到不同node上。
數據范圍分布:將數據的某個特征值按照值域分為不同區(qū)間。比如按時間、區(qū)間分割,不同時間范圍劃分到不同server上。
優(yōu)點:數據區(qū)間可以自由分割,當出現數據傾斜時,即某一個區(qū)間的數據量非常大,則可以將該區(qū)間split然后將數據進行重分配;集群方便擴展,當添加新的節(jié)點,只需將數據量多的節(jié)點數據遷移到新節(jié)點即可。
缺點:需要存儲大量的元信息(數據區(qū)間和server的對應關系)。
應用例子:Hbase的數據分布則是利用data的rowkey進行區(qū)間劃分到不同的region server,而且支持region的split。
數據量分布:按數據量分布,可以考慮一個簡單例子:當使用log文件記錄一些系統(tǒng)運行的日志信息時,當日志文件達到一定大小,就會生成新的文件開始記錄后續(xù)的日志信息。這樣的存儲方式和數據的特征類型沒有關系,可以理解成將一個大的文件分成固定大小的多個block。
優(yōu)點:不會有數據傾斜的問題,而且數據遷移時速度非常快(因為一個文件由多個block組成,block在不同的server上,遷移一個文件可以多個server并行復制這些block)。
缺點: 需要存儲大量的meta信息(文件和block的對應關系,block和server的對應關系)。
應用例子:Hdfs的文件存儲按數據量block分布。
一致性哈希:前文剛提到的哈希方式,當添加刪除節(jié)點時候,所有節(jié)點都會參與到數據的遷移,整個集群都會受到影響。那么一致性哈??梢院芎玫慕鉀Q這個問題。一致性哈希和哈希的數據分布方式大概一致,唯一不同的是一致性哈希hash的值域是個環(huán)。
優(yōu)點:集群可擴展性好,當增加刪除節(jié)點,只影響相鄰的數據節(jié)點。
缺點:上面的優(yōu)點同時也是缺點,當一個節(jié)點掛掉時,將壓力全部轉移到相鄰節(jié)點,有可能將相鄰節(jié)點壓垮。
應用例子:Cassandra數據分布使用的是一致性hash,只不過使用的是一致性hash改良版:虛擬節(jié)點的一致性hash(有興趣的可以研究下)。
討論完數據分布問題,接下來該考慮如何解決當某個節(jié)點服務不可達的時候系統(tǒng)仍然可以正常工作(分布式系統(tǒng)CAP中網絡分區(qū)異常問題)?這個問題的解決方案說起來很簡單,就是將數據的存儲增加多個副本,而且分布在不同的節(jié)點上,當某個節(jié)點掛掉的時候,可以從其他數據副本讀取。
引入多個副本后,引來了一系列問題:多個副本之間,讀取時以哪個副本的數據為準呢,更新時什么才算更新成功,是所有副本都更新成功還是部分副本更新成功即可認為更新成功?這些問題其實就是CAP理論中可用性和一致性的問題。其中primary-secondary副本控制模型則是解決這類問題行之有效的方法。

主從(primary-secondary )模型是一種常見的副本更新讀取模型,這種模型相對來說簡單,所有的副本相關控制都由中心節(jié)點控制,數據的并發(fā)修改同樣都由主節(jié)點控制,這樣問題就可以簡化成單機問題,極大的簡化系統(tǒng)復雜性。
注:常用的副本更新讀取架構有兩種:主從(primary-secondary)和去中心化(decentralized)結構,其中主從結構較為常見,而去中心化結構常采用paxos、raft、vector time等協(xié)議,這里由于本人能力有限,就不再這兒敘述了,有興趣可以自己學習,歡迎補充。
其中涉及到主從副本操作有以下幾種:
副本的更新
副本更新基本流程:數據更新操作發(fā)到primary節(jié)點,由primary將數據更新操作同步到其他secondary副本,根據其他副本的同步結果返回客戶端響應。各類數據存儲分布式系統(tǒng)的副本更新操作流程大體是一樣的,唯一不同的是primary副本更新操作完成后響應客戶端時機的不同,這與系統(tǒng)可用性和一致性要求密切相關。
以mysql的master slave簡單說明下,通常情況下,mysql的更新只需要master更新成功即可響應客戶端,slave可以通過binlog慢慢同步,這種情形讀取slave會有一定的延遲,一致性相對較弱,但是系統(tǒng)的可用性有了保證;另一種slave更新策略,數據的更新操作不僅要求master更新成功,同時要求slave也要更新成功,primary和secondray數據保持同步,系統(tǒng)保證強一致性,但可用性相對較差,響應時間變長。
上述的例子只有兩個副本,如果要求強一致性,所有副本都更新完成才認為更新成功,響應時間相對來說也可以接受,但是如果副本數更多,有沒有什么方法在保證一定一致性同時滿足一定的可用性呢?這時就需要考慮Quorum協(xié)議,其理論可以用一個簡單的數學問題來說明:
有N個副本,其中在更新時有W個副本更新成功,那我們讀取R個副本,W、R在滿足什么條件下保證我們讀取的R個副本一定有一個副本是最新數據(假設副本都有一個版本號,版本號大的即為最新數據)?
問題的答案是:W+R > N (有興趣的可以思考下)
通過quorum協(xié)議,在保證一定的可用性同時又保證一定的一致性的情形下,設置副本更新成功數為總副本數的一半(即N/2+1)性價比最高。(看到這兒有沒有想明白為什么zookeeper server數最好為基數個?)
副本的讀取
副本的讀取策略和一致性的選擇有關,如果需要強一致性,我們可以只從primary副本讀取,如果需要最終一致性,可以從secondary副本讀取結果,如果需要讀取最新數據,則按照quorum協(xié)議要求,讀取相應的副本數。
副本的切換
當系統(tǒng)中某個副本不可用時,需要從剩余的副本之中選取一個作為primary副本來保證后續(xù)系統(tǒng)的正常執(zhí)行。這兒涉及到兩個問題:
副本狀態(tài)的確定以及防止brain split問題:一般方法是利用zookeeper中的sesstion以及臨時節(jié)點,其基本原理則是lease協(xié)議和定期heartbeat。Lease協(xié)議可以簡單理解成參與雙方達成一個承諾,針對zookeeper,這個承諾就是在session有效時間內,我認為你的節(jié)點狀態(tài)是活的是可用的,如果發(fā)生session timeout,認為副本所在的服務已經不可用,無論誤判還是服務真的宕掉了,通過這種機制可以防止腦裂的發(fā)生。但這樣會引起另外一個問題:當在session timeout期間,primary 副本服務掛掉了,這樣會造成一段時間內的服務不可用。
primary副本的確定:這個問題和副本讀取最新數據其實是一個問題,可以利用quoram以及全局版本號確定primary副本。zookeeper在leader選舉的過程中其實利用了quoram以及全局事務id——zxid確定primary副本。
存儲架構模型
關于數據的分布和副本的模型這些細節(jié)問題已經詳細敘述,那么從系統(tǒng)整體架構來看,數據存儲的一般流程和主要模塊都有哪些呢?從元數據存儲以及節(jié)點之間的membership管理方面來看,主要分以下兩類:
中心化的節(jié)點membership管理架構

這類系統(tǒng)主要分為三個模塊:client模塊,負責用戶和系統(tǒng)內部模塊的通信;master節(jié)點模塊,負責元數據的存儲以及節(jié)點健康狀態(tài)的管理;data節(jié)點模塊,用于數據的存儲和數據查詢返回。
數據的查詢流程通常分兩步:1. 向master節(jié)點查詢數據對應的節(jié)點信息;2. 根據返回的節(jié)點信息連接對應節(jié)點,返回相應的數據。
分析一下目前常見的數據存儲系統(tǒng),從hdfs,hbase再到Elastic Search,通過與上述通用系統(tǒng)對比,發(fā)現:master節(jié)點模塊具體對應hdfs的namenode、hbase的hMaster、Elastic
Search的master節(jié)點;data節(jié)點對應hdfs的datanode、hbase的region server、Elastic Search的data node。
去中心化的節(jié)點membership管理架構

與上一模型比較,其最大的變化就是該架構中不存在任何master節(jié)點,系統(tǒng)中的每個節(jié)點可以做類似master的任務:存儲系統(tǒng)元信息以及管理集群節(jié)點。
數據的查詢方式也有所不同,client可以訪問系統(tǒng)中的任意節(jié)點,而不再局限于master節(jié)點,具體查詢流程如下:1. 查詢系統(tǒng)中任意節(jié)點,如果該數據在此節(jié)點上則返回相應的數據,如果不在該節(jié)點,則返回對應數據的節(jié)點地址,執(zhí)行第二步;2. 獲得數據對應的地址后向相關請求數據。
節(jié)點之間共享狀態(tài)信息是如何做到的呢?常用的方法是使用如gossip的協(xié)議以及在此基礎之上開發(fā)的serf框架,感興趣的話可以參考redis cluster 和 consul實現。
數據計算處理系統(tǒng)
常用的數據計算主要分為離線批量計算,可以是實時計算,也可以是準實時mini-batch計算,雖然開源的系統(tǒng)很多,且每個系統(tǒng)都有其側重點,但有些問題卻是共性相通的。
數據投遞策略
在數據處理中首先要考慮一個問題,我們的數據記錄在系統(tǒng)中會被處理幾次(包括正常情形和異常情形):
at most once:數據處理最多一次,這種語義在異常情況下會有數據丟失;
at least once:數據處理最少一次,這種語義會造成數據的重復;
exactly once:數據只處理一次,這種語義支持是最復雜的,要想完成這一目標需要在數據處理的各個環(huán)節(jié)做到保障。
如何做到exactly once,需要在數據處理各個階段做些保證:
數據接收:由不同的數據源保證。
數據傳輸:數據傳輸可以保證exactly once。
數據輸出:根據數據輸出的類型確定,如果數據的輸出操作對于同樣的數據輸入保證冪等性,這樣就很簡單(比如可以把kafka的offset作為輸出mysql的id),如果不是,要提供額外的分布式事務機制如兩階段提交等等。
異常任務的處理
異常處理相對數據存儲系統(tǒng)來說簡單很多,因為數據計算的節(jié)點都是無狀態(tài)的,只要啟動任務副本即可。
注意:異常任務除了那些失敗、超時的任務,還有一類特殊任務——straggler(拖后腿)任務,一個大的Job會分成多個小task并發(fā)執(zhí)行,發(fā)現某一個任務比同類型的其他任務執(zhí)行要慢很多(忽略數據傾斜導致執(zhí)行速度慢的因素)。
其中任務恢復策略有以下幾種:
簡單暴力,重啟任務重新計算相關數據,典型應用:storm,當某個數據執(zhí)行超時或失敗,則將該數據從源頭開始在拓撲中重新計算。
根據checkpoint重試出錯的任務,典型應用:mapreduce,一個完整的數據處理是分多個階段完成的,每個階段(map 或者reduce)的輸出結果都會保存到相應的存儲中,只要重啟任務重新讀取上一階段的輸出結果即可繼續(xù)開始運行,不必從開始重新執(zhí)行該任務。
背壓——Backpressure
在數據處理中,經常會擔心這樣一個問題:數據處理的上游消費數據速度太快,會不會壓垮下游數據輸出端如mysql等。 通常的解決方案:上線前期我們會做詳細的測試,評估數據下游系統(tǒng)承受的最大壓力,然后對數據上游進行限流的配置,比如限制每秒最多消費多少數據。其實這是一個常見的問題,現在各個實時數據處理系統(tǒng)都提供了背壓的功能,包括spark streaming、storm等,當下游的數據處理速度過慢,系統(tǒng)會自動降低上游數據的消費速度。
對背壓感興趣朋友們,或者有想法自己實現一套數據處理系統(tǒng),可以參考Reactive Stream,該項目對通用數據處理提供了一種規(guī)范,采用這種規(guī)范比較有名的是akka。
數據處理通用架構
數據處理的架構大抵是相似的,通常包含以下幾個模塊:
client: 負責計算任務的提交。
scheduler : 計算任務的生成和計算資源的調度,同時還包含計算任務運行狀況的監(jiān)控和異常任務的重啟。
worker:計算任務會分成很多小的task,worker負責這些小task的執(zhí)行同時向scheduler匯報當前node可用資源及task的執(zhí)行狀況。

上圖是通用的架構模型圖,有些人會問這是hadoop
v1版本的mapreduce計算框架圖,現在都已經yarn模式的新的計算框架圖,誰還用這種模式?哈哈,說的對,但是現在仍然有些處理框架就是這種模型————storm。
不妨把圖上的一些概念和storm的概念映射起來:Job tracker 對應于 nimbus,task tracker 對應于 supervisor,每臺supervisor 同樣要配置worker slot,worker對應于storm中的worker。這樣一對比,是不是就覺得一樣了?
這種框架模型有它的問題,責任不明確,每個模塊干著多樣工作。例如Job tracker不僅要監(jiān)控任務的執(zhí)行狀態(tài),還要負責任務的調度。TaskTracker也同樣,不僅要監(jiān)控task的狀態(tài)、執(zhí)行,同樣還要監(jiān)控節(jié)點資源的使用。

針對以上問題,基于yarn模式的新的處理架構模型,將任務執(zhí)行狀態(tài)的監(jiān)控和任務資源的調度分開。原來的Job tracker分為resource manger 負責資源的調度,任務執(zhí)行的監(jiān)控則交給每個appMaster來負責,原來的task tracker,變?yōu)榱薾ode manager,負責資源的監(jiān)控和task的啟動,而task的執(zhí)行狀態(tài)和異常處理則交給appMaster處理。
同樣的,twitter 根據storm架構方面的一些問題,推出了新的處理框架heron,其解決的問題也是將任務的調度和任務的執(zhí)行狀態(tài)監(jiān)控責任分離,引入了新的概念Topology Master,類似于這兒的appMaster。
總結
分布式系統(tǒng)涵蓋的內容非常多,本篇文章主要從整體架構以及概念上介紹如何入門,學習過程有一些共性的問題,在這兒總結一下:
先分析該系統(tǒng)是數據存儲還是計算系統(tǒng)。
如果是數據存儲系統(tǒng),從數據分布和副本策略開始入手;如果是數據處理問題,從數據投遞策略入手。
讀對應系統(tǒng)架構圖,對應著常用的架構模型,每個組件和已有的系統(tǒng)進行類比,想一下這個組件類似于hdfs的namenode等等,最后在腦海里梳理下數據流的整個流程。
在了解了系統(tǒng)的大概,著重看下文檔中fault tolerence章節(jié),看系統(tǒng)如何容錯,或者自己可以預先問些問題,比如如果一個節(jié)點掛了、一個任務掛了系統(tǒng)是如何處理這些異常的,帶著問題看文檔。
文檔詳細讀了一遍,就可以按照官方文檔寫些hello world的例子了,詳細查看下系統(tǒng)配置項,隨著工作的深入就可以看些系統(tǒng)的細節(jié)和關鍵源碼了。
————————————————————————————————————
想了解更多前沿技術,想獲取最新免費編程資源視頻源碼筆記,小伙伴請往下看!
qun號是:八六四,六三四,八四五。qun內有很多開發(fā)工具,很多干貨和技術資料分享!
如果您覺得此篇文章對您有幫助,歡迎關注微信公眾號:大禹編程,您的支持是對我最大的鼓勵!共同學習,共同進步: