最近在看與RPC相關的東西,在GitHub上看到一個使用Java實現的簡單RPC框架,因而本身也想用Java實現一個簡單的RPC,以便加深對於RPC框架的理解。本篇文章主要是記錄如何使用ZooKeeper做爲RPC框架的註冊中心,實現服務的註冊和發現。java
RPC,即 Remote Procedure Call(遠程過程調用),說得通俗一點就是:調用遠程計算機上的服務,就像調用本地服務同樣。正式的描述是:一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡技術的協議。node
若是對於dubbo這款國產RPC框架有必定的瞭解,就知道最開始它是基於ZooKeeper實現服務的註冊和發現的。關於服務的註冊和發現,主要是把服務名以及服務相關的服務器IP地址註冊到註冊中心,在使用服務的時候,只須要根據服務名,就能夠獲得全部服務地址IP,而後根據必定的負載均衡策略來選擇IP地址。git
下圖是服務的註冊和發現接口:github
在ZooKeeper的節點概念中,Znode有四種類型,PERSISTENT(持久節點)、PERSISTENT_SEQUENTIAL(持久的連續節點)、EPHEMERAL(臨時節點)、EPHEMERAL_SEQUENTIAL(臨時的連續節點)。Znode的類型在建立時肯定而且以後不能再修改。bash
關於服務的註冊,其實就是把服務和IP註冊到ZooKeeper的節點中。服務器
private ZkClient zkClient;
public ZooKeeperServiceRegistry(String zkAddress) {
// 建立 ZooKeeper 客戶端
zkClient = new ZkClient(zkAddress, ZkConstants.SESSION_TIMEOUT, ZkConstants.CONNECTION_TIMEOUT);
log.info("connect zookeeper");
}
@Override
public void register(String serviceName, String serviceAddress) {
try {
String registryPath = ZkConstants.REGISTRY_PATH;
if (!zkClient.exists(registryPath)) {
zkClient.createPersistent(registryPath);
log.info("zk create registry node: {}", registryPath);
}
//建立服務節點(持久化)
String servicePath = registryPath + "/" + serviceName;
if (!zkClient.exists(servicePath)) {
zkClient.createPersistent(servicePath);
log.info("zk create service node: {}", servicePath);
}
//建立 address 節點(臨時)
String addressPath = servicePath + "/address-";
String addressNode = zkClient.createEphemeralSequential(addressPath, serviceAddress);
log.info("zk create ip address node: {}",addressNode);
} catch (Exception e) {
e.printStackTrace();
log.error("zk create error: {}", e.getMessage());
}
}
複製代碼
經過ZooKeeper的節點把服務名和IP寫入其節點中,這樣就實現了最簡單的服務註冊,下面來看下服務的發現。網絡
服務的發現就是根據服務名來獲取ZooKeeper節點中的IP地址。負載均衡
private String zkAddress;
public ZooKeeperServiceDiscovery(String zkAddress) {
this.zkAddress = zkAddress;
}
@Override
public String discover(String serviceName) {
ZkClient zkClient = new ZkClient(zkAddress, ZkConstants.SESSION_TIMEOUT, ZkConstants.CONNECTION_TIMEOUT);
log.info("connect zookeeper....");
try {
String servicePath = ZkConstants.REGISTRY_PATH + "/" + serviceName;
if (!zkClient.exists(servicePath)) {
throw new SystemException(String.format("can not find any service node on path: %s", servicePath));
}
//獲取路徑的子節點
List<String> addressList = zkClient.getChildren(servicePath);
if (CollectionUtils.isEmpty(addressList)) {
throw new SystemException(String.format("can not find any address node on path: %s", servicePath));
}
//獲取 address 節點
String address;
if (Objects.equals(addressList.size(), 1)) {
//若是隻有一個地址,則獲取地址
address = addressList.get(0);
log.info("get only address node: {}", address);
} else {
//若是有多個ip,隨機選擇一個
address = addressList.get(ThreadLocalRandom.current().nextInt(addressList.size()));
log.info("get random address node:{}", address);
}
//獲取 address 節點的值
String addressPath = servicePath + "/" + address;
return zkClient.readData(addressPath);
} finally {
zkClient.close();
}
}
複製代碼
經過測試樣例,實現了最簡單的服務註冊和發現功能。框架
public static void main(String[] args) {
ServiceRegistry registry = new ZooKeeperServiceRegistry("127.0.0.1:2181");
registry.register("rpc", "192.168.20.49:8080");
ServiceDiscovery discovery = new ZooKeeperServiceDiscovery("127.0.0.1:2181");
String address = discovery.discover("rpc");
System.out.println("服務RPC的地址是:" + address);
}
複製代碼
輸出:dom
服務RPC的地址是:192.168.20.49:8080
複製代碼