阅读 42

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的节点中。

    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);
    }
复制代码

输出:

服务RPC的地址是:192.168.20.49:8080
复制代码

参考

关注下面的标签,发现更多相似文章
评论