1. 程式人生 > >基於ZooKeeper和Thrift構建動態RPC呼叫

基於ZooKeeper和Thrift構建動態RPC呼叫

說明:這次的部落格是自己在復旦大學一個課程的作業。有修改、調整

一、基本功能

  實現服務端向ZooKeeper叢集註冊自己提供的服務,並且把自己的IP地址和服務埠建立到具體的服務目錄下。客戶端向ZooKeeper叢集監聽自己關注的RPC服務(例如:sayHello和天氣服務), 監聽服務目錄下的IP地址列表變化。若要在自己的專案中使用,可以採用阿里的Dubbo分散式服務框架
  在WEB端展示可以訪問的RPC服務,WEB端可以通過RPC客戶端向制定IP地址的RPC伺服器發出呼叫RPC服務,RPC服務端向客戶端反饋提供的服務內容,WEB客戶端展示內容。
  只是展示動態RPC基本原理,真正的呼叫一般都是不是web端觸發的,應該是RPC的客戶端根據監聽到的多個IP服務提供者,根據每個IP的負載情況,動態選擇最優可用的RPC服務端並且呼叫服務。
  我們提供2個基本RPC服務,網路及應用部署如圖:
  這裡寫圖片描述


最終

二、ZooKeeper介紹

ZooKeeper是一個開放原始碼的分散式應用程式協調服務,由知名網際網路公司雅虎建立,是Google的Chubby一個開源的實現,是Hadoop和Hbase的重要元件。 ZooKeeper的目標就是封裝好複雜易出錯的關鍵服務,將簡單易用的介面和效能高效、功能穩定的系統提供給使用者。它是一個為分散式應用提供一致性服務的軟體,以下是ZooKeeper典型的應用場景

  • 資料釋出和訂閱:就是釋出者將資料釋出到ZooKeeper的一個或一系列節點上,供訂閱者進行資料訂閱,進而達到動態獲取資料的目的,實現配置資訊的集中管理和資料的動態更新。
  • 負載均衡:用來對多個計算機、網路連線、CPU、磁碟驅動或其他資源進分配負載,已達到優化資源使用、最大化吞吐率、最下化響應和避免過載。
  • 命名服務:命名服務是分散式系統最基本的公共服務之一。在分散式系統中,被命名的實體通常可以就是叢集中的機器、提供的服務地址或遠端物件等–這些我們都可以統稱它們的名稱(Name),其中較常見的就是一些分散式服務框架(如RPC、RMI)中的服務地址列表,通過使用命名服務,客戶端應用能夠指定名字來獲取資源的實體、服務地址和提供者的資訊。
  • 叢集管理:隨著分散式系統規模的日益擴大,叢集中的機器規模也隨之變大、因此叢集監控與叢集控制就變得很重要。
  • 分散式鎖:分散式鎖就是控制分散式系統之間同步訪問共享資源的一種方式。在分散式系統中,常常需要協調他們的動作。如果不同的系統或是同一個系統的不同主機之間共享了一個或一組資源,那麼訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分散式鎖。
  • 分散式佇列:利用Zookeeper的功能我們也可以實現類似於ActiveMQ、Kafka和HornetQ等的訊息中介軟體。

三、構建ZooKeeper叢集機及RPC服務機

在Ubuntu 桌面系統下完成,利用Oracle下的虛擬機器軟體VirtualBox。虛擬出了5個Ubuntu 作業系統,3個 ZooKeeper機,分別是ZooKeeper-1,ZooKeeper-2,ZooKeeper-3個構建出一個 ZooKeeper叢集。2各RPC服務機,把在宿主機編寫好的程式,通過打包的方式,釋出到RPC服務機的jetty下,提供RPC服務 。

四、配置ZooKeeper

從官方網站下載後,解壓到了虛擬機器的/work/目錄下,將/work/zookeeper-3.4.8/conf/目錄下的zoo_sample.cfg重新複製一份命名位zoo.cfg,開啟zoo.cfg檔案。 
 修改配置檔案:

     tickTime=2000    
     ddataDir=/work/data/zookeeper   
     clientPort=2181 
    Server.1=192.168.0.3:2888:3888
     Server.2=192.168.0.4:2888:3888
     Server.3=192.168.0.5:2888:3888

引數說明:
tickTime: zookeeper中使用的基本時間單位, 毫秒值.
dataDir: 資料和日誌的目錄. 可以是任意目錄.此處我們配置到了/work/data/zookeeper 目錄下
     clientPort: 監聽client連線的埠號.
     Server.X=HOST/IP:port:port 
     Server.X :X是我們配置zookeeper叢集服務每臺機子的編號,需要在每臺機子的/work/data/zookeeper/下建立myid檔案,內容就是機子的編號。

五、啟動、關閉

切換到/work/zookeeper-3.4.8/bin目錄下
1. 啟動
./zkServer.sh start

   ZooKeeper JMX enabled by default
  Using config: /work/zookeeper-3.4.8/bin/../conf/zoo.cfg
  Starting zookeeper ... STARTED
  1. 驗證
    ./zkCli.sh
[zk: localhost:2181(CONNECTED) 0] 

進入ZooKeeper 客戶端終端命令就說明ZooKeeper 啟動成功了。
3. 關閉
./zkServer.sh stop

   ZooKeeper JMX enabled by default
  Using config: /work/tool/zookeeper-3.4.8/bin/../conf/zoo.cfg
  Stopping zookeeper ... STOPPED

