分布式進階(十七)分布式設計介紹
分布式設計介紹

前言
分布式設計與開發在IDF05(Intel?Developer?Forum?2005)上,Intel首席執行官Craig?Barrett就取消4GHz芯片計劃一事,半開玩笑當眾單膝下跪致歉,給廣大軟件開發者一個明顯的信號,單純依靠垂直提升硬件性能來提高系統性能的時代已結束,分布式開發的時代實際上早已悄悄地成為了時代的主流,吵得很熱的云計算實際上只是包裝在分布式之外的商業概念,很多開發者(包括我)都想加入研究云計算這個潮流,在google上通過“云計算”這個關鍵詞來查詢資料,查到的都是些概念性或商業性的宣傳資料,其實真正需要深入的還是那個早以被人熟知的概念------分布式。
分布式可繁也可以簡,最簡單的分布式就是大家最常用的,在負載均衡服務器后加一堆web服務器,然后在上面搞一個緩存服務器來保存臨時狀態,后面共享一個數據庫,其實一致性Hash算法本身比較簡單,不過可以根據實際情況有很多改進的版本,其目的無非是兩點:
1.節點變動后其他節點受影響盡可能小
2.節點變動后數據重新分配盡可能均衡
實現這個算法就技術本身來說沒多少難度和工作量,需要做的是建立起你所設計的映射關系,無需借助什么框架或工具,sourceforge上倒是有個項目libconhash,可以參考一下。
以上兩個算法在我看來就算從不涉及算法的開發人員也需要了解的,算法其實就是一個策略,而在分布式環境常常需要我們設計一個策略來解決很多無法通過單純的技術搞定的難題,學習這些算法可以提供我們一些思路。分布式環境中大多數服務是允許部分失敗,也允許數據不一致,但有些最基礎的服務是需要高可靠性,高一致性的,這些服務是其他分布式服務運轉的基礎,比如naming?service、分布式lock等,這些分布式的基礎服務有以下要求:
高可用性 ?高一致性 ?高性能
對于這種有些挑戰CAP原則的服務該如何設計,是一個挑戰,也是一個不錯的研究課題,Apache的ZooKeeper也許給了我們一個不錯的答案。ZooKeeper是一個分布式的,開放源碼的分布式應用程序協調服務,它暴露了一個簡單的原語集,分布式應用程序可以基于它實現同步服務,配置維護和命名服務等。關于ZooKeeper更多信息可以參見官方文檔。
ZooKeeper的基本使用
搭一個分布式的ZooKeeper環境比較簡單,基本步驟如下:
1)在各服務器安裝ZooKeeper下載ZooKeeper后在各服務器上進行解壓即可
tar?-xzf?zookeeper-3.2.2.tar.gz
2)配置集群環境
分別在各服務器的zookeeper安裝目錄下創建名為zoo.cfg的配置文件,內容填寫如下:
#?The?number?of?milliseconds?of?each?tick
tickTime=2000
#?The?number?of?ticks?that?the?initial
#?synchronization?phase?can?take
initLimit=10
#?The?number?of?ticks?that?can?pass?between
#?sending?a?request?and?getting?an?acknowledgement
syncLimit=5
#?the?directory?where?the?snapshot?is?stored.
dataDir=/home/admin/zookeeper-3.2.2/data
#?the?port?at?which?the?clients?will?connect
clientPort=2181
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
其中zoo1和zoo2分別對應集群中各服務器的機器名或ip,server.1和server.2中1和2分別對應各服務器的zookeeper?id,id的設置方法為在dataDir配置的目錄下創建名為myid的文
件,并把id作為其文件內容即可,在本例中就分為設置為1和2。其他配置具體含義可見官方文檔。
3)啟動集群環境分別在各服務器下運行zookeeper啟動腳本/home/admin/zookeeper-3.2.2/bin/zkServer.sh?start
4)應用zookeeper
應用zookeeper可以在是shell中執行命令,也可以在java或c中調用程序接口。
在shell中執行命令,可運行以下命令:
bin/zkCli.sh?-server?10.20.147.35:2181
其中10.20.147.35為集群中任一臺機器的ip或機器名。執行后可進入zookeeper的操作面板,具體如何操作可見官方文檔
在java中通過調用程序接口來應用zookeeper較為復雜一點,需要了解watch和callback等概念,不過試驗最簡單的CURD倒不需要這些,只需要使用ZooKeeper這個類即可,具體測試代碼如下:
Public?static?void?main(String[]?args)?{
try?{
ZooKeeper?zk?=?new?ZooKeeper("10.20.147.35:2181",?30000,?null);
String?name?=?zk.create("/company",?"alibaba".getBytes(),
Ids.OPEN_ACL_UNSAFE,?CreateMode.PERSISTENT_SEQUENTIAL);
Stat?stat?=?new?Stat();
System.out.println(new?String(zk.getData(name,?null,?stat)));
zk.setData(name,?"taobao".getBytes(),?stat.getVersion(),?null,?null);
System.out.println(new?String(zk.getData(name,?null,?stat)));
stat?=?zk.exists(name,?null);
zk.delete(name,?stat.getVersion(),?null,?null);
System.out.println(new?String(zk.getData(name,?null,?stat)));
}?catch?(Exception?e)?{
e.printStackTrace();
}
}
以上代碼比較簡單,查看一下zooKeeper的api?doc就知道如何使用了。
ZooKeeper的實現機理
ZooKeeper的實現機理是我看過的開源框架中最復雜的,它解決的是分布式環境中的一致性問題,這個場景也決定了其實現的復雜性。看了兩三天的源碼還是有些摸不著頭腦,有些超出了我的能力,不過通過看文檔和其他高人寫的文章大致清楚它的原理和基本結構。
1)ZooKeeper的基本原理
ZooKeeper是以Fast?Paxos算法為基礎的,在前一篇blog中大致介紹了一下paxos,而沒有提到的是paxos存在活鎖的問題,也就是當有多個proposer交錯提交時,有可能互相排斥導致沒有一個proposer能提交成功,而Fast?Paxos作了一些優化,通過選舉產生一個leader,只有leader才能提交propose,具體算法可見Fast?Paxos。因此要想弄懂ZooKeeper首先得對Fast?Paxos有所了解。
2)ZooKeeper的基本運轉流程
ZooKeeper主要存在以下兩個流程:
選舉Leader
同步數據
選舉Leader過程中算法有很多,但要達到的選舉標準是一致的:
1.Leader要具有最高的zxid
2.集群中大多數的機器得到響應并follow選出的Leader同步數據這個流程是ZooKeeper的精髓所在,并且就是Fast?Paxos算法的具體實現。一個牛人畫了一個ZooKeeper數據流動圖,比較直觀地描述了ZooKeeper是如何同步數據的。
以上兩個核心流程我暫時還不能悟透其中的精髓,這也和我還沒有完全理解Fast?Paxos算法有關,有待后續深入學習。
ZooKeeper的應用領域
Tim在blog中提到了Paxos所能應用的幾個主要場景,包括database?replication、naming?service、config配置管理、access?control?list等等,這也是ZooKeeper可以應用的幾個主要場景。此外,ZooKeeper官方文檔中提到了幾個更為基礎的分布式應用,這也算是ZooKeeper的妙用吧。
1)分布式Barrier
Barrier是一種控制和協調多個任務觸發次序的機制,簡單說來就是搞個閘門把欲執行的任務給攔住,等所有任務都處于可以執行的狀態時,才放開閘門。
在單機上JDK提供了CyclicBarrier這個類來實現這個機制,但在分布式環境中JDK就無能為力了。在分布式里實現Barrer需要高一致性做保障,因此ZooKeeper可以派上用場,所采取的方案就是用一個Node作為Barrer的實體,需要被Barrer的任務通過調用exists()檢測這個Node的存在,當需要打開Barrier的時候,刪掉這個Node,ZooKeeper的watch機制會通知到各個任務可以開始執行。
2)分布式Queue
與Barrier類似,分布式環境中實現Queue也需要高一致性做保障,ZooKeeper提供了一個種簡單的方式,ZooKeeper通過一個Node來維護Queue的實體,用其children來存儲Queue的內容,并且ZooKeeper的create方法中提供了順序遞增的模式,會自動地在name后面加上一個遞增的數字來插入新元素。可以用其children來構建一個queue的數據結構,offer的時候使用create,take的時候按照children的順序刪除第一個即可。ZooKeeper保障了各個server上數據是一致的,因此也就實現了一個分布式Queue。take和offer的實例代碼如下所示:
/**
*?Removes?the?head?of?the?queue?and?returns?it,?blocks?until?it?succeeds.
*?@return?The?former?head?of?the?queue
*?@throws?NoSuchElementException
*?@throws?KeeperException
*?@throws?InterruptedException
*/
Public?byte[]?take()?throws?KeeperException,?InterruptedException?{
TreeMap
//?Same?as?for?element.??Should?refactor?this.
while(true){
LatchChildWatcher?childWatcher?=?new?LatchChildWatcher();
try{
orderedChildren?=?orderedChildren(childWatcher);
}catch(KeeperException.NoNodeException?e){
zookeeper.create(dir,?new?byte[0],?acl,?CreateMode.PERSISTENT);
continue;
}
if(orderedChildren.size()?==?0){
childWatcher.await();
continue;
}
for(String?headNode?:?orderedChildren.values()){
String?path?=?dir?+"/"+headNode;
try{
byte[]?data?=?zookeeper.getData(path,?false,?null);
zookeeper.delete(path,?-1);
return?data;
}catch(KeeperException.NoNodeException?e){
//?Another?client?deleted?the?node?first.
}
}
}
}
/**
*?Inserts?data?into?queue.
*?@param?data
*?@return?true?if?data?was?successfully?added
*/
public?boolean?offer(byte[]?data)?throws?KeeperException,?InterruptedException{
for(;;){
try{
zookeeper.create(dir+"/"+prefix,data,acl,CreateMode.PERSISTENT_SEQUENTIAL);
return?true;
}catch(KeeperException.NoNodeException?e){
zookeeper.create(dir,?newbyte[0],?acl,?CreateMode.PERSISTENT);
}
}
}
3)分布式lock
利用ZooKeeper實現分布式lock,主要是通過一個Node來代表一個Lock,當一個client去拿鎖的時候,會在這個Node下創建一個自增序列的child,然后通過getChildren()方式來
check創建的child是不是最靠前的,如果是則拿到鎖,否則就調用exist()來check第二靠前的child,并加上watch來監視。當拿到鎖的child執行完后歸還鎖,歸還鎖僅僅需要刪除
自己創建的child,這時watch機制會通知到所有沒有拿到鎖的client,這些child就會根據前面所講的拿鎖規則來競爭鎖。
一個大型系統里各個環節中最容易出性能和可用性問題的往往是數據庫,因此分布式設計與開發的一個重要領域就是如何讓數據層具有可擴展性,數據庫的擴展分為Scale?Up?和Scale?Out,而Scale?Up說白了是通過升級服務器配置來完成,因此不在分布式設計的考慮之內。Scale?Out是通過增加機器的方式來提升處理能力,一般需要考慮以下兩個問題:
1.數據拆分
2.數據庫高可用架構
數據拆分是最先會被想到的,原理很簡單,當一個表的數據達到無法處理的時候,就需要把它拆成多個表,說起來簡單,真正在項目里運用的時候有很多點是需要深入研究的,一般分為:切分策略與應用程序端的整合策略。
切分策略
切分策略一般分為垂直切分、橫向切分和兩者的混搭。
1)垂直切分
垂直切分就是要把表按模塊劃分到不同數據庫中,這種拆分在大型網站的演變過程中是很常見的。當一個網站還在很小的時候,只有小量的人來開發和維護,各模塊和表都在一起,當網站不斷豐富和壯大的時候,也會變成多個子系統來支撐,這時就有按模塊和功能把表劃分出來的需求。其實,相對于垂直切分更進一步的是服務化改造,說得簡單就是要把原來強耦合的系統拆分成多個弱耦合的服務,通過服務間的調用來滿足業務需求看,因此表拆出來后要通過服務的形式暴露出去,而不是直接調用不同模塊的表,淘寶在架構不斷演變過程,最重要的一環就是服務化改造,把用戶、交易、店鋪、寶貝這些核心的概念抽取成獨立的服務,也非常有利于進行局部的優化和治理,保障核心模塊的穩定性。這樣一種拆分方式也是有代價的:表關聯無法在數據庫層面做單表大數據量依然存在性能瓶頸事務保證比較復雜應用端的復雜性增加上面這些問題是顯而易見的,處理這些的關鍵在于如何解除不同模塊間的耦合性,這說是技術問題,其實更是業務的設計問題,只有在業務上是松耦合的,才可能在技術設計上隔離開來。沒有耦合性,也就不存在表關聯和事務的需求。另外,大數據瓶頸問題可以參見下面要講的水平切分。
2)水平切分
上面談到垂直切分只是把表按模塊劃分到不同數據庫,但沒有解決單表大數據量的問題,而水平切分就是要把一個表按照某種規則把數據劃分到不同表或數據庫里。例如像計費系統,通過按時間來劃分表就比較合適,因為系統都是處理某一時間段的數據。而像SaaS(software?as?a?service)應用,通過按用戶維度來劃分數據比較合適,因為用戶與用戶之間的隔離的,一般不存在處理多個用戶數據的情況。水平切分沒有破壞表之間的聯系,完全可以把有關系的表放在一個庫里,這樣就不影響應用端的業務需求,并且這樣的切分能從根本上解決大數據量的問題。它的問題也是很明顯的:當切分規則復雜時,增加了應用端調用的難度數據維護難度比較大,當拆分規則有變化時,需要對數據進行遷移。對于第一個問題,可以參考后面要講的如何整合應用端和數據庫端。對于第二個問題可以參考一致性hash的算法,通過某些映射策略來降低數據維護的成本。
布式設計與開發(二)------幾種必須了解的分布式算法
3)垂直與水平聯合切分
由上面可知垂直切分能更清晰化模塊劃分,區分治理,水平切分能解決大數據量性能瓶頸問題,因此常常就會把兩者結合使用,這在大型網站里是種常見的策略,這可以結合兩者的優點,當然缺點就是比較復雜,成本較高,不太適合小型網站,下面是結合前面兩個例子的情況:與應用程序端的整合策略數據切出來還只是第一步,關鍵在于應用端如何方便地存取數據,不能因為數據拆分導致應用端存取數據錯誤或者異常復雜。按照從前往后一般說來有以下三種策略:
應用端做數據庫路由
在應用端和服務器端加一個代理服務器做路由
數據庫端自行做路由
1)應用端做數據庫路由
應用端做數據庫路由實現起來比較簡單,也就是在數據庫調用的點通過工具包的處理,給每次調用數據庫加上路由信息,也就是分析每次調用,路由到正確的庫。這種方式多多少少沒有對應用端透明,如果路由策略有更改還需要修改應用端,并且這種更改很難做到動態更改。最關鍵的是應用端的連接池設計會比較復雜,池里的連接就不是無狀態了,不利于管理和擴展。
2)在應用端和服務器端加一個代理服務器做路由
通過代理服務器來做服務器做路由可以對客戶端屏蔽后端數據庫拆分細節,增強了拆分規則的可維護性,一般而言proxy需要提供以下features:
對客戶端和數據庫服務端的連接管理和安全認證
數據庫請求路由可配置性
對調用命令和SQL的解析
調用結果的過濾和合并
現在有些開源框架提供了類似功能,比如ameoba,在以前博文設計與開發應用服務器(一)------常見模式中介紹過ameoba的大致結構,在構建高性能web之路------mysql讀寫分離實戰介紹過如何實戰ameoba,有興趣的朋友可以參考一下。
3)數據庫端自行做路由
例如MySQL就提供了MySQL?Proxy的代理產品可以在數據庫端做路由。這種方式的最大問題就是拆分規則配置的靈活性不好,不一定能滿足應用端的多種劃分需求。
以上介紹了些數據拆分的策略和相關支撐策略,隨后會研究一下前面談到的數據庫高可用架構。
美圖美文
ZooKeeper 分布式 華為開源鏡像站 Mirrors 數據庫
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。