vlambda博客
学习文章列表

Dubbo源码:搞定URL,就走完了进度条的一半

Dubbo 中的 URL

Dubbo 中任意的一个实现都可以抽象为一个 URL,Dubbo 使用 URL 来统一描述了所有对象和配置信息,并贯穿在整个 Dubbo 框架之中。


dubbo://172.17.32.91:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&dubbo=2.0.2&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=32508&release=&side=provider&timestamp=1593253404714dubbo://172.17.32.91:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&dubbo=2.0.2&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=32508&release=&side=provider&timestamp=1593253404714

这个 Demo Provider 注册到 ZooKeeper 上的 URL 信息,简单解析一下这个 URL 的各个部分:

  • protocol:dubbo 协议

  • username/password:没有用户名和密码

  • host/port:172.17.32.91:20880

  • path:org.apache.dubbo.demo.DemoService

  • parameters:参数键值对,这里是问号后面的参数


下面是 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");  }  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);  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-common 包中还提供了 URL 的辅助类:

  • URLBuilder, 辅助构造 URL

  • URLStrParser, 将字符串解析成 URL 对象

Dubbo源码:搞定URL,就走完了进度条的一半

URL 在 Dubbo 中被当作是“公共的契约”。一个 URL 可以包含非常多的扩展点参数,URL 作为上下文信息贯穿整个扩展点设计体系。


其实在 Dubbo 中使用 URL 的好处多多:

  • 代码更加易读、易懂,不用花大量时间去揣测传递数据的格式和含义,进而形成一个统一的规范

  • 作为方法的入参(相当于一个 Key/Value 都是 String 的 Map),含义比单个参数更丰富,当代码需要扩展的时候,可以将新的参数以 Key/Value 的形式追加到 URL 之中,而不需要改变入参或是返回值的结构

  • 可以省去很多沟通成本


URL 在 SPI 中的应用

Dubbo SPI 中有一个依赖 URL 的重要场景——适配器方法,是被 @Adaptive 注解标注的, URL 一个很重要的作用就是与 @Adaptive 注解一起选择合适的扩展实现类。


例如,在 dubbo-registry-api 模块中我们可以看到 RegistryFactory 这个接口,其中的 getRegistry() 方法上有 @Adaptive({"protocol"})  注解,说明这是一个适配器方法,Dubbo 在运行时会为其动态生成相应的 “$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;  // 尝试获取URL的Protocol,如果Protocol为空,则使用默认值"dubbo"  String extName = (url.getProtocol() == null ? "dubbo" :  url.getProtocol());  if (extName == null)  throw new IllegalStateException("...");  // 根据扩展名选择相应的扩展实现,Dubbo SPI的核心原理在下一课时深入分析  RegistryFactory extension = (RegistryFactory) ExtensionLoader  .getExtensionLoader(RegistryFactory.class)  .getExtension(extName);  return extension.getRegistry(arg0);  } }

我们会看到,在生成的 RegistryFactory$Adaptive 类中会自动实现 getRegistry() 方法,其中会根据 URL 的 Protocol 确定扩展名称,从而确定使用的具体扩展实现类。


我们可以找到 RegistryProtocol 这个类,并在其 getRegistry() 方法中打一个断点, 得到如下图所示的内容:

Dubbo源码:搞定URL,就走完了进度条的一半


这里传入的 registryUrl 值为:


zookeeper://127.0.0.1:2181/org.apache.dubbo...

那么在 RegistryFactory$Adaptive 中得到的扩展名称为 zookeeper,此次使用的 Registry 扩展实现类就是 ZookeeperRegistryFactory。


URL 在服务暴露中的应用

Provider 在启动时,会将自身暴露的服务注册到 ZooKeeper 上,来看 ZookeeperRegistry.doRegister() 方法,在其中打个断点,然后 Debug 启动 Provider,会得到下图:


URL 在服务订阅中的应用

Consumer 启动后会向注册中心进行订阅操作,并监听自己关注的 Provider。那 Consumer 是如何告诉注册中心自己关注哪些 Provider 呢?


我们来看 ZookeeperRegistry 这个实现类,它是由上面的 ZookeeperRegistryFactory 工厂类创建的 Registry 接口实现,其中的 doSubscribe() 方法是订阅操作的核心实现,在第 175 行打一个断点,并 Debug 启动 Demo 中 Consumer,会得到下图所示的内容:

可以看到传入的 URL 参数如下:

consumer://...?application=dubbo-demo-api-consumer&category=providers,configurators,routers&interface=org.apache.dubbo.demo.DemoService...

其中 Protocol 为 consumer ,表示是 Consumer 的订阅协议,其中的 category 参数表示要订阅的分类,这里要订阅 providers、configurators 以及 routers 三个分类;interface 参数表示订阅哪个服务接口,这里要订阅的是暴露 org.apache.dubbo.demo.DemoService 实现的 Provider。


通过 URL 中的上述参数,ZookeeperRegistry 会在 toCategoriesPath() 方法中将其整理成一个 ZooKeeper 路径,然后调用 zkClient 在其上添加监听。


推荐阅读:



勾勾的Java宇宙 发起了一个读者讨论 在其他框架或是实际工作中,有没有类似 Dubbo URL 这种统一的契约?