书接上文,前文主要从RegistryProtocol export方法一直分析到了NettyServer doOpen方法启动Netty服务整个流程,作为RPC服务的服务端,客户端又是怎样感知到自己应该和哪个服务端进行通信呢?答案就是:注册中心。服务启动后,服务端要将自己提供的服务信息,注册到对应的注册中心里,客户端通过注册对应的服务监听,当服务端信息发生变更时,由注册中心通知所有的客户端,从而可以感知到服务端信息的变更,本文,就带大家一起看下相关源码实现。
一、温故知新
看过前文的同学,想必对上图的流程应该还会有一些印象,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则是要注册的服务端信息,可以看下两者差异
三、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×tamp=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节点状态,确实如上述所示。
五、总结
本文只要分析了dubbo服务启动后,注册到中心的过程,只是分析了zk的实现,dubbo其实提供了丰富的组册中心支持
相关代码都在dubbo-regisry目录下,可以看到,dubbo其实支持了etcd、redis、nacos、eureka等等,对其他实现有兴趣的同学可以自行查看其实现哈~