当前位置: 首页 > news >正文

dubbo调用service后返回对象null_你玩了3年Dubbo,给我聊聊它的服务暴露过程?

bac8a44393a1513b946083500c148185.png

# 前言

本文 Dubbo 使用版本 2.7.5

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

Dubbo 的服务暴露有两种:

  • 远程暴露

  • 本地暴露

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

  • none 不暴露

  • remote 远程暴露

  • local 本地暴露

# 服务暴露流程

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

a286a2841b660507a69ad7f9eafd343e.png

ProxyFactory 是动态代理,用来创建 Invoker 对象,实现代理使用 JavassistProxyFactory和 JdkProxyFactory。

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

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

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

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

# 远程暴露

服务远程暴露从字面上理解,就是将服务跨网络进行远程通信,并非同一 JVM 中的服务进行调用。服务最后都是转换成 org.apache.dubbo.config.spring.ServiceBean,它的UML类图:

ac1ba01813d728b07eb3f06ecc5f9842.png

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

75ec6f98e988d64a64b55bf2835f0bf3.png

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

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {Map<String, String> map = new HashMap<String, String>();// 配置信息存入 map.....// 获取服务URLString host = findConfigedHosts(protocolConfig, registryURLs, map);Integer port = findConfigedPorts(protocolConfig, name, map);    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);.....String scope = url.getParameter(SCOPE_KEY);// 如果 scope 配置为 none,则服务不进行暴露if (!SCOPE_NONE.equalsIgnoreCase(scope)) {// 本地暴露if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {            exportLocal(url);}// 远程暴露if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {// 判断是否有注册中心if (CollectionUtils.isNotEmpty(registryURLs)) {for (URL registryURL : registryURLs) {//if protocol is only injvm ,not registerif (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {continue;}                    url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));// 获取监控URL                    URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);if (monitorUrl != null) {// 追加监控上报地址,在拦截器上报数据                        url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());}// 日志打印if (logger.isInfoEnabled()) {if (url.getParameter(REGISTER_KEY, true)) {                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);} else {                            logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);}}// For providers, this is used to enable custom proxy to generate invokerString proxy = url.getParameter(PROXY_KEY);if (StringUtils.isNotEmpty(proxy)) {                        registryURL = registryURL.addParameter(PROXY_KEY, proxy);}// 将服务对象转换成 InvokerInvoker> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);// 暴露服务,向注册中心注册服务,进入对应的 RegistryProtocolExporter> exporter = protocol.export(wrapperInvoker);                    exporters.add(exporter);}} else {    // 没有注册中心时if (logger.isInfoEnabled()) {                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);}Invoker> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);// 直接暴露服务Exporter> exporter = protocol.export(wrapperInvoker);                exporters.add(exporter);}/**             * 存储Dubbo服务的元数据,元数据可以存储在远端配置中心和本地,默认是存储在本地             * @since 2.7.0             * ServiceData Store             */WritableMetadataService metadataService = WritableMetadataService.getExtension(url.getParameter(METADATA_KEY, DEFAULT_METADATA_STORAGE_TYPE));if (metadataService != null) {                metadataService.publishServiceDefinition(url);}}}this.urls.add(url);}

上面是代码片段为暴露服务的核心,可以看到 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 中有以下几种:

1ccfcd5c83b4087b1773362ac715ad54.png

# 本地暴露

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

2c9b06756b52317d027e644c3493564d.png

本地暴露会指定 injvm 协议,并且 host 指定为本地 127.0.0.1和端口号为0。protocol.export 调用 InjvmProtocol#export 实现:

@Overridepublic Exporterexport(Invoker invoker) throws RpcException {return new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);}

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

class InjvmExporter<T> extends AbstractExporter<T> {private final String key;private final Map> exporterMap;InjvmExporter(Invoker invoker, String key, Map> exporterMap) {super(invoker);this.key = key;this.exporterMap = exporterMap;        exporterMap.put(key, this);}@Overridepublic void unexport() {super.unexport();        exporterMap.remove(key);}}

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

# 最后

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

 往期推荐 

?

  • 艹,我的日志被Intellij IDEA 控制台给“吃”了!
  • 常用Maven插件终极大全,速度收藏!
  • 爱“挖坑”的面试官:单台服务器并发TCP连接数到底可以有多少 ?

da9ec58a976b8ed24cf1b022da598675.png

779cbbcc65a2e61be359ee4babb35716.gif 

点击

相关文章:

  • 托福试卷真题_托福考试历年真题测试
  • vscode安装旧版本插件_vscode 常用插件安装
  • 联想笔记本键盘排线_笔记本排线坏了怎么修?笔记本排线接触不良怎么办?
  • eclipse导入mysql8.0驱动_eclipse jdbc连接MySQL8.0数据库详解
  • arcgis导入excel字段不显示_ArcGis属性字段操作技巧(上下标显示、批量删除)
  • 筛数方法相关系数_Spearman相关系数的变量筛选方法
  • miui删除内置不卡米教程_小米MIUI免ROOT一键删除系统内置软件
  • ffmpeg 录制指定窗口_音视频最简单基础知识一篇文章带你入门《ffmpeg干货》
  • es6 filter函数的用法_ES6新增数组方法
  • 记录方法用时_发明专利|一种微电网继电保护方法
  • docker容器访问sqlserver_Docker最全教程之MySQL容器化 (二十五)
  • 如何判断手机型号是5s_北斗导航如何启用?需要硬件支持,这些手机型号都可以使用...
  • 刷新报表_润乾报表 dashboard 分析
  • cgo 数据_年度数据分析报告,如何写出“年”味
  • arraylist扩容是创建新数组吗 java_深入了解ArrayList
  • Bytom交易说明(账户管理模式)
  • Golang-长连接-状态推送
  • java第三方包学习之lombok
  • Java基本数据类型之Number
  • LintCode 31. partitionArray 数组划分
  • MySQL的数据类型
  • node-glob通配符
  • rc-form之最单纯情况
  • windows下如何用phpstorm同步测试服务器
  • 好的网址,关于.net 4.0 ,vs 2010
  • 模型微调
  • 译有关态射的一切
  • 容器镜像
  • ​Python 3 新特性:类型注解
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • ​你们这样子,耽误我的工作进度怎么办?
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (机器学习-深度学习快速入门)第一章第一节:Python环境和数据分析
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (转)程序员技术练级攻略
  • (转)创业家杂志:UCWEB天使第一步
  • (转载)利用webkit抓取动态网页和链接
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • .NET 事件模型教程(二)
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • .NET精简框架的“无法找到资源程序集”异常释疑
  • .net下的富文本编辑器FCKeditor的配置方法
  • @RunWith注解作用
  • [ MSF使用实例 ] 利用永恒之蓝(MS17-010)漏洞导致windows靶机蓝屏并获取靶机权限
  • [ccc3.0][数字钥匙] UWB配置和使用(二)
  • [DM复习]Apriori算法-国会投票记录关联规则挖掘(上)
  • [JavaScript]_[初级]_[不使用JQuery原生Ajax提交表单文件并监听进度]
  • [JS入门到进阶] 哎,被vite小坑了一波,大家记得配置build.cssTarget为‘chrome61‘
  • [LeetCode 127] - 单词梯(Word Ladder)
  • [Mac软件]Goldie App v2.2 Mac黄金比例设计工具
  • [NLP] LlaMa2模型运行在Mac机器
  • [pluginviteimport-analysis] vite 提示jsx语法报错
  • [poj] 3422 Kaka's Matrix Travels || 最小费用最大流