Dubbo之服务暴露

前言

本文 Dubbo 使用版本2.7.5 Dubbo 通过使用dubbo:service配置或@service在解析完配置后进行服务暴露,供服务消费者消费。 Dubbo 的服务暴露有两种:

  • 远程暴露
  • 本地暴露

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

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

服务暴露流程

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

ProxyFactory 是动态代理,用来创建 Invoker 对象,实现代理使用JavassistProxyFactoryJdkProxyFactoryInvoker 是一个服务对象实例,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中。

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

上面是代码片段为暴露服务的核心,可以看到 scope 由三个值控制是否暴露和远程或本地暴露,默认远程和本地都暴露。 在远程调用中,分为使用注册中心暴露直接暴露(默认dubbo协议),它们之间的区别在url上:

无注册中心的直接暴露服务。 有注册中心的先创建注册中心,再得到 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 实例化对象。

1class InjvmExporter<t> extends AbstractExporter<t> { 2 3 private final String key; 4 5 private final Map<string, exporter<?>&gt; exporterMap; 6 7 InjvmExporter(Invoker<t> invoker, String key, Map<string, exporter<?>&gt; exporterMap) { 8 super(invoker); 9 this.key = key; 10 this.exporterMap = exporterMap; 11 exporterMap.put(key, this); 12 } 13 14 @Override 15 public void unexport() { 16 super.unexport(); 17 exporterMap.remove(key); 18 } 19 20}

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

最后

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

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

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

我的公众号

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )