轉自:http://xpenxpen.iteye.com/blog/2041897
1.官方的tutorial已經很好了,得首先閱讀。
http://www.jgroups.org/ug.html本文其實是官方的tutorial的筆記,其中大部分文字轉載自http://whitesock.iteye.com/blog/199229
2.基本概況在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 ID。
View內的第一個實例被稱爲coordinator。
Receiver接口上的getState(),setState()方法用於在實例間傳遞狀態。
新的實例通過setState()方法獲得通過狀態,而這個狀態是通過調用集羣中其它某個實例上的getState()獲得的。
3.Chat例子
3.1 實現功能
我們來寫一個聊天程序,只支持文本的。我們要實現如下功能
- 所有的SimpleChat實例可以相互找到並組成一個集羣。
- 沒必要創建一箇中心的ChatServer,這樣就不會有單點故障。
- 聊天消息將被髮到集羣中的所有實例。
- 當一個實例加入或退出(或崩潰)時,其他實例都將得到通知。
- 我們維護一個集羣內的聊天記錄state。新加入的實例可以查詢聊天記錄。
3.2 代碼
代碼就是官方的例子,我加入了詳細的註釋。
- import java.io.BufferedReader;
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.OutputStream;
- 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 extends ReceiverAdapter {
- JChannel channel;
- String user_name = "ABC";
- private List<String> state = new LinkedList<String>();
- private void start() throws Exception {
- channel = new JChannel(); //使用默認配置udp.xml
- channel.setReceiver(this); //指定Receiver用來收消息和得到View改變的通知
- channel.connect("ChatCluster"); //連接到集羣
- //剛加入集羣時,我們通過getState()獲取聊天曆史記錄
- //getState()的第一個參數代表目的地地址,這裏傳null代表第一個實例(coordinator)
- //第二個參數代表等待超時時間,我們等待10秒。如果時間到了,State傳遞不過來,會拋例外。也可以傳0代表永遠等下去
- channel.getState(null, 10000);
- eventLoop();
- channel.close();
- }
- private void eventLoop() {
- BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
- while (true) {
- try {
- System.out.print("> ");
- System.out.flush();
- String line = in.readLine().toLowerCase();
- if (line.startsWith("quit") || line.startsWith("exit")) {
- break;
- }
- line = "[" + user_name + "] " + line;
- //Message構造函數的第一個參數代表目的地地址,這裏傳null代表要發消息給集羣內的所有地址
- //第二個參數表示源地址,傳null即可,框架會自動賦值
- //第三個參數line會被序列化成byte[]然後發送,推薦自己序列化而不是用java自帶的序列化
- Message msg = new Message(null, null, line);
- channel.send(msg); //發消息到集羣
- } catch (Exception e) {
- }
- }
- }
- @Override
- //每當有實例加入或者離開集羣(或崩潰)的時候,viewAccepted方法會被調用
- public void viewAccepted(View new_view) {
- System.out.println("** view: " + new_view);
- }
- @Override
- //有消息時,byte[]會被反序列化成Message對象,也可以用Message.getBuffer得到byte[]然後自己反序列化。
- public void receive(Message msg) {
- String line = msg.getSrc() + ": " + msg.getObject();
- System.out.println(line);
- //加入到歷史記錄
- synchronized (state) {
- state.add(line);
- }
- }
- @Override
- public void getState(OutputStream output) throws Exception {
- //當JChannel.getState()被調用時,某個原來就在集羣中的實例的getState會被調用用來得到集羣的共享state
- //Util.objectToStream方法將state序列化爲output二進制流
- synchronized (state) {
- Util.objectToStream(state, new DataOutputStream(output));
- }
- }
- @Override
- public void setState(InputStream input) throws Exception {
- //當以上集羣的共享state被得到後,新加入集羣的實例的setState方法就會被調用了
- List<String> list = (List<String>) Util.objectFromStream(new DataInputStream(input));
- synchronized (state) {
- state.clear();
- state.addAll(list);
- }
- System.out.println(list.size() + " messages in chat history):");
- for (String str : list) {
- System.out.println(str);
- }
- }
- public static void main(String[] args) throws Exception {
- new SimpleChat().start();
- }
- }
3.3 功能測試
1.運行該代碼3次,開啓了3個實例,觀察控制檯,可以看到每有一個實例加入集羣,其他客戶端都會得到通知(viewAccepted被調用)。
2.隨便哪個客戶端發一條消息,其他客戶端都能收到這條消息。
3.其中一個客戶端輸入exit,其他客戶端都會得到通知。
4.模擬崩潰,可以殺死某個客戶端進程,可以觀察到其他客戶端可以得到通知。
5.新加入的客戶端可以看到聊天曆史記錄。
3.4 監控測試
爲了探索jgroups的內在機理,我們用Process Explorer做另一個測試。
先開啓第1個SimpleChat
看到第1臺機器的53242開始監聽
開啓第2個SimpleChat
第1臺機器的53244和第2臺機器的53243建立連接
第2臺機器的53245和第1臺機器的53242建立連接
開啓第3個SimpleChat
第1臺機器的53244和第2臺機器的53243建立連接
第2臺機器的53247和第3臺機器的53246建立連接
第3臺機器的53248和第1臺機器的53242建立連接
開啓第4個SimpleChat
第1臺機器的53244和第2臺機器的53243建立連接
第2臺機器的53247和第3臺機器的53246建立連接
第3臺機器的53250和第4臺機器的53249建立連接
第4臺機器的53251和第1臺機器的53242建立連接
殺死第3個SimpleChat
第1臺機器的53244和第2臺機器的53243建立連接
第2臺機器的53252和第4臺機器的53249建立連接
第4臺機器的53251和第1臺機器的53242建立連接
我們管中窺豹,略微嗅到了jgroups是怎樣實現可靠多播的,就是採用一個環將各個節點連接起來(TCP連接)。
當有一個節點崩潰(Client3),這個環會重新連接成一個新的環。圖中的藍線便是爲了修補這個環所建立的新的連接。
圖中紅色的端口是UDP的意思,這個端口負責多播通訊,圖中看可出是45588端口,jgroups.jar包默認的udp.xml印證了這一點。
- <UDP
- mcast_port="${jgroups.udp.mcast_port:45588}" />
4.jgroups的應用
上面例子程序我們已經可以看到,jgroups可以用來做state replication
以下項目場景都使用了jgroups
JBoss Application Server Clustering
OSCache Clustering
Jetty HTTP session replication
Tomcat HTTP session replication
5.參考資料
官方文檔部分中文翻譯
https://community.jboss.org/wiki/BelaBansJGroupsManualTranslationSerialI- 共4篇