前言:
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連接池的實現, 也是比較基礎的一部分, 敬請期待.