本系列文章整理自《大型網(wǎng)站系統(tǒng)與Java中間件實踐》
分布式系統(tǒng)的定義
A distributed system is one in which components located at networked computers communicate and coordinate their actions only by passing messages.
組件分布在網(wǎng)絡(luò)計算機上透葛,組件之間僅僅通過消息傳遞來通信并協(xié)調(diào)行動渡处。
理解:分布式系統(tǒng)一定是由多個節(jié)點組成的系統(tǒng)施无,一般來說一個節(jié)點就是一臺計算機,節(jié)點之間互連喘沿;最后這些節(jié)點部署了我們的組件闸度,并且相互之間的操作會有協(xié)同。
分布式系統(tǒng)的意義
- 升級單機處理能力的性價比越來越低蚜印;
- 單機處理能力存在瓶頸莺禁;
- 出于穩(wěn)定性和可用性考慮。
分布式系統(tǒng)基本知識
1.組成計算機的5要素
輸入設(shè)備窄赋、輸出設(shè)備哟冬、運算器楼熄、控制器、存儲器(內(nèi)存和外存)浩峡。計算機斷電時內(nèi)存的數(shù)據(jù)會丟失可岂,外存的數(shù)據(jù)依然存在。
2.線程與進程的執(zhí)行模式
明確:這里指的是單進程下的多線程翰灾。多線程開發(fā)中缕粹,我們需要處理線程間的通信,需要對線程并發(fā)做控制预侯,需要做好線程間的協(xié)調(diào)工作致开。
2.1阿姆達爾定律
P指的是程序中可并行部分的程序在單核上執(zhí)行時間的占比,N表示處理器的個數(shù)(總核心數(shù))萎馅。S(N)是指程序在N個處理器(總核心數(shù))相對在單個處理器(單核)中速度的提升比双戳。
這個公式告訴我們,程序中可并行的代碼比例決定你增加處理器(總核心數(shù))所能帶來的速度提升上限糜芳。例如當P=0.5,速度上限是2飒货。當P=0.2,速度上限是1.25∏涂ⅲ可見塘辅,在多核的時代,并發(fā)程序的開發(fā)多么重要皆撩。
2.2互不通信的多線程模式
在多線程程序中扣墩,多個線程會在系統(tǒng)中并發(fā)執(zhí)行。如果線程之間不需要處理共享的數(shù)據(jù)扛吞,也不需要動作協(xié)調(diào)呻惕,將會非常簡單,就是多個線程獨立完成自己線程的工作滥比。如圖:
2.3基于共享容器協(xié)同的多線程模式
多個線程之間對共享數(shù)據(jù)進行處理亚脆。如經(jīng)典的生產(chǎn)者和消費者例子,我們有一個隊列用于生產(chǎn)和消費盲泛,那么這個隊列就是多個線程會共享一個容器或者數(shù)據(jù)對象濒持,多個線程并發(fā)地訪問這個隊列。
對于這種在多線程環(huán)境下對統(tǒng)一數(shù)據(jù)的訪問寺滚,我們需要有所保護和控制以保證訪問的正確性柑营。對于存儲數(shù)據(jù)的容器或者對象,有線程安全和線程不安全之分村视,對于線程不安全的官套,一般通過加鎖或者通過Copy On Write的方式來控制并發(fā)訪問。使用加鎖的方式時,如果數(shù)據(jù)讀寫比例很高虏杰,一般采用讀寫鎖而非簡單的互斥鎖。對于線程安全的容器或?qū)ο罄障海梢栽诙嗑€程環(huán)境下直接使用纺阔。
注意:有時通過加鎖把使用線程不安全容器的代碼改為使用線程安全容器的代碼時,會遇到一個陷阱修然。
即在一個使用Map進行計數(shù)統(tǒng)計總數(shù)的例子中笛钝,map中value整型使用線程不安全容器HashMap是這樣的:
public class TestClass {
private HashMap<String, Integer> map = new HashMap<String, Integer>();
public void add(String key){
Integer value = map.get(key);
if (value == null) {
map.put(key, 1);
}else{
map.put(key, value + 1);
}
}
}
使用ConcurrentHashMap來替換HashMap,并且僅僅去掉Synchoronized關(guān)鍵字,問題出現(xiàn)了愕宋。(Java代碼如下)
public class TestClass {
private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
public void add(String key) {
Integer value = map.get(key);
if (value == null) {
map.put(key, 1);
} else {
map.put(key, value + 1);
}
}
}
我的理解是對ConcurrentHashMap的get和put方法是線程安全的玻靡,但這個計數(shù)過程是要求對整個過程(get值判斷再put)線程安全的,先get值判斷再put得操作不是原子的中贝,所以不線程安全囤捻,會造成計數(shù)小于實際。
2.4通過事件協(xié)同的多線程模式
除了并發(fā)控制訪問邻寿,線程間會存在協(xié)調(diào)的需求蝎土。例如A、B線程绣否,B線程需要等到某個狀態(tài)或事件發(fā)生后才能繼續(xù)自己的工作誊涯,而這個狀態(tài)發(fā)生或者改變和A線程相關(guān)。這個場景下蒜撮,需要完成線程間的協(xié)調(diào)暴构。
如上圖,右側(cè)線程執(zhí)行到某個步驟需要等待事件觸發(fā)段磨,這個事件由左側(cè)線程觸發(fā)并通知取逾。期間,右側(cè)線程一直阻塞直到事件通知后才繼續(xù)執(zhí)行薇溃。
同時還需要避免死鎖情況菌赖,一般能夠原子性的獲取需要的多個鎖,或者注意調(diào)整對多個鎖的獲取順序沐序,能有效避免死鎖琉用。
下面來看一個死鎖的例子:加鎖有兩個鎖A和B,兩個線程T1和T2策幼,兩個線程T1,T2某段代碼都需要獲取A鎖和B鎖邑时,偽代碼如下
T1代碼
……
A.lock();
B.lock();
……
T2代碼
……
B.lock();
A.lock();
……
這個時候T1等不到B,T2等不到A特姐。下面這種做法可以避免死鎖:
T1代碼
……
A.lock();
B.lock();
……
T2代碼
……
A.lock();
B.lock();
……
T2線程獲取鎖的順序發(fā)生變化晶丘,現(xiàn)在和T1一樣,都獲取A,再獲取B鎖浅浮。
還有另一種方式避免死鎖:
T1代碼
……
getLocks(A,B);
……
T2代碼
……
getLocks(A,B);
……
getLocks方法一次性獲取兩個鎖沫浆。
2.5 多進程模式
下面關(guān)注進程間的關(guān)系,多進程和多線程有許多相似之處滚秩。線程是屬于進程的专执,一個進程的多個線程共享內(nèi)存空間,而多個進程之間內(nèi)存空間是相互獨立的郁油,因此多個進程間通過內(nèi)存共享本股,交換數(shù)據(jù)的方式有所不同。進程間的通信桐腌,協(xié)調(diào)以及事件通知或者等待鎖釋放也與多線程不一樣拄显。
區(qū)別:多進程相對于單進程多線程的方式來說,資源控制更容易實現(xiàn)案站。多進程的單個進程錯誤不會導致系統(tǒng)整體不可用躬审。
分布式系統(tǒng)是多機組成的系統(tǒng),可以把它看做單機多線程變成了多機多線程嚼吞。多機系統(tǒng)帶來一個好處盒件,即當單個機器出現(xiàn)問題時,如果處理得好舱禽,就不會影響整體的集群炒刁。
3.網(wǎng)絡(luò)通信的基礎(chǔ)知識
3.1 OSI與TCP/IP模型
ISO的OSI七層網(wǎng)絡(luò)模型
OSI模型與TCP/IP模型對比
3.2網(wǎng)絡(luò)IO實現(xiàn)方式
接觸比較多的網(wǎng)絡(luò)模型主要是TCP/IP協(xié)議棧,UDP也在一些場景下用到誊稚。
當我們使用socket通信時翔始,有三種方式:BIO、NIO和AIO里伯。
BIO方式
BIO即Blokcing IO,采用阻塞的方式實現(xiàn)城瞎。即一個Socket通信需要一個線程處理。發(fā)生建立連接疾瓮、讀數(shù)據(jù)脖镀、寫數(shù)據(jù)的操作時,都可能會阻塞狼电。這個模式簡單蜒灰。但帶來主要問題是當作為Server端,一個線程只處理一個socket肩碟,在支持并發(fā)連接時强窖,需要更多的線程完成這個工作。
BIO的工作方式:
NIO方式
NIO即NonBlocking IO削祈,基于事件驅(qū)動思想翅溺,采用Reactor模式脑漫。Java實現(xiàn)的服務(wù)器系統(tǒng)較多采用。相對于BIO咙崎,NIO一個明顯的好處是不需要為每個socket套接字分配一個線程优幸,而可以在一個線程處理多個Socket套接字相關(guān)的工作。
上圖褪猛,Rector會管理所有的hanler,并且把出現(xiàn)的事件交給相應(yīng)的Handle處理劈伴。
通信中的應(yīng)用:在NIO模式下不是用單個線程去應(yīng)對單個Socket套接字,而是統(tǒng)一通過Reactor的所有客戶端的Socket套接字處理握爷,然后派發(fā)到不同線程中。這就解決了BIO為支撐更多Socket套接字而需要打開更多線程的問題严里。
AIO方式
AIO即AsynchronousIO新啼,即異步IO。通過Proactor模式刹碾。AIO與NIO不同之處在于燥撞,AIO在讀寫操作時,只需調(diào)用相應(yīng)的read/write方法迷帜,并且傳入ComletionHandler(動作完成處理器)物舒;
在動作完成后,會調(diào)用動作完成處理器戏锹。
AIO與NIO最大區(qū)別:NIO在有通知時可以進行讀或?qū)懝诳瑁鳤IO在有通知時表示相關(guān)操作已完成。
4.如何把應(yīng)用從單機擴展到分布式
輸入锦针、輸出荠察、運算、存儲奈搜、控制這五個方面組成計算機悉盆,分布式有何變化?
4.1輸入設(shè)備的變化
分布式系統(tǒng)由通過網(wǎng)絡(luò)連接的多個節(jié)點組成馋吗,一種是互相連接的多個節(jié)點焕盟,在接收其他節(jié)點傳來的信息時,該節(jié)點可以看做是輸入設(shè)備宏粤;另外一種就是傳統(tǒng)意義上的人機交互輸入設(shè)備脚翘。
4.2輸出設(shè)備變化
一種是指系統(tǒng)中的節(jié)點;另一種是用戶終端屏幕商架。
4.3控制器變化
單機系統(tǒng)中指的是CPU中的控制器堰怨。
分布式系統(tǒng)中是由多個節(jié)點通過網(wǎng)絡(luò)連接在一起并通過消息的傳遞進行協(xié)調(diào)的系統(tǒng)。
控制器的主要作用就是協(xié)調(diào)和控制節(jié)點之間的動作和行為蛇摸。
如上圖是一個遠程服務(wù)調(diào)用的場景备图。所有請求經(jīng)過中間負載均衡設(shè)備來完成請求轉(zhuǎn)發(fā)的控制,這就是一種控制的方式。
使用LVS請求調(diào)用揽涮,這種方式代價低抠藕,可控性強。一般稱之為透明代理蒋困。
在集群中盾似,這種方式對于請求發(fā)起方和處理請求的一方都是透明的。
這種方式有兩種不足:一方面增加網(wǎng)絡(luò)開銷(流量)雪标,延遲零院。數(shù)據(jù)量越大約明顯。由于中間的透明代理處于請求的必經(jīng)路徑上村刨,如果代理出現(xiàn)問題告抄,所有請求都會影響。
第三種方式
請求發(fā)起和處理方直連嵌牺,外部有一個名稱服務(wù)打洼。作用:一個收集提供請求處理的服務(wù)器處理地址信息;另外提供地址信息給請求發(fā)起方逆粹。起到地址交換作用募疮,在發(fā)起請求的機器上需要根據(jù)從名稱服務(wù)得到的服務(wù)器地址進行負載均衡的工作。原來透明代理上的工作被拆分到名稱服務(wù)上和請求發(fā)起方僻弹。
第四種-規(guī)則服務(wù)器請求直連
這種模式下阿浓,請求方和處理方也是直連的。那么請求方如何選擇處理方的機器呢蹋绽?規(guī)則服務(wù)器的規(guī)則搔扁。
在請求方機器,會有對規(guī)則進行處理的代碼邏輯蟋字。規(guī)則服務(wù)器只負責把規(guī)則提供給請求方稿蹲。
最后一種方式Master+Worker
Master管理任務(wù),分配給不同的Worker來做鹊奖。場景:任務(wù)分配和管理苛聘。
4.4運算器的變化
多臺服務(wù)器,由DNS服務(wù)器進行調(diào)度和控制忠聚,在用戶解析DNS時设哗,就會給予一個服務(wù)器地址。
另一種方案:負載均衡
中間增加負載均衡設(shè)備(純硬件或LVS)两蟀,DNS永遠返回負載均衡的地址网梢,而用戶訪問是通過負載均衡到達后面的服務(wù)器。
總結(jié)來說赂毯,構(gòu)成運算器的多個節(jié)點在控制器的配合下對外提供服務(wù)战虏,構(gòu)成分布式系統(tǒng)的運算器拣宰。
日志處理系統(tǒng)場景:
單日志系統(tǒng)
改進Master方式
4.5存儲器變化
分布式系統(tǒng)中需要把承擔存儲功能的多個節(jié)點組織在一起,使之看起來是一個存儲器烦感。
單機的Key-Value場景
改進:使用代理巡社。代理服務(wù)器作為控制器轉(zhuǎn)發(fā)來自應(yīng)用服務(wù)器的請求。一般可以根據(jù)Key來劃分手趣。
使用名稱服務(wù)的Key-Value服務(wù)
應(yīng)用服務(wù)器與KV服務(wù)器直接相連晌该。KV服務(wù)器的選擇邏輯放在應(yīng)用服務(wù)器上完成。實際實施中通過規(guī)則服務(wù)器配合绿渣;另外則是對等看待朝群,靈活適應(yīng)KV服務(wù)器的增加減少。
規(guī)則服務(wù)器不僅寫明了如何對數(shù)據(jù)做劃分中符,還包含了具體的KV服務(wù)器地址潜圃。
不同的是Master會根據(jù)請求返回目標的KV服務(wù)器地址,然后由應(yīng)用服務(wù)器直接請求KV服務(wù)器舟茶。KV存儲服務(wù)器選擇工作在MASTER完成。
應(yīng)用服務(wù)器只需根據(jù)Master返回的結(jié)果去訪問相應(yīng)的KV服務(wù)器即可堵第。具體應(yīng)用廣泛吧凉。
5.分布式系統(tǒng)難點
5.1缺乏全局時鐘
很多時候,我們使用時鐘可以區(qū)分2個動作的順序踏志,而不是一定要知道準確時間阀捅。這個工作交給單獨集群來完成,通過集群區(qū)分多個動作的順序针余。
5.2面對故障獨立性
5.3處理單點故障
整個分布式系統(tǒng)如果某個功能只有某臺單機支撐饲鄙,這個節(jié)點稱為單點。故障稱為單點故障圆雁。SPoF(Single Point of Failure).分布式系統(tǒng)中盡量避免單點忍级。
無法避免:
1.給單點備份,盡量做到遇到問題伪朽,自動恢復轴咱。
2.降低單點故障影響范圍。如:
5.4事務(wù)的挑戰(zhàn)
兩階段提交(2PC)烈涮、最終一致朴肺、BASE、CAP坚洽、Paxos等戈稿。至此簡要介紹了分布式系統(tǒng)中比較基本的偏實踐的知識。