fourinone分布式協(xié)調(diào)設(shè)計解析
分布式協(xié)同是分布式應(yīng)用中不可缺少的,通常擔(dān)任協(xié)調(diào)者的角色,或者說是將多機(jī)協(xié)同的職責(zé)從分布式應(yīng)用中獨(dú)立出來,以減少系統(tǒng)的耦合性和增強(qiáng)擴(kuò)充性。Apache的Zookeeper, google的Chubby都是分布式協(xié)同的實現(xiàn)者。fourinone實際上可以單獨(dú)當(dāng)做Zookeeper用,它使用最少的代碼實現(xiàn)了Zookeeper的所有功能,并且力圖做到功能更強(qiáng)但是使用更簡潔。
一、實現(xiàn)原理
fourinone對分布式協(xié)同的實現(xiàn), 是通過建立一個domain,node兩層結(jié)構(gòu)的節(jié)點(diǎn)信息去完成,domain可以是分類或者包,node可以是具體屬性,domain和node都是自己根據(jù)需求設(shè)計命名,比如可以將domain命名為“a.b.c...”表示一個樹型類目。
一個domain下可以有很多個node,每個node只指定一個domain,可以通過domain返回它下面所有的node。
domain不需要單獨(dú)建立,通常在建立node時,如果不存在domain會自動創(chuàng)建。
如果domain下沒有node了,該domain會自動刪除。
如果刪除domain,該domain下面node也都會刪除。
每個node下可以存放一個值,可以是任意對象。
所有的節(jié)點(diǎn)信息存放在parkserver里,parkserver提供協(xié)同者的功能。如下圖所示:

