1 Overview
JGroups是一個用於建立可靠的組播通信的工具包(這裏指的組播並不一定是IP Multicast,JGroups同樣支持使用TCP作爲傳輸協議)。其中可靠性是指通過適當的配置可以保證:消息在傳輸的過程中不會丟失;所有的接收者以相同的順序接受所有的消息;原子性:一個消息要麼被所有的接收者接收,要麼不被任何一個接收者都接收。目前在JBoss Application Server Clustering,OSCache Clustering,Jetty HTTP session replication, Tomcat HTTP session replication中都使用了JGroups。
Unreliable Reliable
Unicast UDP TCP
Multicast IP Multicast JGroups
TCP和UDP是單播(Unicast)協議,也就是說:發送者和每一接收者之間是點對點傳輸。 如果一個發送者希望向多個接收者傳輸相同的數據,那麼必須相應的複製多份數據。TCP是可靠的傳輸協議,但UDP不是可靠的,也就是說報文在傳輸的過程中可能丟失、重複或着亂序,報文的最大尺寸也有限制。IP Multicast可以將消息同時發送到多個接收者。由於IP Multicast是基於UDP的,因此IP Multicast是不可靠的。IP Multicast需要一個特殊的組播地址,它是一組D類IP地址,範圍從224.0.0.0 到 239.255.255.255,其中有一部分地址是爲特殊的目的保留的。JGroups使用UDP (IP Multicast)、TCP、JMS作爲傳輸協議。JGroups最強大的功能之一是提供了靈活的,可定製的協議棧,以滿足不同的需求。例如,如果選擇使用IP Multicast作爲傳輸協議,那麼爲了防止報文丟失和重複,可以在協議棧中添加NAKACK協議;爲了保證報文的順序,可以在協議棧中添加TOTAL協議,以保證FIFO的順序;爲了在組內成員發生變化時得到通知和回調,可以添加Group Membership Service (GMS) 和 FLUSH協議;Failure Detector (FD)協議用於識別組內崩潰的成員;如果新加入的成員希望獲得組內其它成員維護的狀態,那麼可以向協議棧中添加STATE_TRANSFER協議;如果希望對傳輸的數據進行加密,那麼可以使用CRYPT協議等等。
JGruops的主要功能有:
組的創建和刪除。組可以跨越LANs或者WANs。
加入組、主動或者被動(例如當機或者網絡故障)離開組。
在組成員改變時,組中其它成員可以得到通知。
向組中的單個或者多個成員發送消息。
在JGroups中JChannel類提供了主要的API ,用於連接到集羣(cluster)、發送和接收消息(Message)和註冊listeners等。Message包含消息頭(保存地址等信息)和一個字節數組(保存希望傳輸的數據)。org.jgroups.Address接口及其實現類封裝了地址信息,它通常包含IP地址和端口號。連接到集羣中的所有實例(instance)被稱爲一個視圖(org.jgroups.View)。通過View.getMembers()可以得到所有實例的地址。實例只有在連接到集羣后才能夠發送和接收消息。以相同name調用JChannel.connect(String name)方法的所有實例會連接到同一個集羣。當實例希望離開集羣時,可以調用JChannel.disconnect()方法。當希望釋放佔有的資源時,可以調用JChannel.close()方法。JChannel.close()方法內部會調用JChannel.disconnect()方法。
通過調用JChannel.setReceiver()方法可以接收消息和得到View改變的通知。每當有實例加入或者離開集羣的時候,viewAccepted(View view)方法會被調用。View.toString()方法會打印出View中所有實例的地址,以及View ID。需要注意的是,每次viewAccepted(View view)方法被調用時,view參數都不同,其View ID也會增長。View內的第一個實例被稱爲coordinator。Receiver接口上的getState(),setState()方法用於在實例間傳遞狀態。新的實例通過setState()方法獲得通過狀態,而這個狀態是通過調用集羣中其它某個實例上的getState()獲得的。
以下是JGruops manual中的一個簡單的例子:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.ReceiverAdapter;
import org.jgroups.View;
import org.jgroups.util.Util;
public class SimpleChat {
//
private JChannel channel;
private List<String> state = new LinkedList<String>();
private String userName = System.getProperty("user.name", "WhiteSock");
public void start() throws Exception {
//
channel = new JChannel();
channel.setReceiver(new ReceiverAdapter() {
public void receive(Message msg) {
System.out.println(msg.getSrc() + ": " + msg.getObject());
synchronized(state) {
state.add((String)msg.getObject());
}
}
public void viewAccepted(View view) {
System.out.println("view accepted: " + view);
}
public byte[] getState() {
synchronized(state) {
try {
return Util.objectToByteBuffer(state);
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
@SuppressWarnings("unchecked")
public void setState(byte[] new_state) {
try {
List<String> list=(List<String>)Util.objectFromByteBuffer(new_state);
synchronized(state) {
state.clear();
state.addAll(list);
}
System.out.println("received state (" + list.size() + " messages in chat history):");
for(String str: list) {
System.out.println(str);
}
}
catch(Exception e) {
e.printStackTrace();
}
}
});
channel.connect("ChatCluster");
channel.getState(null, 10000);
//
sendMessage();
//
channel.close();
}
private void sendMessage() throws Exception {
boolean succeed = false;
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
while(true) {
System.out.print(">");
System.out.flush();
String line = br.readLine();
if(line != null && line.equals("exit")) {
break;
} else {
Message msg = new Message(null, null, "[" + userName + "]" + line);
channel.send(msg);
}
}
succeed = true;
} finally {
if(br != null) {
try {
br.close();
} catch (Exception e) {
if(succeed) {
throw e;
}
}
}
}
}
public static void main(String args[]) throws Exception {
new SimpleChat().start();
}
}
在以上例子中,主線程會阻塞,直到從stdin中讀取一行。如果這行是"exit",那麼程序退出,否則向集羣中發送一個消息。如果集羣中某個實例強行退出,那麼集羣中的其它實例也會得到通知。Message構造函數的第一個參數如果是null,那麼意味着消息將被髮送到集羣內所有的實例。