六、利用Thrift提供RPC服務

  1. 定義Weather.thrift檔案
namespace java com.rpc.weather
       service weather{ 
      string getWeather(1:string city) 
   } 
  1. 生成JAVA檔案介面
    在windows環境下使用 Thrift 工具編譯 .thrift檔案,就會生成相應的 .java 檔案。該檔案包含了在 .thrift 檔案中描述的服務類的介面定義,即 .Iface 介面,以及服務呼叫的底層通訊細節。命令如下: thrift.exe -r -gen java  weather.thrift。該命令會自動生產相應的JAVA檔案
    這裡寫圖片描述
    gen-java目錄就是生成好程式碼的地方
  2. 實現RPC介面功能
    weather的介面實現比較複雜,在這裡我們用簡單些Hello來說明,道理是一樣的。Hello介面的實現: hello只是一個簡單的反饋功能,它把客戶端傳遞過來的引數經過簡單的組合一起反饋給RPC的客戶端,本例只是簡單展示了一下RPC服務處理能力,實現上面已經生產好的Hello.Iface 介面。程式碼如下:
public class HelloServiceImpl implements com.rpc.sayhello.Hello.Iface {
    public String helloString(String para) throws TException {
        System.out.println("helloString be calling");
        return "你好:" + para + ",歡迎來到"+GetIP.IP()+"伺服器!";
    }
}

七、RPC服務註冊

我們在ZooKeeper註冊了2個服務(2個ZNode節點),分別是sayHello及Weather。用2個IP提供RPC的服務。目錄結構如圖-:
這裡寫圖片描述
在Zookeeper的每個節點,都可以分為持久節點和臨時節點 持久節點是指一旦這個節點被建立了,除非主動進行刪除操作,否則這個節點將一直儲存在ZooKeeper中.而臨時節點就不一樣了,它的生命週期和客戶端回話繫結,一旦客戶端回話失效,那麼這個客戶端建立的所有臨時節點都會被移除。ZooKeeper主要是利用了“心跳檢測”功能,它會定時向各個服務提供者傳送一個請求,如果長期沒有響應,服務中心就認為該服務提供者已經“掛了”,並將其剔除。注意臨時節點下不可以在建立任何節點。

註冊天氣服務的主要程式碼:

private void createServerHost()  {
        Stat stat = zookeeper.exists(WeatherConstants.RPCNAME + "/" + GetIP.IP() + ":" + WeatherConstants.WeahterPort,false);//檢查節點是否存在
        if (stat == null) {
      // 這裡是臨時的節點,會因伺服器的宕機、網路失效而消失
    path = zookeeper.create(WeatherConstants.RPCNAME + "/" + GetIP.IP() + ":" +   WeatherConstants.WeahterPort, "".getBytes(),Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);//建立節點 
        }
    }

八、RPC客戶端服務監聽

Watcher(事件監聽器),是ZooKeeper的一個很重要的特性。ZooKeeper允許使用者在指定的節點上註冊一些Watcher,並且在一些特定的事件觸發的時候,ZooKeeper伺服器會將事件通知到感興趣的客戶端。利用Watcher監聽2個服務節點下的IP變化,一旦我們監聽的服務下的節點有變化(增加或減少)ZooKeeper就會向我們註冊的監聽類傳送“NodeChildrenChanged”事件,我們就可以在此時更新地址列表變化,從而進行更新。
需要注意的是ZooKeeper伺服器在向客戶端傳送Watcher的通知的時候,僅僅只會發出一個通知,而不會把節點的變化情況傳送給客戶端,客戶端需要自己重新獲取。另外,由於Watcher通知是一次性的,一旦觸發一次通知後,該Watcher就失效了,因此客戶端需要反覆註冊Watcher。

監聽服務列表的變化
在監聽WatchWeather類內我們定義了一個weatherlist的陣列列表,用來儲存提供天氣的所有RPC服務的地址和埠。
通過zookeeper.getChildren獲取在zookeeper註冊的所有提供天氣的IP地址。並且註冊了在這個節點下的監聽類。

    weatherlist = zookeeper.getChildren(WeatherConstants.RPCNAME, this);
    //在監聽的WatchWeather實現Watcher介面的process方法:
    public void process(WatchedEvent event) {
        if (EventType.NodeChildrenChanged == event.getType()) {  
          weatherlist = zookeeper.getChildren(WeatherConstants.RPCNAME, this);
     }   
    }
  

只要我們監聽的節點下有變動就會接受到NodeChildrenChanged 事件,在這裡我們再次獲取了節點下的最新IP地址列表,並且重新註冊了監聽類。

九、呼叫RPC服務

public class CallWeatherRPC {
    public String callWeather(String ip, int port, String city) {
        String retString = null;
        TTransport transport = new TSocket(ip, port);
        transport.open();
        TProtocol protocol = new TBinaryProtocol(transport);
        weather.Client client = new weather.Client(protocol);//weather為定義介面實現的檔案
        retString = client.getWeather(city);//呼叫RPC服務
        transport.close();      
        return retString;
    }
}

此處是使用了Thrift的客戶端呼叫RPC服務端的相應程式,主要特點是IP地址不固定,可以有多地址可以呼叫。