RPC 框架代码分析之注册中心模块

我们之前在“如何自己实现一个 RPC 框架?”这篇文章中介绍到说:注册中心负责服务地址的注册与查找,相当于目录服务。 服务端启动的时候将服务名称及其对应的地址(ip+port)注册到注册中心,服务消费端根据服务名称找到对应的服务地址。有了服务地址之后,服务消费端就可以通过网络请求服务端了。

简单来说注册中心就像是一个中转站,提供的作用就是根据调用的服务名称找到远程服务的地址(数据保存服务)。

注册中心模块整体结构如下:

09 RPC 框架代码分析之注册中心模块 - 图1

我们定义了两个接口 ServiceDiscovery.java 和 ServiceRegistry.java,这两个接口分别定义了服务发现和服务注册行为。

ServiceRegistry.java

  1. /**
  2. * 服务注册
  3. */
  4. public interface ServiceRegistry {
  5. /**
  6. * 注册服务到注册中心
  7. *
  8. * @param rpcServiceName 完整的服务名称(class name+group+version)
  9. * @param inetSocketAddress 远程服务地址
  10. */
  11. void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress);
  12. }

ServiceDiscovery.java

  1. /**
  2. * 服务发现
  3. */
  4. public interface ServiceDiscovery {
  5. /**
  6. * 根据 rpcServiceName 获取远程服务地址
  7. *
  8. * @param rpcServiceName 完整的服务名称(class name+group+version)
  9. * @return 远程服务地址
  10. */
  11. InetSocketAddress lookupService(String rpcServiceName);
  12. }

接下来,我们使用 ZooKeeper 作为注册中心的实现方式,并实现了这两个接口。

ZkServiceRegistry.java

  1. /**
  2. * 服务注册(基于zookeeper实现)
  3. */
  4. @Slf4j
  5. public class ZkServiceRegistry implements ServiceRegistry {
  6. @Override
  7. public void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress) {
  8. String servicePath = CuratorUtils.ZK_REGISTER_ROOT_PATH + "/" + rpcServiceName + inetSocketAddress.toString();
  9. CuratorFramework zkClient = CuratorUtils.getZkClient();
  10. CuratorUtils.createPersistentNode(zkClient, servicePath);
  11. }
  12. }

当我们的服务被注册进 zookeeper 的时候,我们将完整的服务名称 rpcServiceName (class name+group+version)作为根节点 ,子节点是对应的服务地址(ip+端口号)。

class name : 服务接口名也就是类名比如:github.javaguide.HelloService。
version :(服务版本)主要是为后续不兼容升级提供可能
group :主要用于处理一个接口有多个类实现的情况。

一个根节点(rpcServiceName)可能会对应多个服务地址(相同服务被部署多份的情况)。

09 RPC 框架代码分析之注册中心模块 - 图2

如果我们要获得某个服务对应的地址的话,就直接根据完整的服务名称来获取到其下的所有子节点,然后通过具体的负载均衡策略取出一个就可以了。相关代码如下在 ZkServiceDiscovery.java中已经给出。

ZkServiceDiscovery.java

  1. /**
  2. * 服务发现(基于zookeeper实现)
  3. */
  4. @Slf4j
  5. public class ZkServiceDiscovery implements ServiceDiscovery {
  6. private final LoadBalance loadBalance;
  7. public ZkServiceDiscovery() {
  8. this.loadBalance = new RandomLoadBalance();
  9. }
  10. @Override
  11. public InetSocketAddress lookupService(String rpcServiceName) {
  12. CuratorFramework zkClient = CuratorUtils.getZkClient();
  13. List<String> serviceUrlList = CuratorUtils.getChildrenNodes(zkClient, rpcServiceName);
  14. if (serviceUrlList.size() == 0) {
  15. throw new RpcException(RpcErrorMessage.SERVICE_CAN_NOT_BE_FOUND, rpcServiceName);
  16. }
  17. // load balancing
  18. String targetServiceUrl = loadBalance.selectServiceAddress(serviceUrlList);
  19. log.info("Successfully found the service address:[{}]", targetServiceUrl);
  20. String[] socketAddressArray = targetServiceUrl.split(":");
  21. String host = socketAddressArray[0];
  22. int port = Integer.parseInt(socketAddressArray[1]);
  23. return new InetSocketAddress(host, port);
  24. }
  25. }

我们根据完整的服务名称便可以将对应的服务地址查出来, 查出来的服务地址可能并不止一个。

所以,我们可以通过对应的负载均衡策略来选择出一个服务地址。

CuratorUtils.java

另外,我们还自定义了一个 ZooKeeper Java 客户端 Curtor 的工具类 CuratorUtils.java 。关于这个工具类,这里就不再提了。

在《08 Zookeeper 常用命令+ Curtor 使用详解》中已经介绍的非常详细了。