最近在看与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