標籤:
一、序言
近幾個月一直從事一個分布式非同步通訊系統,今天就整理並blog一下.
這是一個全國性的通訊平台,對效能,海量資料,容錯性以及擴充性有非常高的要求,所以在系統的架構上就不能簡單的採用集中式.簡單的總結一下就是:
1.資料分布式儲存
2.請求分布式調度
3.多結點分布式部署
4.雙重備份,熱切換
系統的核心無非就是網路架構,分布式運算元和通訊,要求如下:
分布式運算元:
1.對於任意輸入,輸出均勻分布
2.輸出結果數可控
通訊:
1.高並發量
2.多線程
分布式運算元我們選擇的是sun公司的hash函數,通訊用的則是cindy socket通訊.網路架構以及具體的描述會在後面的blog中逐步給出.
二、網路架構
整個系統的架構,包括四層,每一層可以由若干結點來對資料和請求分流:
1.介面伺服器(Interface Server):
1).對外提供提供者並接受請求,考慮到HTTP的廣泛性,一般內建一個http伺服器處理序
2).監控各dispatcher server的工作狀態
3).轉寄請求到其中的一個最優dispatcher中,這裡的最優性判斷以各dispatcher server的工作狀態為依據,當然在這一層上不心請求的具體內容可以簡單地採用輪詢或隨機演算法.
2.訊息散發者(Dispatcher Server):
1).接受來自於介面伺服器的請求
2).解析請求,提取特徵參數(一般是類似於使用者帳號之類的東西,一個帳號下的資料會被分布到同一個結點上),然後對該參數執行hash函數,計算出目標資料所在的App Server,然後將請求轉寄給該App Server.
3).事實上,在實際的項目中的處理比上面的介紹要更複雜一些,但伸縮性大大加強了.
3.應用伺服器(App Server):
1).執行商務邏輯,等同於集中式系統中的應用伺服器,已經不存在分布式的特徵了.所處理的資料就是自己資料庫中的資料,與網路上的其他結點無關.
2).被劃分為多個邏輯組(group),同一個組中的伺服器負載平衡
3).考慮到資料庫的雙重備份,熱切換和負載平衡,才用了多資料庫單讀多寫策略.對於讀,監控各資料庫工作狀態,選擇一個最優資料庫來提供資料;對於寫,同時寫所有的資料庫,因此必須保證操作的事務性.
4.資料庫伺服器(DB Server):
1).提供資料訪問,沒什麼好說的,對於非事務性資料庫需要在App Server層提供輔助措施;
5.結點之間的通訊
1).資料(請求,響應,異常)以網路格式非同步並發傳輸
三、分布式演算法
介面伺服器(Interface Server)和訊息散發者(Dispatcher Server)在分發請求的策略上有所不同.
輔助函數和變數:
public String[] getTargetServerIps();//目標伺服器的ip,如193.243.15.45:8080
public int[] getTargetServerIds();//目標伺服器ID,與上述伺服器ip一一對應,可以自由配置
public boolean isServerWorking(index);//判斷目標伺服器的狀態
int currentTargetServerIndex=0;//當前的目標伺服器在targetServerIds中的index
介面伺服器(Interface Server)採用輪詢演算法:
public String getTargetServerIp(){//擷取該次請求所要分發的目標伺服器
String[] targetServerIps=getTargetServerIps();
int[] targetServerIds=getTargetServerIds();
int index=currentTargetServerIndex;
boolean isWorking=false;
while(!isWorking){
index=targetServerIds.length()%(currentTargetServerIndex+1);
isWorking=isServerWorking(index);
if(!isWorking&&index==currentTargetServerIndex){//無任何目標伺服器可用
return "0:0";
}
}
currentTargetServerIndex=index;
return targetServerIps[index];
}
訊息散發者(Dispatcher Server)分發請求採用的hash演算法
// hash algrithm from JDK‘s String,來自於jdk的hash演算法
public int hash(byte[] bs) {
int hash = 0;
for (int i = 0; i < bs.length; i++) {
hash = 31 * hash + bs[i];
}
return hash;
}
public int getTargetServerGroupIndexByHash(String hashParam) throws BtirException {//返回根據hash計算出的目標伺服器群組
byte[] hashinfo=hashParam.getBytes("utf-8");
int frameCount=2://由hash值的後兩位進行分段的數目 ,即hash結果數,目標伺服器群組的數量
int step = 100 / frameCount;
int hash = Math.abs(hash(hashParam) % 100);
for(int i=0, beg=0, end=step; i<frameCount; i++) {
if(beg <= hash && hash < end )
return 2*i;
beg = end;
end += step;
}
return 2*(frameCount-1); //如果設定得好,應該不會走到這裡
}
public String getTargetServerIpInGriuo(int groupIndex){//根據輪詢演算法,計算服務器群組中的最優伺服器
String[] targetServerIps=getTargetServerIps();//組內的輪詢演算法,代碼略
int[] targetServerIds=getTargetServerIds();
int index=getTargetServerIndexInGroup(groupIndex);//輪詢演算法代碼略
return targetServerIps[index];
}
四、通訊節點設計模型
通訊是請求響應的方式,這對於介面伺服器,訊息散發者和應用伺服器來說都是一直的,所以三者可以採用一致的模型來描述.
包括兩個部分:client和server.這裡描述一下二者的結構和網路通訊.
client構造並發送請求,在非同步系統裡可以將構造和發送解偶,
RequestBuilder產生Request 將Request投入到Request隊列(RequestQueue)中 獨立線程RequestScanner掃描Request隊列並調用RequestSender發送請求. 針對不同類型的請求可以構造不同的隊列和不同的sender,隊列中Request的優先順序策略可以根據需要來定製.server接收請求並處理,
server接收請求並處理,
RequestAccepter接收請求並放入Request隊列 獨立線程掃描隊列並將調用RequestHandler進行處理 RequestHandler處理完畢後返回Response.client與server之間的通訊: 通訊協定和技術有很多,如web service,EJB,jms,單這裡採用基於java NIO的socket,因為其非同步性和高並發量. 採用socket的兩個基本標準是: 1.伺服器上的線程數可控,切忌與請求數線性增長 2.將處理請求和接收請求分開,否則會降低吞吐率和並法量
java分布式通訊系統(J2EE分布式伺服器架構)