5、Dubbo源码系列-服务端注册

191 阅读3分钟

书接上文,前文主要从RegistryProtocol export方法一直分析到了NettyServer doOpen方法启动Netty服务整个流程,作为RPC服务的服务端,客户端又是怎样感知到自己应该和哪个服务端进行通信呢?答案就是:注册中心。服务启动后,服务端要将自己提供的服务信息,注册到对应的注册中心里,客户端通过注册对应的服务监听,当服务端信息发生变更时,由注册中心通知所有的客户端,从而可以感知到服务端信息的变更,本文,就带大家一起看下相关源码实现。

一、温故知新

image.png 看过前文的同学,想必对上图的流程应该还会有一些印象,ProtocolAdaptive的export方法,最终启动了服务端的Netty服务,方法调用结束之后,则会继续执行RegistryProtocol的register方法来实现服务的注册,下面就让我们来看看register方法内部到底做了什么吧。

二、RegistryProtocol-register

    public void register(URL registryUrl, URL registeredProviderUrl) {
        Registry registry = registryFactory.getRegistry(registryUrl);
        registry.register(registeredProviderUrl);

        ProviderModel model = ApplicationModel.getProviderModel(registeredProviderUrl.getServiceKey());
        model.addStatedUrl(new ProviderModel.RegisterStatedURL(
                registeredProviderUrl,
                registryUrl,
                true
        ));
    }

方法很简单,首先通过registryFactory.getRegistry()方法获取到对应的Registry,然后调用registry.register方法注册对应的服务端url信息

可以看到,这里有两个URL对象,一个是registryUrl,一个是registeredProviderUrl,故名思义,registryUrl则是表示注册中心信息,registeredProviderUrl则是要注册的服务端信息,可以看下两者差异

image.png image.png

三、RegistryFactory-getRegistry

    @Override
    public Registry getRegistry(URL url) {
        url = URLBuilder.from(url)
                .setPath(RegistryService.class.getName())
                .addParameter(INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(EXPORT_KEY, REFER_KEY)
                .build();
        String key = url.toServiceStringWithoutResolving();
        // Lock the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            //create registry by spi/ioc
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }

方法实现比较简单,加锁防止重复初始化,调用createRegistry方法创建对应的registry,由于我们使用的是zookeeper用来做我们的注册中心,所以createRegistry方法会调用ZookeeperRegistryFactory用来创建 ZookeeperRegistry,查看其初始化方法,主要是初始化zookeeper根目录信息 以及初始化zk连接。

    public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
        super(url);
        if (url.isAnyHost()) {
            throw new IllegalStateException("registry address == null");
        }
        String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
        if (!group.startsWith(PATH_SEPARATOR)) {
            group = PATH_SEPARATOR + group;
        }
        this.root = group;
        zkClient = zookeeperTransporter.connect(url);
        zkClient.addStateListener((state) -> {
           .. ...
        });
    }

四、ZookeeperRegistry.doRegister

    @Override
    public void doRegister(URL url) {
         zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
    }
    private String toUrlPath(URL url) {
        return toCategoryPath(url) + PATH_SEPARATOR + URL.encode(url.toFullString());
    }

doRegister方法,最终用来执行具体的注册动作,可以看到首先调用toUrlPath方法把url方法进行转化。 转换后的url由两部分组成,一部分是categoryPath,以我本地的demo为例,内容如下:

/dubbo/org.apache.dubbo.demo.DemoService/providers/

另一部分则是对url.fullString字段做了encode,fullString原始信息如下

dubbo://192.168.31.38:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=6129&release=&side=provider&timestamp=1661044979323

调用zkClient.create执行注册,方法内部其实是个递归实现,首先根据标志位“/”对categoryPath进行截取,依创建/dubbo、/dubbo/org.apache.dubbo.demo.DemoService、/dubbo/org.apache.dubbo.demo.DemoService/providers/目录,最终在providers目录下添加categoryPath+encode(fullString)信息。

执行完上面代码后,使用本地的zk/bin/zkCli.sh 命令查看zk节点状态,确实如上述所示。

image.png

五、总结

本文只要分析了dubbo服务启动后,注册到中心的过程,只是分析了zk的实现,dubbo其实提供了丰富的组册中心支持 image.png 相关代码都在dubbo-regisry目录下,可以看到,dubbo其实支持了etcd、redis、nacos、eureka等等,对其他实现有兴趣的同学可以自行查看其实现哈~