1、zookeeper中注册的数据
我们一般使用zookeeper作为dubbo的注册中心,但是我们注册到dubbo中的数据是什么样的呢,这里我们就来看一下:
1、初始时zookeeper中的数据:
这是zookeeper中只有testRoot 和zookeeper两个路径
2、我们启动dubbo的提供者和消费者:
消费者:
3、查看zookeeper中注册的信息
下面是我们将获取的两段url解码后的信息:
consumer:
consumer://192.168.5.1/cn.zsm.dubbo.service.PersonService?
application=dubboConsumer&category=consumers&check=false&default.timeout=1000&dubbo=2.6.2
&interface=cn.zsm.dubbo.service.PersonService&methods=getStudent&pid=32092&retries=3
&revision=2.0.0&side=consumer×tamp=1621514797507&version=2.0.0
provider:
dubbo://192.168.16.1:20880/cn.zsm.dubbo.service.PersonService?
anyhost=false&application=dubboProvider&default.timeout=4000&dubbo=2.6.2&generic=false
&interface=cn.zsm.dubbo.service.PersonService&methods=getStudent&pid=36184&revision=2.0.0
&side=provider×tamp=1621514737703&version=2.0.0,
dubbo://192.168.16.1:20880/cn.zsm.dubbo.service.PersonService?
anyhost=false&application=dubboProvider&default.timeout=4000&dubbo=2.6.2&generic=false
&interface=cn.zsm.dubbo.service.PersonService&methods=getStudent&pid=36184&retries=2
&revision=1.0.0&side=provider×tamp=1621514738394&version=1.0.0
从上面我们可以看出,zookeeper中服务的目录结构大概为:
从图中我们可以知道:
- 服务提供者在providers目录下进行注册
- 服务消费者会在consumers目录下进行注册,并监听providers目录,通过箭筒提供者的增加或减少,实现服务发现
- Monitor模块会对整个服务级别进行监听,用来统计整体的服务情况。
2、服务注册流程
2.1 服务注册的整个流程大致如下图所示:
- ServiceConfig类,获取到对外提供服务的类ref(如我们上边提到的:PersionServiceImpl类)
- 通过ProxyFactory接口实现类中的 getInvoker 方法,使ref生成一个 AbstractProxyInvoker 实例, 完成ref 到invoker的转换
- 将转换后得到的invoker, 再转换得到Exporter。(这一步很关键,我们重点来看这步转换过程)
这一切操作都是在ServiceConfig中进行的,我们就来进入到ServiceConfig中看一下。
2.2 ServiceConfig 源码解读
ServiceConfig中有三个比较重要的属性: protocol(协议)、 ProxyFactory(代理工厂)、 ref(业务逻辑类,这里是泛型)
按照上面我们说的服务注册流程,ServiceConfig先获取ref,通过ProxyFactory 获取invoker, 再将invoker转换成 exporter 。 后面这两步都是在doExportUrlsFor1Protocol (执行一种协议下的url)方法中完成的。从下面的截图中我们可以看到这个方法很长,接近200行, 但是它的大部分篇幅都是用来封装一个map集合、拼接URL串。
下面我们debug模式启动服务提供者,看看服务注册的过程:
我们在proxyFactory 将ref转换成invoker处打个断点,看看这之前程序都做了些什么:
前面的一百多行代码,实际处理的事情主要是拼装一个map集合, 还有拼接URL信息。
处理ref 、invoker 、 exporter的代码只有图中红色框内部分:
- proxyFactory将ref 转换成invoker
- invoker 被封装成 DelegateProviderMetaDataInvoder
- 将封装后的invoker 转换成一个exporter (这一步是转换是服务注册的主要步骤)
下面我们重点来看看invoker是如何被转换成exporter的。
Exporter<?> exporter = protocol.export(wrapperInvoker);
(1) potocol的扩展点
从potocol属性我们知道,该属性是通过SPI动态扩展的, 那么potocol都有哪些实现类:
从图中我们可以看出,potocol有11个扩展点,那么这里应用的扩展点是哪一个怎么判断??
dubbo的SPI配置中,可以根据URL信息进行选择扩展点的,那么我们只需要知道这里使用的URL信息即可:
从上图我们可以看到这里使用的事registry协议, 所以我们可以认定,这里使用的是 : RegistryProtocol的export方法
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 获取注册中心地址
URL registryUrl = getRegistryUrl(originInvoker);
// 服务提供者的注册地址
URL providerUrl = getProviderUrl(originInvoker);
// 获取进行注册override协议的访问地址
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
// 增加override 的监听器
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// 根据现有的override协议, 对注册地址进行改写操作
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// 导出当前服务, 完成后即可在本地20880端口号启动,且暴露服务
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// 获取注册中心实例 和 注册到注册中心的地址, 一般是 ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// 获取当前url是否需要进行注册参数
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) { // 注册服务
registry.register(registeredProviderUrl);
}
// 在服务提供者的model上注册声明的url
registerStatedUrl(registryUrl, registeredProviderUrl, register);
// 设置当前导出中的相关信息
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
// 注册override协议, 用于适配2.6.x 及之前的版本
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
notifyExport(exporter);
// 返回导出对象
return new DestroyableExporter<>(exporter);
}
这里我们重点看一下服务注册的方法:
if (register) { // 注册服务
registry.register(registeredProviderUrl);
}
Registry接口及实现类:
我们这里用的是zookeeper作为注册中心,所以这里使用的是 ZookeeperRegistry , 但是ZookeeperRegistry 中没有实现重写register方法,所以这里调用的事它的父类中的register方法:
FailbackRegistry 的register方法:
这里有一个doRegister方法, 这个方法在 FailbackRegistry 类中是抽象方法,实际调用的是ZookeeperRegistry 中的实现:
@Override
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
到这里服务注册的流程已经介绍完毕。
3、URL和本地缓存
3.1 URL规则详解:
URL的格式:
potocol://host:port/path?key=val&key=val
如下面我们启动服务提供者时的url:
dubbo://192.168.16.1:20880/cn.zsm.dubbo.service.PersonService?
anyhost=false&application=dubboProvider&bind.ip=192.168.16.1&bind.port=20880&default.
timeout=4000&dubbo=2.6.2&generic=false&interface=cn.zsm.dubbo.service.PersonService
&methods=getStudent&pid=49148&revision=2.0.0&side=provider×tamp=1621585834888
&version=2.0.0
URL 的结构及说明:
- protocol :协议, 一般像我们的provider、consume 着这里是认为具体的协议
- host : 当前provider或其他协议的具体地址, 边角特殊的像override协议所指定的host是0.0.0.0代表所有机器生效
- port: 代表处理的端口号
- path: 服务路径,在provider和consumer中代表我们真实地业务接口
- key=value: 代表服务的一些参数,也可以理解为对这个服务的配置
需要注意的事,dubbo中的URL与java中的URL是有一些区别的, 如:
- dubbo提供了针对参数的parameter的增加和减少,支持动态更改
- dubbo提供了缓存功能,对一些基础数据做缓存
3.2 服务本地缓存
dubbo支持对服务路径的本地缓存。dubbo服务消费者通过注册中心注册信息,获取服务提供者,但如果拼单从zk中获取信息,可能会出现单点故障问题,这时dubbo就需要将提供者的信息缓存到本地。
dubbo在订阅注册中心的回调处理逻辑中,会保存服务提供者的信息到本地缓存文件中,有同步和异步两种方式, 以URL为维度进行全量保存。
dubbo在服务引用过程中会创建registry对象,并加载本地缓存文件,会有限订阅注册中心,订阅注册中心失败后,才会访问本地缓存文件内容来获取服务提供者的信息。
4、服务消费流程
服务消费过程:
- ReferenceConfig 使用protocol 调用refer 方法生成invoker实例
- ProxyFactory 将invoker实例转换成ref代理对象
- 调用ref代理对象中的方法,执行真实的业务逻辑操作
我们将服务注册的过程拿过来对比一下: 很容易发现,服务消费的过程就是服务注册过程的逆向操作过程
下面我们看一下服务消费过程的一些具体实现: ReferenceConfig
在ReferenceConfig类中我们可以发现在ServiceConfig中我们重点强调的三个属性, 如下图:
上面的这些过程都是在ReferenceConfig的 init() 方法中完成的,下面我们看一下这个init方法:
init方法也要近100行,这里我们只关注里面的创建代理对象 ref 的过程:
1、调用createProxy 方法
2、createProxy 方法:
2.1 、 REF_PROTOCOL.refer 方法生成invoker
2.2 、 PROXY_FACTORY.getProxy 方法生成代理对象
生成代理对象的底层逻辑有两种: JDK和Javassis