0%

RPC系列之基于ZooKeeper实现服务注册中心

最近在看与RPC相关的东西,在GitHub上看到一个使用Java实现的简单RPC框架,于是自己也想用Java实现一个简单的RPC,以便加深对于RPC框架的理解。本篇文章主要是记录如何使用ZooKeeper作为RPC框架的注册中心,实现服务的注册和发现。

什么是RPC?

RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样。正式的描述是:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

基于ZooKeeper实现的服务注册中心

如果对于dubbo这款国产RPC框架有一定的了解,就知道最开始它是基于ZooKeeper实现服务的注册和发现的。关于服务的注册和发现,主要是把服务名以及服务相关的服务器IP地址注册到注册中心,在使用服务的时候,只需要根据服务名,就可以得到所有服务地址IP,然后根据一定的负载均衡策略来选择IP地址。

下图是服务的注册和发现接口:

服务的注册

在ZooKeeper的节点概念中,Znode有四种类型,PERSISTENT(持久节点)、PERSISTENT_SEQUENTIAL(持久的连续节点)、EPHEMERAL(临时节点)、EPHEMERAL_SEQUENTIAL(临时的连续节点)。Znode的类型在创建时确定并且之后不能再修改。

关于服务的注册,其实就是把服务和IP注册到ZooKeeper的节点中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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();
}
}

总结

通过测试样例,实现了最简单的服务注册和发现功能。

1
2
3
4
5
6
7
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);
}

输出:

1
服务RPC的地址是:192.168.20.49:8080

Reference

客官,赏一杯coffee嘛~~~~