what that?
Zookeeper在分散式開發中使用頻繁,但許多框架都對其進行了封裝,初學者可能無法較好的理解其工作原理,該文章演示了使用Zookeeper實現服務註冊,服務發現的簡單demo,希望能達到拋磚引玉的效果;
why need RegisterCenter?
之所以需要訪問註冊和服務發現是因為分散式系統中,服務之間需要相互呼叫,但若每個服務自己維護一份依賴的服務資訊的話,就顯得很麻煩,且自身維護的資料無法保證其實時性,當依賴的服務資訊發生變更時,無法及時獲取更新,解決方案就是引入一個註冊中心,服務提供方將自己的資訊寫入到註冊中心,服務使用方從註冊中心來獲取服務資訊; 如下圖:
client表示服務使用方,server表示服務提供方
實現的效果: 客戶端可自動發現服務資訊,當服務狀態發生變化時(上線,下線,更換地址),客戶端可以及時響應變化,效果如下圖:
實現
-
首先保證Zookeeper以安裝啟動,且可以正常訪問
-
建立Maven專案並新增Zookeeper的Java客戶端依賴(注意版本號需>3.6)
<dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.6.1</version> </dependency>
-
編寫服務提供方
package com.jerry; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.ACL; import java.io.IOException; import java.io.InputStream; import java.net.*; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import static java.net.InetAddress.getLocalHost; public class UserService { public static void main(String[] args) throws IOException, InterruptedException, KeeperException { new UserService().serving(); } public void serving() throws IOException, KeeperException, InterruptedException { //獲取本機ip地址 String ip = null; Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); while (networkInterfaces.hasMoreElements()) { NetworkInterface ni = (NetworkInterface) networkInterfaces.nextElement(); Enumeration<InetAddress> nias = ni.getInetAddresses(); while (nias.hasMoreElements()) { InetAddress ia = (InetAddress) nias.nextElement(); if (!ia.isLinkLocalAddress() && !ia.isLoopbackAddress() && ia instanceof Inet4Address) { ip = ia.getHostAddress(); } } } int port = 8988; //啟動服務 ServerSocket socket = new ServerSocket(port); System.out.println("伺服器已啟動..."); //註冊服務 serverRegister(ip, port); //處理請求 clientHandler(socket); } private void clientHandler(ServerSocket socket) throws IOException { while (true) { Socket accept = socket.accept(); InputStream inputStream = accept.getInputStream(); byte[] barr = new byte[1024]; while (true) { int size = inputStream.read(barr); if (size == -1) { //System.out.println("客戶端已關閉.."); accept.close(); break; } String s = new String(barr, 0, size); //輸出客戶端訊息 System.out.println(accept.getInetAddress().getHostAddress() + ": " + s); } } } private void serverRegister(String ip, int port) throws IOException, KeeperException, InterruptedException { //註冊服務 ZooKeeper zooKeeper = new ZooKeeper("10.211.55.4", 2181, null); try { ArrayList<ACL> acl = new ArrayList<>(); acl.add(new ACL(31, ZooDefs.Ids.ANYONE_ID_UNSAFE)); zooKeeper.create("/userServer", (ip + ":" + port).getBytes(StandardCharsets.UTF_8), acl, CreateMode.EPHEMERAL); System.out.println("服務釋出成功!"); } catch (KeeperException | InterruptedException e) { e.printStackTrace(); throw e; } } }
-
編寫服務服務使用方
package com.yyh; import org.apache.zookeeper.*; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Scanner; public class UserClient implements Watcher { String node = "/userServer"; //服務資訊所在的節點 服務提供方和服務消費方一致 private ZooKeeper zooKeeper; String server_ip; int server_port; public static void main(String[] args) throws Exception { //開始服務監聽 UserClient userClient = new UserClient(); userClient.run(); //當訪問可用時與服務互動 Scanner scanner = new Scanner(System.in); while (true){ System.out.println("輸入要傳送的資訊(e:退出)"); String text = scanner.next(); if (text.equals("e"))System.exit(-1); if (userClient.server_ip == null){ System.err.println("沒有可用的服務..."); }else { userClient.sendToServer(text); } } } private void run() throws Exception { //連線zookeeper zooKeeper = new ZooKeeper("10.211.55.4:2181", 3000, null); //嘗試獲取服務資訊 getServerInfo(); //新增對服務資訊的永久監聽 zooKeeper.addWatch(node,this,AddWatchMode.PERSISTENT); } //獲取服務資訊 private void getServerInfo() { try { byte[] data = zooKeeper.getData(node, false, null); String[] infos = new String(data).split(":"); server_ip = infos[0]; server_port = Integer.parseInt(infos[1]); System.out.println("獲取服務資訊成功!"); System.out.println(server_ip+":"+ server_port); } catch (KeeperException e) { System.err.println("服務資訊不存在! 等待服務上線........"); } catch (InterruptedException e) { e.printStackTrace(); } } //當節點狀態傳送變化時將執行該方法(通知處理) @Override public void process(WatchedEvent event) { if (event.getPath().equals(node)) { //根據具體邏輯處理不同的事件型別,此處只關心節點的建立刪除和更新 if (event.getType() == Event.EventType.NodeCreated) { System.err.println("服務上線了"); getServerInfo(); } else if (event.getType() == Event.EventType.NodeDataChanged) { System.err.println("服務更新了"); getServerInfo(); }else if (event.getType()== Event.EventType.NodeDeleted){ server_ip = null; server_port = 0; System.err.println("服務下線了"); } } } public void sendToServer(String text) { InetSocketAddress server_address = new InetSocketAddress(server_ip, server_port); Socket socket = new Socket(); try { socket.connect(server_address); //System.out.println("連線伺服器成功!"); OutputStream outputStream = socket.getOutputStream(); outputStream.write(text.getBytes()); System.out.println("訊息傳送成功!"); } catch (IOException e) { e.printStackTrace(); } try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } }
-
打包服務端程式碼,該步驟可忽略,僅為了測試客戶端正確性, 為了在打包時附帶其全部依賴,此處藉助Spring的打包外掛,在pom中新增以下內容:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.5.6.RELEASE</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
注意:Spring-boot打包外掛會自動獲取專案中的主函式,必須保證主函式只有一個,所以需要暫時註釋客戶端的主函式,最後執行maven的package,得到jar包
-
將jar上傳至虛擬機器並執行
java -jar ZookeeperTest-1.0-SNAPSHOT.jar
若沒有其他問題則客戶端依然可以正常連線伺服器傳送訊息;
以上便是使用Zookeeper實現服務註冊和服務發現的具體步驟,在實際開發中,我們可能還會將提供的服務部署為叢集,這時可將叢集中的各個服務資訊作為子節點註冊到指定節點下,客戶端監聽該節點變化,獲取子節點列表從而獲取到服務列表,還可以在此基礎上加上負載均衡演算法實現對服務列表的合理訪問; 如圖: