前言

本文 Dubbo 使用版本2.7.5

Dubbo 通过使用dubbo:service配置或@service在解析完配置后进行服务暴露,供服务消费者消费。

Dubbo 的服务暴露有两种:

  • 远程暴露
  • 本地暴露

可以通过scope 显式指定暴露方式:

  • none 不暴露
  • remote 远程暴露
  • local 本地暴露

服务暴露流程

下面是一个服务暴露的流程图:

ProxyFactory 是动态代理,用来创建 Invoker 对象,实现代理使用JavassistProxyFactoryJdkProxyFactory

Invoker 是一个服务对象实例,Dubbo 框架的实体域。它可以是一个本地的实现,一个远程的实现或一个集群的实现,可以向它发起 Invoker 调用。

Protocol 是服务域,负责 Invoker 的生命周期管理,是 Invoker 暴露和引用的主要功能入口,对应该类的exportrefer方法。

Exporter 是根据不同协议暴露 Invoker 进行封装的类,它会根据不同的协议头进行识别(比如:registry://dubbo://),调用对应XXXProtocolexport()方法。

从上图中可以看到,Dubbo 中服务暴露分为两个大步骤:第一步通过代理将服务实例转换成 Invoker,这就是通过我们常用的反射实现。第二步将 Invoker 根据具体的协议转换成 Exporter,这是就是我们要分析的核心。从这里可以看到 Dubbo 服务对象都是围绕 Invoker 进行工作。

远程暴露

服务远程暴露从字面上理解,就是将服务跨网络进行远程通信,并非同一 JVM 中的服务进行调用。

服务最后都是转换成org.apache.dubbo.config.spring.ServiceBean,它的UML类图:

ServiceBean继承自ServiceConfig,服务在ServiceConfig#doExportUrls根据不同协议进行暴露。

通过获取所有注册中心实例(registryURLs)后,进行依次暴露,暴露操作在doExportUrlsFor1Protocol中。

  1. private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
  2. Map<String, String> map = new HashMap<String, String>();
  3. // 配置信息存入 map
  4. .....
  5. // 获取服务URL
  6. String host = findConfigedHosts(protocolConfig, registryURLs, map);
  7. Integer port = findConfigedPorts(protocolConfig, name, map);
  8. URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
  9. .....
  10. String scope = url.getParameter(SCOPE_KEY);
  11. // 如果 scope 配置为 none,则服务不进行暴露
  12. if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
  13. // 本地暴露
  14. if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
  15. exportLocal(url);
  16. }
  17. // 远程暴露
  18. if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
  19. // 判断是否有注册中心
  20. if (CollectionUtils.isNotEmpty(registryURLs)) {
  21. for (URL registryURL : registryURLs) {
  22. //if protocol is only injvm ,not register
  23. if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
  24. continue;
  25. }
  26. url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
  27. // 获取监控URL
  28. URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
  29. if (monitorUrl != null) {
  30. // 追加监控上报地址,在拦截器上报数据
  31. url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
  32. }
  33. // 日志打印
  34. if (logger.isInfoEnabled()) {
  35. if (url.getParameter(REGISTER_KEY, true)) {
  36. logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
  37. } else {
  38. logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
  39. }
  40. }
  41. // For providers, this is used to enable custom proxy to generate invoker
  42. String proxy = url.getParameter(PROXY_KEY);
  43. if (StringUtils.isNotEmpty(proxy)) {
  44. registryURL = registryURL.addParameter(PROXY_KEY, proxy);
  45. }
  46. // 将服务对象转换成 Invoker
  47. Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
  48. DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
  49. // 暴露服务,向注册中心注册服务,进入对应的 RegistryProtocol
  50. Exporter<?> exporter = protocol.export(wrapperInvoker);
  51. exporters.add(exporter);
  52. }
  53. } else { // 没有注册中心时
  54. if (logger.isInfoEnabled()) {
  55. logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
  56. }
  57. Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
  58. DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
  59. // 直接暴露服务
  60. Exporter<?> exporter = protocol.export(wrapperInvoker);
  61. exporters.add(exporter);
  62. }
  63. /**
  64. * 存储Dubbo服务的元数据,元数据可以存储在远端配置中心和本地,默认是存储在本地
  65. * @since 2.7.0
  66. * ServiceData Store
  67. */
  68. WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));
  69. if (metadataService != null) {
  70. metadataService.publishServiceDefinition(url);
  71. }
  72. }
  73. }
  74. this.urls.add(url);
  75. }

上面是代码片段为暴露服务的核心,可以看到 scope 由三个值控制是否暴露和远程或本地暴露,默认远程和本地都暴露。

在远程调用中,分为使用注册中心暴露直接暴露(默认dubbo协议),它们之间的区别在url上:

  • 无注册中心:dubbo://192.168.3.19:20880/xxxx
  • 有注册中心:registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=provider&dubbo=2.0.2&export=dubbo://192.168.3.19:20880/xxxx

无注册中心的直接暴露服务。

有注册中心的先创建注册中心,再得到 export 的服务地址,然后暴露服务,当服务暴露成功后把服务元数据注册到注册中心。

代码中protocol#export会根据服务 url 的请求头进行区分不同XXXProtocol#export的逻辑,比如。

目前 Dubbo 中有以下几种:

本地暴露

同一个应用中,可能既要提供服务远程暴露给其他应用引用,也要给自身提供引用。如果只提供远程暴露的话,当自身应用需要引用自身的服务时,需要通过远程通信访问,那么这大大浪费网络资源。这是就需要用 injvm 协议暴露,就是我们所说的本地暴露,无需跨网络远程通信,可以更好的节省资源。

通过上面代码中,我们知道本地暴露调用的是ServiceConfig#exportLocal方法。

本地暴露会指定 injvm 协议,并且 host 指定为本地127.0.0.1和端口号为0。

protocol.export 调用 InjvmProtocol#export 实现:

  1. @Override
  2. public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
  3. return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
  4. }

export 中返回了 InjvmExporter 实例化对象。

  1. class InjvmExporter<T> extends AbstractExporter<T> {
  2. private final String key;
  3. private final Map<String, Exporter<?>> exporterMap;
  4. InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
  5. super(invoker);
  6. this.key = key;
  7. this.exporterMap = exporterMap;
  8. exporterMap.put(key, this);
  9. }
  10. @Override
  11. public void unexport() {
  12. super.unexport();
  13. exporterMap.remove(key);
  14. }
  15. }

本地暴露就比较简单,将 Invoker 直接保存在 InjvmExporter 的 exporterMap 中。

最后

本篇对 Dubbo 的服务暴露流程进行了分析,核心点就是开篇图中的得到 Invoker 后转化到 Export。其中更多详细的地方,由于展开后篇幅太大,不能一一写到,会在今后相关的 Dubbo 文章再进行讲解。

个人博客: https://ytao.top

关注公众号 【ytao】,更多原创好文

Dubbo之服务暴露的更多相关文章

  1. dubbo服务暴露过程

    所谓服务暴露最终做的事情:绑定网络端口,开启serversokect服务以接收外部请求 服务暴露时序图 本地暴露 远程暴露 整体总结 dubbo服务提供者暴露服务的主过程:首先 ServiceConf ...

  2. Dubbo服务暴露分析

    Dubbo的服务暴露是一个重要的特性,了解其机制很重要.之前有很多人写了有关的源代码分析,在本文中不再重新分析.官方文档中的一篇写的就很好,本文主要是有关内容进行补充与总结. 传送门:服务导出 为什么 ...

  3. dubbo服务暴露原理-远程暴露

    1.与本地暴露相比,远程暴露也大同小异 我们已经到了第三个关键词Procotol我们来看看他的继承体系图 按照经典图的路线,我们下一个关键词应该就是Server了,从方法名openServer(url ...

  4. dubbo服务暴露原理

    1.发布流程 暴露本地服务 暴露远程服务 启动netty 连接zookeeper 到zookeeper注册 监听zookeeper 2.官方文档 3.看输出日志,就会发现在暴露本地服务之前,有一句很重 ...

  5. 7.1 服务暴露前的准备-ServiceBean的装配

    dubbo的服务暴露以第一章 第一个dubbo项目中的dubbo-demo-provider来讲述. 列出dubbo-demo-provider的xml配置: <?xml version=&qu ...

  6. Dubbo之服务注册

    在上一篇文章Dubbo之服务暴露分析中介绍了当远程暴露时,如果有注册中心,需要在服务暴露后再将服务注册到注册中心.该篇将介绍该功能的有关步骤. 注册的起点 在RegistryProtocol.expo ...

  7. Dubbo之服务消费原理

    前言 上篇文章<Dubbo之服务暴露>分析 Dubbo 服务是如何暴露的,本文接着分析 Dubbo 服务的消费流程.主要从以下几个方面进行分析:注册中心的暴露:通过注册中心进行服务消费通知 ...

  8. Dubbo 服务暴露注册流程

    Dubbo的应用会在启动时完成服务注册或订阅(不论是生产者,还是消费者)如下图所示. 图中小方块Protocol, Cluster, Proxy, Service, Container, Regist ...

  9. dubbo服务暴露

    想熟悉dubbo源码,首先要知道dubbo extensionLoader,而dubbo的这种扩展机制,是根据java spi衍生而来. 这是基础,但是我放在后面说明. 一:dubbo demo pr ...

随机推荐

  1. apache和tomcat的关系

    apache和tomcat的关系: 举个例子:apache是一辆卡车,上面可以装一些东西如html等.但是不能装水,要装水必须要有容器(桶),tomcat就是一个桶(装像JAVA这样的水),而这个桶也 ...

  2. 关于php自学

    自己本人现在正在自学php有一段时间了,不知道现在的学习状态咋样,在我看来应该属于不算很糟糕,但有点糟糕的状态. 如果算学习自学php的话,现在断断续续应该是有5个月了,按理说是差不多可以做出独立项目 ...

  3. tfjs-node初体验:训练模型的存储

    JS,一门从浏览器兴起,却不止于浏览器的脚本,个人一直认为其是最有潜力的脚本语言.不只是因为ES6优雅的语法,更重要的是其易上手,跨平台的优点. Node将JS从browser带去了client是革命 ...

  4. MySQL数据类型(DATA Type)与数据恢复与备份方法

    一.数据类型(DATA Type)概述 MySQL支持多种类型的SQL数据类型:数字类型,日期和时间类型,字符串(字符和字节)类型以及空间类型 数据类型描述使用以下约定: M表示整数类型的最大显示宽度 ...

  5. HexoC++第04课 构造析构.md

    C++第04课 构造析构.mdhtml {overflow-x: initial !important;}#write, body { height: auto; } #write, #write h ...

  6. js javascript 获取url,获得当前页面的url,静态html文件js读取url参数

    获得当前页面的url window.location.href 静态html文件js读取url参数 location.search; //获取url中"?"符后的字串 下边为转载的 ...

  7. MobX中@computed和自定义get函数的区别

    首先这两者解决方法都会得到一个相同的结果,但使用@computed的意义在于它能够由MobX进行更智能的优化. 如果我不使用computed属性,直接使用自定义的getTheValue函数的话,那么一 ...

  8. html常用事件

    1.onblur 当窗口失去焦点时运行 2.click 点击鼠标触发的事件 3.onfocus 当窗口获得焦点时运行 4.oninput 当元素获得用户输入时运行 5.onsubmit 当提交表单时运 ...

  9. Tortoises SVN 教程

    1.  TortoiseSVN 简介 版本控制是管理信息修改的艺术,它一直是程序员最重要的工具,程序员经常会花时间作出小的修改,然后又在某一天取消了这些修改,想象一下一个开发者并行工作的团队 - 或许 ...

  10. 重启aliyun esc 需要重新启动redis

    /export/sorftware ./redis-server ../redis.conf redis-server  配置路径 如redis-server  /etc/redis.conf 不是后 ...