從上圖可以看到,其他分布式進(jìn)程可以通過parkserver的用戶接口ParkLocal,對節(jié)點(diǎn)進(jìn)行增加、修改、刪除、指定心跳、指定權(quán)限等操作,并且結(jié)合parkserver提供同步備份、領(lǐng)導(dǎo)者選舉、過期時間設(shè)置等功能,共同來實現(xiàn)眾多分布式協(xié)同功能,比如:
1、分布式配置,多個機(jī)器的應(yīng)用公用一個配置信息,并且掛掉能夠領(lǐng)導(dǎo)者選舉,詳細(xì)見指南和demo
2、分布式鎖,多個機(jī)器競爭一個鎖,當(dāng)某個機(jī)器釋放鎖或者掛掉,其他機(jī)器可以競爭到鎖繼續(xù),詳細(xì)見指南和demo
3、集群管理,集群內(nèi)機(jī)器可以互相感知和領(lǐng)導(dǎo)者選舉,詳見指南和demo #p#
二、核心API
ParkLocal核心api說明:
//創(chuàng)建node,可以根據(jù)是否需要權(quán)限和心跳屬性調(diào)用不同方法
public ObjectBean create(String domain, Serializable obj);//自動創(chuàng)建node
public ObjectBean create(String domain, String node, Serializable obj);
public ObjectBean create(String domain, String node, Serializable obj, AuthPolicy auth);
public ObjectBean create(String domain, String node, Serializable obj, boolean heartbeat);
public ObjectBean create(String domain, String node, Serializable obj, AuthPolicy auth, boolean heartbeat);
//更新node
public ObjectBean update(String domain, String node, Serializable obj);
//獲取node
public ObjectBean get(String domain, String node);
//獲取最新node,需要傳入舊node進(jìn)行對照
public ObjectBean getLastest(String domain, String node, ObjectBean ob);
//獲取最新domain
public List
//獲取最新domain下所有node,需要傳入舊的node集合對照
public List
//刪除node
public ObjectBean delete(String domain, String node);
//強(qiáng)行設(shè)置domain可刪除
public boolean setDeletable(String domain);
//刪除domain及下所有node
public List
//添加node的事件監(jiān)聽
public void addLastestListener(String domain, String node, ObjectBean ob, LastestListener liser);
//添加domain的事件監(jiān)聽
public void addLastestListener(String domain, List
三、權(quán)限機(jī)制:
public ObjectBean create(String domain, String node, Serializable obj, AuthPolicy auth);
通過上面方法創(chuàng)建node時,可以指定一個權(quán)限參數(shù),有只讀(AuthPolicy.OP_READ)、讀寫(AuthPolicy.OP_READ_WRITE)、所有(AuthPolicy.OP_ALL)三種屬性,默認(rèn)為AuthPolicy.OP_ALL
注意:這里的權(quán)限屬性是指創(chuàng)建進(jìn)程對其他使用進(jìn)程的權(quán)限約束,而不包括它自己。也就是對node的創(chuàng)建進(jìn)程來說,它擁有對該node和domain所有操作權(quán)限(讀寫刪,只要它不退出或者中止)
建設(shè)現(xiàn)在創(chuàng)建了一個domain為d,node為n的節(jié)點(diǎn), 對于其他使用進(jìn)程來說,操作權(quán)限如下表所示:
|
權(quán)限\其他進(jìn)程 |
讀(get)n | 寫(update)n | 刪(delete)n | 刪(delete)d |
|
AuthPolicy.OP_READ |
Yes | No | No | No |
|
AuthPolicy.OP_READ_WRITE |
Yes | Yes | No | No |
|
AuthPolicy.OP_ALL |
Yes | Yes | Yes | No |
從上表可以發(fā)現(xiàn),當(dāng)創(chuàng)建進(jìn)程指定node的權(quán)限為AuthPolicy.OP_ALL時,其他使用進(jìn)程可以刪除該node,但是不能刪除其domain,這是為什么呢?
因為domain下通常還有其他node,它們的權(quán)限并不都是AuthPolicy.OP_ALL,比如還有一個n1的node權(quán)限為AuthPolicy.OP_READ,按照正常操作,該使用進(jìn)程無法刪除n1,假設(shè)它可以刪除domain,那么它最后間接刪除了n1,于是發(fā)生了悖論,因此,為了避免風(fēng)險,所有的使用進(jìn)程只能根據(jù)權(quán)限刪除node,但是無法刪除domain。
從上表可以發(fā)現(xiàn),當(dāng)創(chuàng)建進(jìn)程指定node的權(quán)限為AuthPolicy.OP_ALL時,其他使用進(jìn)程可以刪除該node,但是不能刪除其domain,這是為什么呢?
不過你允許承擔(dān)這樣的刪除風(fēng)險,也可以在創(chuàng)建進(jìn)程里強(qiáng)行指定該domain可刪除,通過在domain創(chuàng)建后,調(diào)用:
public boolean setDeletable(String domain);
該方法只能被domain的創(chuàng)建進(jìn)程調(diào)用,其他使用進(jìn)程沒有權(quán)限調(diào)用。
強(qiáng)行指定可刪除后,其他進(jìn)程可以直接刪除該domain及所含node并忽略后果。 #p#
四、相對于zookeeper的優(yōu)勢
Zookeeper無疑是一款成功的開源產(chǎn)品,并擁有廣泛的信任者和應(yīng)用場景,和以往一樣,老外作者在apache網(wǎng)站上發(fā)布了一款產(chǎn)品,我們的工程師馬上會虛心的學(xué)習(xí)和忠心的捍衛(wèi),而國產(chǎn)原創(chuàng)的產(chǎn)品往往會遭到百般質(zhì)疑,因為我們的原創(chuàng)更多是抄襲和粗制濫造,我們的國產(chǎn)更多是框架集成而不是架構(gòu)設(shè)計,所以這種情感上的傾向性不是一天能改變。
做產(chǎn)品對比和列舉優(yōu)勢往往容易引起激烈爭論,會被認(rèn)為是在宣傳和引導(dǎo)產(chǎn)品使用,實際上在都能滿足功能需求的情況下,選擇使用哪款產(chǎn)品更多的是個政治問題,而不是技術(shù)問題,領(lǐng)導(dǎo)意志及工程師本身的熟悉程度和愛好等等都是決定因素。
這里我們僅僅從技術(shù)角度闡述幾點(diǎn)優(yōu)勢,Zookeeper做為一個chubby和paxos模仿品,缺乏創(chuàng)新型的設(shè)計改進(jìn),它仍然存在以下缺點(diǎn):
1、樹型配置節(jié)點(diǎn)的繁瑣復(fù)雜,性能低下。為了保證這種結(jié)構(gòu),Zookeeper需要維持一套虛擬文件結(jié)構(gòu)的開銷,對于目錄結(jié)構(gòu)深的樹節(jié)點(diǎn),造成性能影響,而配置信息結(jié)構(gòu)實際上往往不一定需要樹結(jié)構(gòu)。
2、“觀察”(watch)機(jī)制的僵化設(shè)計:zookeeper沒有獲取最新版本信息的方法支持,它只能粗暴的在每次寫入更新等方法時注冊一個watch,當(dāng)這些方法被調(diào)用后就回調(diào),它不考慮信息內(nèi)容是否變化,對于沒有使信息內(nèi)容發(fā)生改變的更新,zookeeper仍然會回調(diào),并且zookeeper的回調(diào)比較呆板,它只能用一次,如果信息持續(xù)變化,必須又重新注冊watch。而fourinone的事件處理則可以自由控制是否持續(xù)響應(yīng)信息變化。
3、領(lǐng)導(dǎo)者選舉機(jī)制實現(xiàn)的太過局限,集群只有兩個節(jié)點(diǎn),zookeeper無法進(jìn)行領(lǐng)導(dǎo)者選舉,zookeeper的領(lǐng)導(dǎo)者選舉必須要奇數(shù)節(jié)點(diǎn)的奇怪限制。另外,ZooKeeper的領(lǐng)導(dǎo)者選舉實現(xiàn)雖然比原始的Paxos要簡化,但是它仍然存在領(lǐng)導(dǎo)者(Leader)、跟隨者(Follower)、觀察者(observer)、學(xué)習(xí)者 (Learner)等眾多角色和跟隨狀態(tài)(Following)、尋找狀態(tài)(Looking)、觀察狀態(tài)(Observing)、領(lǐng)導(dǎo)狀態(tài) (Leading)等復(fù)雜狀態(tài)。相對于fourinone的領(lǐng)導(dǎo)者選舉,zookeeper仍然不夠直觀簡潔,難以用較少配置和代碼演示。
4、Windows系統(tǒng)上幾乎不支持,需要安裝linux殼,并且僅建議用于學(xué)習(xí)研究。Fourinone支持windows、linux集群混合使用。
Fourinone提出一種新的分布式協(xié)同系統(tǒng)設(shè)計,在滿足zookeeper所有功能下,并克服了以上缺點(diǎn),提出了新的配置結(jié)構(gòu)、變化事件機(jī)制、簡化的領(lǐng)導(dǎo)者選舉實現(xiàn),能更好的滿足分布式協(xié)調(diào)需求。 #p#
五、演示demo
下面是一個操作節(jié)點(diǎn)的演示demo,請留意各自節(jié)點(diǎn)的權(quán)限范圍,程序說明:
1、 ParkServerDemo: 啟動parkserver(它的IP端口已經(jīng)在配置文件的PARK部分的SERVERS指定
2、 ParkSet:往parkserver里創(chuàng)建了d1n1、d2n2、d3n3、d4n4共4個節(jié)點(diǎn),分別對應(yīng)只讀、讀寫,所有,所有+強(qiáng)行刪除權(quán)限
3、 ParkGet:依次對d1n1、d2n2、d3n3、d4n4進(jìn)行讀、寫、刪除、刪除domain操作,觀察結(jié)果輸出,如果沒有權(quán)限操作,parkserver會輸出信息,并且操作返回的結(jié)果對象為空
啟動命令和順序:
Javac –classpath fourinone.jar; *.java
Java –classpath fourinone.jar; ParkServerDemo
Java –classpath fourinone.jar; ParkSet
Java –classpath fourinone.jar; ParkGet
如果沒有fourinone.jar,可以到以下地址下載:
http://www.skycn.com/soft/68321.html
下面是demo源碼:
// ParkServerDemo
import com.fourinone.BeanContext;
public class ParkServerDemo{
public static void main(String[] args){
BeanContext.startPark();
}
}
// ParkSet
import com.fourinone.BeanContext;
import com.fourinone.ParkLocal;
import com.fourinone.ObjectBean;
import com.fourinone.AuthPolicy;
public class ParkSet{
public static void main(String[] args){
//獲取parkserver用戶接口
ParkLocal pl = BeanContext.getPark();
//在domain d1下創(chuàng)建節(jié)點(diǎn)node n1,指定權(quán)限為只讀
ObjectBean d1n1 = pl.create("d1","n1","v1",AuthPolicy.OP_READ);
if(d1n1!=null)
System.out.println("d1n1 with AuthPolicy.OP_READ create success!");
//在domain d2下創(chuàng)建節(jié)點(diǎn)node n2,指定權(quán)限為讀寫
ObjectBean d2n2 = pl.create("d2","n2","v2",AuthPolicy.OP_READ_WRITE);
if(d2n2!=null)
System.out.println("d2n2 with AuthPolicy.OP_READ_WRITE create success!");
//在domain d3下創(chuàng)建節(jié)點(diǎn)node n3,指定權(quán)限為所有
ObjectBean d3n3 = pl.create("d3","n3","v3",AuthPolicy.OP_ALL);
if(d3n3!=null)
System.out.println("d3n3 with AuthPolicy.OP_ALL create success!");
//在domain d4下創(chuàng)建節(jié)點(diǎn)node n4,指定權(quán)限為所有,并且創(chuàng)建完成強(qiáng)行設(shè)置為其他進(jìn)程可刪除
ObjectBean d4n4 = pl.create("d4","n4","v4",AuthPolicy.OP_ALL);
if(d4n4!=null)
System.out.println("d4n4 with AuthPolicy.OP_ALL create success!");
boolean r = pl.setDeletable("d4");
if(r)
System.out.println("set d4 deletable!");
}
}
// ParkGet
import com.fourinone.BeanContext;
import com.fourinone.ParkLocal;
import com.fourinone.ObjectBean;
import java.util.List;
public class ParkGet{
public static void main(String[] args){
//獲取parkserver用戶接口
ParkLocal pl = BeanContext.getPark();
//獲取節(jié)點(diǎn)d1n1,節(jié)點(diǎn)權(quán)限為AuthPolicy.OP_READ
ObjectBean d1n1 = pl.get("d1","n1");//獲取節(jié)點(diǎn)
System.out.println("get d1n1:"+(String)d1n1.toObject());
d1n1 = pl.update("d1","n1","v1-update");//更新節(jié)點(diǎn)
if(d1n1!=null)
System.out.println("update node d1n1 success!");
else
System.out.println("update node d1n1 failure!");
List
if(d1!=null)
System.out.println("delete domain d1 success!");
else
System.out.println("delete domain d1 failure!");
d1n1 = pl.delete("d1","n1");//刪除節(jié)點(diǎn)
if(d1n1!=null)
System.out.println("delete node d1n1 success!");
else
System.out.println("delete node d1n1 failure!");
//獲取節(jié)點(diǎn)d2n2,節(jié)點(diǎn)權(quán)限為AuthPolicy.OP_READ_WRITE
ObjectBean d2n2 = pl.get("d2","n2");
System.out.println("get d2n2:"+(String)d2n2.toObject());
d2n2 = pl.update("d2","n2","v2-update");
if(d2n2!=null)
System.out.println("update node d2n2 success!");
else
System.out.println("update node d2n2 failure!");
List
if(d2!=null)
System.out.println("delete domain d2 success!");
else
System.out.println("delete domain d2 failure!");
d2n2 = pl.delete("d2","n2");
if(d2n2!=null)
System.out.println("delete node d2n2 success!");
else
System.out.println("delete node d2n2 failure!");
//獲取節(jié)點(diǎn)d3n3, 節(jié)點(diǎn)權(quán)限為AuthPolicy.OP_ALL
ObjectBean d3n3 = pl.get("d3","n3");
System.out.println("get d3n3:"+(String)d3n3.toObject());
d3n3 = pl.update("d3","n3","v3-update");
if(d3n3!=null)
System.out.println("update node d3n3 success!");
else
System.out.println("update node d3n3 failure!");
List
if(d3!=null)
System.out.println("delete domain d3 success!");
else
System.out.println("delete domain d3 failure!");
d3n3 = pl.delete("d3","n3");
if(d3n3!=null)
System.out.println("delete node d3n3 success!");
else
System.out.println("delete node d3n3 failure!");
//獲取節(jié)點(diǎn)d4n4,節(jié)點(diǎn)權(quán)限為AuthPolicy.OP_ALL
ObjectBean d4n4 = pl.get("d4","n4");
System.out.println("get d4n4:"+(String)d4n4.toObject());
d4n4 = pl.update("d4","n4","v4-update");
if(d4n4!=null)
System.out.println("update node d4n4 success!");
else
System.out.println("update node d4n4 failure!");
//由于創(chuàng)建進(jìn)程已經(jīng)強(qiáng)行指定該domain可刪除setDeletable(d4),因此這里可以刪除掉
List
if(d4!=null)
System.out.println("delete domain d4 success!");
else
System.out.println("delete domain d4 failure!");
d4n4 = pl.delete("d4","n4");//這里刪除節(jié)點(diǎn)會失敗,因為上面已經(jīng)刪除了該domian下所有節(jié)點(diǎn)
if(d4n4!=null)
System.out.println("delete node d4n4 success!");
else
System.out.println("delete node d4n4 failure!");
}
}




























