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

245 阅读3分钟
原文链接: mp.weixin.qq.com

最近在看与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的节点中。

123456789101112131415161718192021222324252627282930313233
private ZkClient zkClient;public ZooKeeperServiceRegistry(String zkAddress) {    // 创建 ZooKeeper 客户端    zkClient = new ZkClient(zkAddress, ZkConstants.SESSION_TIMEOUT, ZkConstants.CONNECTION_TIMEOUT);    log.info("connect zookeeper");}@Overridepublic 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地址。

1234567891011121314151617181920212223242526272829303132333435363738
private String zkAddress;public ZooKeeperServiceDiscovery(String zkAddress) {    this.zkAddress = zkAddress;}@Overridepublic 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();    }}

总结

通过测试样例,实现了最简单的服务注册和发现功能。注意:在测试之前启动本地ZooKeeper服务。

1234567
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

参考

轻量级分布式 RPC框架https://my.oschina.net/huangyong/blog/361751

GitHub源码地址 https://github.com/tedburner/Simple-Rpc