面向服務的體系架構 SOA(三) --- Zookeeper API、zkClient API的使用


zookeeper簡單介紹及API使用

1.1 zookeeper簡介

zookeeper是一個針對大型分布式系統的可靠的協調系統,提供的功能包括配置維護、名字服務、分布式同步、組服務等。zookeeper可以集群復制,集群間通過zab協議來保持數據的一致性。該協議包括兩個階段:leader election階段和Atomic broadcas階段。

leader election階段:集群間選舉出一個leader,其他的機器則稱為follower,所有的寫操作都被傳送給leader,並通過broadcas將所有的更新告訴follower,當leader崩潰或leader失去大多數的follower時,需要重新選舉出一個新的leader,讓所有的服務器都恢復到一個正確的狀態。當leader被選舉出來且大多數服務器完成了和leader的狀態同步后,leader election過程結束,進入Atomic broadcas階段。

Atomic broadcas階段:Atomic broadcas同步leader和follower之間的信息,保證二者具有相同的系統狀態。

zookeeper的協作過程簡化了松散耦合系統之間的交互,即使參與者彼此不知道對方的存在,也能夠相互發現並且完成交互。

1.2 zookeeper API簡單使用

可以認為zookeeper是一個小型的、精簡的文件系統,它的每個節點稱為znode,znode除了本身能夠包含一部分數據之外,還能擁有子節點,當節點或子節點數據發生變化時,基於watcher機制,會發出相應的通知給訂閱其狀態變化的客戶端。

1.2.1 zookeeper節點創建

maven項目中引入模塊:

<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>

創建zookeeper對象和節點:

 1     public static void main(String[] args) throws Exception {
 2         /*
 3          * 127.0.0.1:2181:服務器地址
 4          * 10:超時時間
 5          * watcher:若包含boolean watch的讀方法中傳入true,則將默認watcher注冊為所關注事件的watcher
 6          * 若傳入false,則不注冊任何watcher。此處暫且定為空
 7          */
 8         ZooKeeper zookeeper = new ZooKeeper("127.0.0.1:2181", 10, null);
 9         /*
10          * 若創建的節點已經存在,則會拋出異常
11          * /root:節點路徑 ; root data:路徑包含的字節數據
12          * Ids.OPEN_ACL_UNSAFE:訪問權限
13          * CreateMode.PERSISTENT:節點類型
14          */
15         zookeeper.create("/root", "root data".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
16         /*
17          * 設置節點數據
18          * -1:版本號;若匹配不到響應的節點則會拋出異常
19          */
20         zookeeper.setData("/root", "hello".getBytes(), -1);
21         /*
22          * 讀取節點數據
23          * stat是節點狀態參數,讀取時會傳出該節點當前狀態信息
24          */
25         Stat stat = new Stat();
26         byte[] data = zookeeper.getData("/root", false, stat);
27         System.out.println(new String(data));
28         /*
29          * 添加子節點,若父節點不存在會拋出異常
30          */
31         zookeeper.create("/root/child", "child data".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
32         /*
33          * 判斷節點是否存在,不存在則返回的stat為null
34          */
35         Stat existsStat = zookeeper.exists("/root/child", false);
36         System.out.println(existsStat);
37         /*
38          * /root/child:刪除節點路徑
39          * -1:節點的版本號;若設置為-1,則匹配所有版本,zookeeper會比較刪除的版本和服務器版本是否一致,不一致會拋出異常
40          */
41         zookeeper.delete("/root/child", -1);
42     }

 實際運行中最常出現這個錯誤:

Exception in thread "main" org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /root
at org.apache.zookeeper.KeeperException.create(KeeperException.java:90)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:42)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:643)
at com.project.soa.zookeeper.ZookeeperDemo.main(ZookeeperDemo.java:12)

這是因為還未連接上zookeeper就開始添加、刪除節點等操作,為避免這種情況發生,可以在做操作時對連接狀態做判斷:

1 ZooKeeper zookeeper = new ZooKeeper("127.0.0.1:2181", 10, null);
2 if (zookeeper.getState() == States.CONNECTED) {
3 
4 }

 1.2.2 watcher的實現

節點的狀態發生變化,可以通過zookeeper的watcher機制讓客戶端取得通知。watcher的實現較為簡單,只需實現org.apache.ZooKeeper.Watcher接口即可,其中節點的狀態變化包含以下幾種狀態:

注意:watcher機制是一次性的,每次處理完狀態變化事件之后需重新注冊watcher。這也導致在處理事件和重新加上watcher這段時間發生的節點狀態無法被感知。

1.2.3 zkClient的使用

zkClient解決了watcher的一次性注冊問題,將znode的事件重新定義為子節點的變化、數據的變化、連接及狀態的變化三類,watcher執行后重新讀取數據的同時再注冊相同的watcher。在異常發生時zkClient會自動創建新的zookeeper實例進行重連,此時原來的watcher節點都將失效,可在zkClient定義的連接狀態變化的接口中進行相應處理。同時zkClient還提供了序列化和反序列化接口ZkSerializer,簡化了znode上對象的存儲。

maven中引入zkClient模塊:

<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>

