最近在看与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
复制代码