写点什么

Dubbo 中的统一契约是如何实现的?

用户头像
冰河
关注
发布于: 2021 年 01 月 22 日
Dubbo中的统一契约是如何实现的?

写在前面


之前,很多小伙伴私信我:如何才能快速的掌握 Dubbo 的核心原理和源码。所以,我写了一篇《我是如何在短期内快速掌握Dubbo的原理和源码的(纯干货)?》。对于 Dubbo 的源码解析系列文章,我也在思考如何让源码解析的文章变得更加简单易懂,所以,我调整了写 Dubbo 源码解析文章的策略,力求让小伙伴们能够以更简单、易懂的方式彻底掌握 Dubbo 源码。今天,我们先说说 Dubbo 中的统一契约是如何实现的。


文章已收录到:


https://github.com/sunshinelyz/technology-binghe


https://gitee.com/binghe001/technology-binghe


不得不说的 URL


URL 全称为统一资源定位符,它能够在互联网中定位到唯一的一个网络地址。URL 的格式如下所示。


protocol://username:password@host:port/path?key=value&key=value
复制代码


其中,各个部分的简要说明如下所示。


  • protocol:URL 的协议。最常见的协议就是 HTTP 和 HTTPS,其他的还有 FTP、WS、FILE、SMTP 等。

  • username:用户名。

  • password:密码。

  • host:主机,通常是域名或者 IP 地址。

  • port:主机的端口号。

  • path:请求的目标文件的路径。

  • parameters:请求的具体参数信息,这里为 key=value&key=value。


这就是我们互联网中的 URL 的简单说明。


那么,在 Dubbo 内部,大量的方法接收的参数都是以 URL 进行封装的,那么,URL 在 Dubbo 内部到底起到了什么作用呢?我们继续往下看。


Dubbo 中的 URL


总的来说,在 Dubbo 内部,服务提供者 Provider 会将自身的相关信息封装成 URL 注册到 Zookeeper 或其他注册中心中,从而对外暴露自己提供的服务。而服务消费者 Consumer 也会通过 URL 的形式向 Zookeeper 或其他注册中心订阅自己想要调用的服务。而在 Dubbo 的 SPI 实现中,URL 又会参与扩展实现的逻辑处理。所以说,URL 在 Dubbo 的实现中是非常重要的。也可以这么说,Dubbo 中的 URL 就是 Dubbo 的统一契约。


我们先来看一下 Dubbo 中的 URL 具体长什么样吧,通过调试 Dubbo 自带 Provider 的示例源码,我们可以看到在 Dubbo 中的 URL 如下所示。


dubbo://192.168.175.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=15012&release=&side=provider&timestamp=1610857629484
复制代码


这也是 Provider 注册到 Zookeeper 或者其他注册中心的信息。各个部分的说明如下所示。


  • dubbo:使用的是 dubbo 协议。

  • host:主机的 IP 地址为 192.168.175.1。

  • port:端口号为 20880。

  • path:这里的请求路径为:org.apache.dubbo.demo.DemoService

  • parameters:请求的参数信息,这里为:anyhost=true&application=dubbo-demo-annotation-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=15012&release=&side=provider&timestamp=1610857629484。


既然 Dubbo 是向 Zookeeper 或其他注册中心注册这些信息的,那 Dubbo 内部是如何对 URL 进行封装的呢。


在 dubbo-common 模块中,有一个 URL 类专门用于封装 URL,如下所示。



在 URL 类中,我们来看一个核心构造函数,如下所示。


public URL(String protocol,           String username,           String password,           String host,           int port,           String path,           Map<String, String> parameters,           Map<String, Map<String, String>> methodParameters) {    if (StringUtils.isEmpty(username)        && StringUtils.isNotEmpty(password)) {        throw new IllegalArgumentException("Invalid url, password without username!");    }    this.protocol = protocol;    this.username = username;    this.password = password;    this.host = host;    this.port = Math.max(port, 0);    this.address = getAddress(this.host, this.port);
// trim the beginning "/" while (path != null && path.startsWith("/")) { path = path.substring(1); } this.path = path; if (parameters == null) { parameters = new HashMap<>(); } else { parameters = new HashMap<>(parameters); } this.parameters = Collections.unmodifiableMap(parameters); this.methodParameters = Collections.unmodifiableMap(methodParameters);}
复制代码


可以看到,Dubbo 对于 URL 的核心封装,基本与互联网中的 URL 封装是一致的。


在 Dubbo 的 dubbo-common 模块提供了处理 URL 的工具类:URLBuilder 和 URLStrParser。如下所示。


这两个类的实现还是比较简单的,小伙伴们可以自行阅读 Dubbo 的源码。


接下来,我们一起来看看在 Dubbo 内部,URL 是如何实现统一契约的?


Dubbo 中 URL 的实际应用


这里,我们主要通过三方面来简单聊聊 URL 在 Dubbo 内部的实际应用:


  • URL 在 SPI 中的应用。

  • URL 在服务注册中的应用。

  • URL 在服务发现中的应用。


URL 在 SPI 中的应用


稍微了解过 Dubbo 的小伙伴都知道,Dubbo 具有高度的可扩展性,而这种扩展性是基于 Dubbo 自身的 SPI 来实现的。在 Dubbo 实现的 SPI 中,URL 又起到了非常重要的作用。


在 Dubbo SPI 的实现中,一个典型的场景就是被 @Adaptive 注解修饰的接口方法,例如,在 dubbo-registry-api 模块中的 RegistryFactory 接口中的 getRegistry()方法上被 @Adaptive({"protocol"})注解修饰。如下所示。



说明 RegistryFactory 接口中的 getRegistry()方法是一个适配器方法,Dubbo 在运行的过程中,会为 getRegistry()方法动态生成RegistryFactory$Adaptive类型。例如,生成的RegistryFactory$Adaptive类型如下所示。


public class RegistryFactory$Adaptive              implements RegistryFactory {     public Registry getRegistry(org.apache.dubbo.common.URL arg0) {         if (arg0 == null) throw new IllegalArgumentException("");         org.apache.dubbo.common.URL url = arg0;         String extName = (url.getProtocol() == null ? "dubbo" :  url.getProtocol());         if (extName == null)             throw new IllegalStateException("");         RegistryFactory extension = (RegistryFactory) ExtensionLoader.getExtensionLoader(RegistryFactory.class).getExtension(extName);         return extension.getRegistry(arg0);     } }
复制代码


这段代码相对来说还是比较容易理解的,生成的 RegistryFactory$Adaptive 会自动实现 getRegistry()方法,在 getRegistry()方法中,会获取 URL 中的 protocol 参数来确定 URL 的协议,如果获取的 protocol 为空,则使用默认的 dubbo 协议,有了这个协议,就能够通过 SPI 动态加载具体的扩展实现类。


我们在 Dubbo 的 dubbo-registry-api 模块中找到 RegistryProtocol 类,如下所示。



找到其中的 getRegistry()方法并打上断点,如下所示。



接下来,debug 启动 Dubbo 的 Provider 示例,如下所示。



可以看到,此时使用的 protocol 协议为 zookeeper。有关 Dubbo 中 SPI 的实现,我们后面再详细剖析,今天,小伙伴们有个大致的了解即可。


URL 在服务注册中的应用


在 Dubbo 中的服务注册实现中,URL 同样起到了非常重要的作用。这里,我使用的注册中心是 Zookeeper,所以,我们在 dubbo-registry-zookeeper 模块中找到 ZookeeperRegistry 类,如下所示。



找到其中的 doRegister()方法,打上断点,如下所示。



debug 启动 Dubbo 自带的 provider 示例,如下所示。



可以看到,在注册到 Zookeeper 中的 URL 中,包含了 protocol 协议、host 主机名、port 端口号、path 请求路径,parameters 参数等信息。


URL 在服务发现中的应用


Dubbo 中服务的消费者 Consumer 在启动时,会向 Zookeeper 注册中心订阅自身需要调用的服务,那具体是如何通过 URL 订阅的呢?我们同样在 dubbo-registry-zookeeper 模块中的 ZookeeperRegistry 类中找到 doSubscribe()方法。在 doSubscribe()方法中打上断点,如下所示。



启动 Dubbo 自带的 Consumer 示例,如下所示。



我们可以看到,Dubbo 的 Consumer 会向 Zookeeper 传入如下参数进行服务的订阅操作。


consumer://192.168.175.1/org.apache.dubbo.demo.DemoService?application=dubbo-demo-annotation-consumer&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=15184&side=consumer&sticky=false&timestamp=1610860963037
复制代码


其中的 protocol 为 consumer,表示订阅协议。category 表示要订阅的分类,这里是 providers,configurators,routers 三个分类。interface 表示要订阅的接口服务,这里是 org.apache.dubbo.demo.DemoService。methods 表示要订阅的方法,这里是 sayHello,sayHelloAsync。


还有一点需要注意的是:在服务注册的过程中,Dubbo 会将 URL 转化为 Zookeeper 路径将信息注册到 Zookeeper 中;在服务发现的过程中,Dubbo 会将 URL 转化为 Zookeeper 路径,从而监听 Zookeeper 目录的变化来订阅相关的服务。


总之,在 Dubbo 内部通过 URL 实现了统一的契约。你学会了吗?


推荐阅读



好了,今天就到这儿吧,我是冰河,大家有啥问题可以在下方留言,也可以加我微信:sun_shine_lyz,一起交流技术,一起进阶,一起牛逼~~


发布于: 2021 年 01 月 22 日阅读数: 20
用户头像

冰河

关注

公众号:冰河技术 2020.05.29 加入

Mykit系列开源框架发起者、核心架构师和开发者,《海量数据处理与大数据技术实战》与《MySQL开发、优化与运维实战》作者。【冰河技术】微信公众号作者。

评论

发布
暂无评论
Dubbo中的统一契约是如何实现的?