簡單事例:
 1     public static void main(String[] args) {
 2         ZkClient zkClient = new ZkClient("192.168.146.132:2181");
 3         String path = "/root";
 4         zkClient.createPersistent(path);
 5         zkClient.create(path + "/child", "znode child", CreateMode.EPHEMERAL);
 6         List<String> children = zkClient.getChildren(path);
 7         System.out.println(children);
 8         int countChildren = zkClient.countChildren(path);
 9         System.out.println(countChildren);
10         System.out.println(zkClient.exists(path));
11         zkClient.writeData(path + "/child", "hello everyone");
12         Object data = zkClient.readData(path + "/child");
13         System.out.println(data);
14         zkClient.delete(path + "/child");
15         
16         //訂閱數據的變化
17         zkClient.subscribeDataChanges(path, new IZkDataListener() {
18             
19             public void handleDataDeleted(String arg0) throws Exception {
20                 
21             }
22             
23             public void handleDataChange(String arg0, Object arg1) throws Exception {
24                 
25             }
26         });
27 
28         //訂閱子節點的變化
29         zkClient.subscribeChildChanges(path, new IZkChildListener() {
30             
31             public void handleChildChange(String arg0, List<String> arg1) throws Exception {
32                 
33             }
34         });
35         
36         zkClient.subscribeStateChanges(new IZkStateListener() {
37             
38             public void handleStateChanged(KeeperState arg0) throws Exception {
39                 
40             }
41             
42             public void handleNewSession() throws Exception {
43                 // 在這里可以進行異常發生時節點失效的容錯處理
44                 
45             }
46         });
47     }

 1.2.4 路由和負載均衡

當服務規模變大時,服務之間的依賴變得十分復雜,這時我們不僅需要了解服務提供方,還需要了解服務消費方以了解服務的調用情況,可以以此作為服務擴容或下線的依據。

服務消費者獲取服務提供者地址列表的部分代碼為:

 1     List<String> serverList;
 2 
 3     public List<String> getServerList() {
 4         serverList = new ArrayList<String>();
 5         String serviceName = "server - A";
 6         String serviceString = "127.0.0.1:2181";
 7         String path = "/config/" + serviceName;
 8         ZkClient zkClient = new ZkClient(serviceString);
 9         if (zkClient.exists(path)) {//服務存在則取地址列表
10             serverList = zkClient.getChildren(path);
11         } else {
12             throw new RuntimeException();
13         }
14         // 注冊監聽事件
15         zkClient.subscribeChildChanges(path, new IZkChildListener() {
16 
17             public void handleChildChange(String s, List<String> list) throws Exception {
18                 serverList = list;
19             }
20         });
21         return serverList;
22     }

先取得服務上所注冊的包含服務提供者地址的子節點,取得服務器地址列表后便可根據負載均衡算法選取調用服務器,服務器列表還存在本地以降低網絡開銷。注冊監聽器來感知服務器上線、下線和宕機事件,若發生節點改動,則將監聽方法中取得的最新子節點賦給當前的serverList。

服務提供者向zookeeper注冊服務:

 1         String path = "/config";
 2         String serverList = "127.0.0.1:2181";
 3         String serverName = "server";
 4         ZkClient zkClient = new ZkClient(serverList);
 5         if (!zkClient.exists(path)) {
 6             zkClient.createPersistent(path);//創建根節點
 7         }
 8         if (zkClient.exists(path + "/" + serverName)) {
 9             zkClient.createPersistent(path + "/" + serverName);//創建服務節點
10         }
11         //注冊當前服務器
12         InetAddress addr = InetAddress.getLocalHost();
13         //取得本機ip
14         String ip = addr.getHostAddress().toString();
15         //創建當前服務器節點
16         zkClient.createPersistent(path + "/" + serverName + "/" + ip);

這樣只有當配置信息更新時服務消費者才會去獲取最新的服務地址列表,其他時候使用本地緩存即可,這樣能大大降低配置中心的壓力。

1.3 HTTP服務網關

移動互聯網的崛起出現了多平台的現狀,同樣的功能廠商需根據不同平台開發不同的APP,使得開發成本增高。而由於客戶端APP、第三方ISV(獨立軟件開發商)應用都必須經過公共網絡來發起客戶端請求,網關(gateway)作用得以凸顯。gateway接收外部各種APP的請求,經過一系列權限與安全校驗等,根據服務名到對應配置中心選取服務器列表,再由負載均衡算法選取一台服務器進行調用,將結果返回給客戶端。

gateway可以攔截一系列惡意請求,而且能使不同的平台共用重復的邏輯,降低開發和運維成本。但由於gateway是整個網絡的核心節點,一旦失效,依賴它的所有外部APP都將無法使用,因此在設計之初應該考慮到系統流量的監控和容量的規划,以便在達到峰值時能夠快速進行系統擴容。

上圖是一種網關集群的架構方案,一組對等的服務器組成網關集群接收外部HTTP請求,當流量達到警戒值,能方便地增加機器進行擴容。網關前有兩台負載均衡設備負責對網關集群進行負載均衡,設備間進行心跳檢測,一旦其中一台宕機,另一台則變更自己的地址接管宕機設備,平時這兩台機器均對外提供服務。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM