1. 程式人生 > >Thrift 個人實戰--RPC服務的釋出訂閱實現(基於Zookeeper服務)

Thrift 個人實戰--RPC服務的釋出訂閱實現(基於Zookeeper服務)

前言: Thrift作為Facebook開源的RPC框架, 通過IDL中間語言, 並藉助程式碼生成引擎生成各種主流語言的rpc框架服務端/客戶端程式碼. 不過Thrift的實現, 簡單使用離實際生產環境還是有一定距離, 本系列將對Thrift作程式碼解讀和框架擴充, 使得它更加貼近生產環境. 本文講述如何借用zookeeper來實現中介角色, 使得服務端和客戶端解耦, 並讓RPC服務平臺化發展.

基礎架構:

RPC服務往平臺化的方向發展, 會遮蔽掉更多的服務細節(服務的IP地址叢集, 叢集的擴容和遷移), 只暴露服務介面. 這部分的演化, 使得server端和client端完全的解耦合. 兩者的互動通過ConfigServer(MetaServer)的中介角色來搭線.

注: 該圖源自dubbo的官網

這邊藉助Zookeeper來扮演該角色, server扮演釋出者的角色, 而client扮演訂閱者的角色. 

Zookeeper基礎:

Zookeeper是分散式應用協作服務. 它實現了paxos的一致性演算法, 在命名管理/配置推送/資料同步/主從切換方面扮演重要的角色.

其資料組織類似檔案系統的目錄結構:

每個節點被稱為znode, 為znode節點依據其特性, 又可以分為如下型別:

1). PERSISTENT : 永久節點

2). EPHEMERAL : 臨時節點, 會隨session(client disconnect)的消失而消失

3). PERSISTENT_SEQUENTIAL

: 永久節點, 其節點的名字編號是單調遞增的

4). EPHEMERAL_SEQUENTIAL : 臨時節點, 其節點的名字編號是單調遞增的

注: 臨時節點不能成為父節點

Watcher觀察模式, client可以註冊對節點的狀態/內容變更的事件回撥機制. 其Event事件的兩類屬性需要關注下:

1). KeeperState : Disconnected,SyncConnected,Expired

2). EventType : None,NodeCreated,NodeDeleted,NodeDataChanged,NodeChildrenChanged

RPC服務端:

作為具體業務服務的RPC服務釋出方, 對其自身的服務描述由以下元素構成.

1). product : 產品名稱

2). service : 服務介面, 採用釋出方的類全名來表示

3). version : 版本號

借鑑了Maven的GAV座標系, 三維座標系更符合服務平臺化的大環境.

*) 資料模型的設計

具體RPC服務的註冊路徑為: /rpc/{product}/{service}/{version}, 該路徑上的節點都是永久節點

RPC服務叢集節點的註冊路徑為: /rpc/{product}/{service}/{version}/{ip:port}, 末尾的節點是臨時節點

*) RPC服務節點的配置和行為

服務端的配置如下所示:

<register>
  <server>{ip:port => Zookeeper的地址列表}</servers>
  <application>{application name => 服務的應用程式名}</application>
</register>

<server>
  <interface>{interface => 服務介面名}</interface>
  <version>{version => 服務版本號}</version>
  <class>{class => interface的具體實現Handler類}</class>
  <port>{提供服務的監聽埠}</port>
</server>

服務端的註冊邏輯:

Zookeeper zk = new Zookeeper("127.0.0.1:2181", timeout, null);
while ( !application exit ) {
  Stat stat = zk.exists("/rpc/{product}/{service}/{version}/{ip:port}", false);
  if ( stat == null ) {
    zk.create("/rpc/{product}/{service}/{version}/{ip:port}", Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
  }	
  Thread.sleep(wait_timeout);
}

注: zookeeper client與zookeeper的斷開會導致臨時節點的丟失, 因此需要重新構建, 這邊採用開啟一個迴圈執行緒, 用於定時輪詢.

RPC客戶端:

客戶端的簡單註冊配置:

<register>
  <server>{ip:port => Zookeeper的地址列表}</servers>
  <application>{application name => 服務的應用程式名}</application>	
</register>

<service>
  <interface>{interface => 服務介面名}</interface>
  <version>{version => 服務版本號}</version>
</sevice>

客戶端的程式碼:

1). 初始獲取server列表

Zookeeper zk = new Zookeeper("127.0.0.1:2181", timeout, null);
List<String> childrens = zk.getChildren(path, true);

2). 註冊Watcher監聽, EventType.NodeChildrenChanged事件, 每次回撥, 重新獲取列表

class WatcherWarpper implements Watcher {
  public void process(WatchedEvent event) {
    if ( event.getType() == EventType.NodeChildrenChanged ) {
      List<String> childrens = zk.getChildren(path, true);
      // notify Thrift client, rpc service的server ip:port列表發生了變化	
    }
  }
}

總結: 

這部分其實涉及thrift點並不多, 但該文確實是rpc服務平臺化的理論基礎. 服務端作為服務的釋出方, 而客戶端藉助zookeeper的watcher機制, 來實現其對服務列表的訂閱更新功能. 從而達到解耦, 邁出服務平臺化的一步.

後續: 後續文章講解Thrift client連線池的實現, 也是比較基礎的一部分, 敬請期待.