前言

在之前我们讲过Spring和Dubbo的集成,我们在服务上标注了@Reference的注解,然后最终Dubbo会调用到ReferenceBean#get方法中来返回一个代理对象,本次我们就来剖析下服务引用的全流程。

一、前置回顾

在ReferenceAnnotationBeanPostProcesso#doGetInjectedBean方法中会对属性上标注了@Reference注解生成一个代理对象

这个代理对象绑定了ReferenceBeanInvocationHandler,那么最终执行目标方法会执行到ReferenceBeanInvocationHandler#invoke方法。

这里invoke方法中会首先调用ReferenceBean#get方法再返回一个代理对象,然后通过反射调用其对应方法,ReferenceBean#get方法会调用到其父类ReferenceConfig#get方法,然后我们本次来剖析ReferenceConfig#get方法流程。

二、服务引入

2.1、ReferenceConfig#get

  1. 首先该方法使用synchronized修饰,防止并发冲突
  2. 然后判断销毁标识,判断ref是否为空,这个ref就是其实现类,就是对应的代理对象。
  3. 如果ref为空则调用init方法进行初始化,不为空则直接返回ref对象。

2.2、ReferenceConfig#init

  1. 判断是否初始化过,已经初始化了直接返回,然后检查interfaceName是否为空,为空抛出异常
  2. checkDefault检查consumer是否为空,为空就创建并给空属性填充值
  3. 调用appendProperties方法,填充ReferenceConfig中为空的属性
  4. 如果当前ReferenceConfig中没有设置泛化标识,则从ConsumerConfig中获取并设置到ReferenceConfig
  5. 判断是不是泛化调用,是的话则把interfaceClass设置成GenericService.class,不是则加载interfaceClass,并检查method引用的方法是否在interface指定的接口中存在
  6. 从系统属性中获取该接口的直连服务提供者,如果存在 -Dinterface=dubbo://127.0.0.1:20880,其中interface为dubbo:reference interface属性的值。
  7. 如果未指定-D属性,则尝试从resolve配置文件中查找,首先尝试获取resolve配置文件的路径,其来源可以通过-Ddubbo.resolve.file=文件路径名来指定,如果未配置该系统参数,则默认从${user.home}/dubbo-resolve.properties,如果过文件存在,则设置resolveFile的值,否则resolveFile为null。
  8. 如果resolveFile不为空,则加载resolveFile文件中内容,然后通过interface获取其配置的直连服务提供者URL。
  9. 如果resolve不为空,则填充ReferenceBean的url属性为resolve(点对点服务提供者URL),打印日志,点对点URL的来源(系统属性、resolve配置文件)。
  10. 如果当前ReferenceConfig的application、module、registries…如果为空的话,判断如果判断如果ConsumerConfig不为空的话,则从consumer中获取赋值,如果从consumer中没有获取到,再尝试从moduleConfig、ApplicationConfig中获取。
  11. 校验ReferenceBean的application是否为空,如果为空,new 一个application,并尝试加载资源填充其属性
  12. 校验stub、mock实现类与interface的兼容性,这个在服务导出的时候已经分析过了
  13. 构建Map,封装服务消费者引用服务提供者URL的属性,这里主要填充side:consume(消费端)、dubbo:2.0.0(版本)、timestamp、pid:进程ID。
  14. 判断如果不是泛化调用,增加interface下的所有方法名,多个用逗号隔开,填充到map中
  15. 往map中填充application配置、module配置,默认消费者参数(ConsumerConfig)、服务消费者dubbo:reference的属性。
  16. 获取服务键值 /{group}/interface:版本,如果group为空,则为interface:版本,其值存为prifex,然后将dubbo:method的属性名称也填入map中,键前缀为dubbo.method.methodname.属性名。dubbo:method的子标签dubbo:argument标签的属性也追加到attributes map中,键为 prifex + methodname.属性名。
  17. 拼接上注册中心的ip
  18. 把属性也存储在系统上下文中
  19. 创建代理对象
  20. 将服务消费者缓存在ApplicationModel中



2.3、ReferenceConfig#createProxy

  1. 首先判断消费者引用的服务是不是本JVM内的服务
  2. 如果消费者引用的是本地JVM中的服务,这里的url中的protocol就是injvm,那么就会调用InjvmProtocol#refer方法构造一个InjvmInvoker。
  3. 判断如果是直连的情况,那么对直连的url按照逗号切割,如果url中不包含path属性,则给URL设置path属性为interfaceName,如果直连提供者的协议为registry,则对url增加refer属性,其值为消息消费者所有的属性。(表示从注册中心发现服务提供者),如果是其他协议提供者,则合并服务提供者与消息消费者的属性,并移除服务提供者默认属性。以default开头的属性。
  4. 如果不是直连的,则获取注册中心的所有url,然后根据注册中心url构建监控中心url,如果监控中心不为空,则在注册中心url后增加属性monitor,在注册中心中增加属性refer,值为消费端的所有配置组成的url。
  5. 根据url是一个还是多个,多个url就循环调用refprotocol#refer来获取invoker,然后再调用Cluster#join合并到一起返回,一个就直接调用refprotocol#refer获取invoker了,这里因为我们的协议是registry,所以最终会调用到RegistryProtocol#refer,但是由于wrapper机制,所以这里拿到的是经过几层包装的:Listener -> Filter -> Qos -> RegistryProtocol,会依次调用对应的refer方法。
  6. 调用proxyFactory#getProxy创建代理类



2.4、refprotocol#refer

2.4.1、QosProtocolWrapper#refer

  1. 首先判断协议是否是registry,我们这里是满足的,所以会调用startQosServer启动QOS服务。
  2. 调用ProtocolFilterWrapper#refer方法


2.4.2、ProtocolFilterWrapper#refer

  1. 判断协议是否是registry,我们这里是满足的,所以不会往下执行,直接调用ProtocolListenerWrapper#refer方法

2.4.3、ProtocolListenerWrapper#refer

  1. 判断协议是否是registry,我们这里是满足的,所以不会往下执行,直接调用RegistryProtocol#refer方法

2.4.3、RegistryProtocol#refer

  1. url 中parameters 里面的registry的值获取出来,如果你是用zk作为注册中心,获取出来的就是zookeeper,然后设置到url的protocol属性上,之后就是将parameters 的 registry remove掉了,这时候url的protocol 就是zookeeper了
  2. 就是根据 url的protocol来获取RegistryFactory 的实现类,也就是ZookeeperRegistryFactory ,然后就是调用它的getRegistry()方法,获取Registry ,总而言之就是获取 注册中心的Registry 对象。
  3. 解析将url中refer 属性值取出来,然后解析成map,其实这个refer就是在ReferenceConfig#createProxy 中塞进去的,就是一些属性,然后获取group 属性,进行分组。
  4. 调用doRefer方法生成Invoker对象

2.4.4、RegistryProtocol#doRefer

  1. 首先是创建了个RegistryDirectory 对象,这个对象非常重要,Directory 是目录的意思,而这里就是多个invoker,这里维护了invoker列表,而且这个列表是会根据注册中心变动的。
  2. 构造消费者的url并注册到注册中上
  3. 调用 directory.subscribe 的方法进行订阅providers、configurators、routers 等节点数据
  4. 调用cluster.join(directory); 获取invoker ,这个cluster 是dubbo spi注入进来的。

2.4.5、RegistryDirectory#subscribe

这里面调用的是FailbackRegistry.subscribe

  1. 调用父类的订阅方法缓存订阅信息
  2. 从失败订阅列表中移除当前订阅,因为现在重新开始进行订阅了
  3. 调用doSubscribe进行订阅
  4. 如果订阅发生异常则将失败的订阅记录到失败列表中,定时进行重试

这里会调用到ZookeeperRegistry的doSubscribe方法,主要做了几件事:

  1. 在zk中添加节点和节点监听器
  2. notify通知消费者去创建跟服务提供者之间的TCP连接


因为我们的RegistryDirectory已经订阅了这个路径,所以上面的那个notify方法会调用到RegistryDirectory#notify方法中,在方法中最终会把服务提供者的urls全转成Invoker对象


这里可以看到通过RegistryDirectory.toInvokers方法触发了Protocol@Adaptive.refer()-------->QosProtocolWrapper.refer()-------->ProtocolFilterWrapper.refer()-------->ProtocolListenerWrapper.refer()-------->DubboProtocol.refer()

2.4.5、DubboProtocol#refer

1.创建了DubboInvoker并返回,这里在创建的时候会调用getClients方法和服务端建立连接。

2.4.6、DubboProtocol#getClients

1.获取交换客户端ExchangeClient,默认使用共享链接,这里注意下

  • 如果connections不配置,则共享连接,否则每服务每连接,
  • 共享连接的意思是对于同一个ip+port的所有服务只创建一个连接,
  • 如果是非共享连接则每个服务+(ip+port)创建一个连接

1.以address(ip:port)为key进行缓存

  • 如果连接存在了则引用数加1,引用数表示有多少个服务使用了此client
  • 当某个client调用close()时,引用数减一
  • 如果引用数大于0,表示还有服务在使用此连接, 不会真正关闭client
  • 如果引用数为0,表示没有服务在用此连接,此时连接彻底关闭
  1. 根据URL初始化exchangeClient
  2. 得到referenceCountExchangeClient
  3. 把这些client保存到MAP中

逻辑如下:
1.设置client的类型,默认是netty,设置codec,默认是dubboCodec,默认开启心跳60S发一次包
2.如果Connection不是懒加载,那就立马创建跟服务提供方的connection,这里大家可以跟一下,最终会调用到AbstractClientl来建立和server的连接。

2.5、proxyFactory#getProxy创建代理对象

最终会调用到JavassistProxyFactory#getProxy方法:
1.通过 Proxy 的 getProxy 方法获取 Proxy 子类
2.创建 InvokerInvocationHandler 对象,并将该对象传给 newInstance 生成 Proxy 实例。
注:InvokerInvocationHandler 实现 JDK 的 InvocationHandler 接口,具体的用途是拦截接口类调用

生成的代理类结构如下,最终调用其方法会调用到InvokerInvocationHandler的invoke方法中

最终会调用到invoker#invoke方法,这里invoker的结构是包装了两层MockClusterInvoker->FailoverClusterInvoker,因为我们这里没有mock的逻辑,所以最终会调用到FailoverClusterInvoker#invoke,也就是其父类AbstractClusterInvoker#invoke方法


Dubbo源码解析-——服务引用相关推荐

  1. Dubbo 源码分析 - 服务引用

    1. 简介 在上一篇文章中,我详细的分析了服务导出的原理.本篇文章我们趁热打铁,继续分析服务引用的原理.在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直联的方式引用服务,第二种 ...

  2. Dubbo源码解析-——服务导出

    前言 在之前我们讲过Spring和Dubbo的集成,我们在服务上标注了@DubboService的注解,然后最终Dubbo会调用到ServiceBean#export方法中,本次我们就来剖析下服务导出 ...

  3. Dubbo源码解析 - 远程暴露

    作者:肥朝 原文地址:http://www.jianshu.com/p/893f7e6e0c58 友情提示:欢迎关注公众号[芋道源码].????关注后,拉你进[源码圈]微信群和[肥朝]搞基嗨皮. 友情 ...

  4. dubbo(4) Dubbo源码解析之服务引入过程

    来源:https://juejin.im/post/5ca37314e51d454cb97d9c40 1. 简介 在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直连的方式引用服 ...

  5. dubbo(5) Dubbo源码解析之服务调用过程

    来源:https://juejin.im/post/5ca4a1286fb9a05e731fc042 Dubbo源码解析之服务调用过程 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与 ...

  6. dubbo源码解析-逻辑层设计之服务降级

    Dubbo源码解析系列文章均来自肥朝简书 前言 在dubbo服务暴露系列完结之后,按计划来说是应该要开启dubbo服务引用的讲解.但是现在到了年尾,一些朋友也和我谈起了明年跳槽的事.跳槽这件事,无非也 ...

  7. Dubbo源码解析-Dubbo服务消费者_Dubbo协议(一)

    前言: 在介绍完Dubbo 本地模式(Injvm协议)下的服务提供与消费后,上文我们又介绍了Dubbo远程模式(dubbo协议)下的服务暴露过程,本质上就是通过Netty将dubbo协议端口暴露出去, ...

  8. dubbo源码解析(二)

    大家好,我是烤鸭: dubbo 源码解析: 1.服务导出 介绍: Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可分为三 ...

  9. Dubbo源码解析 —— Router

    作者:肥朝 原文地址:http://www.jianshu.com/p/278e782eef85 友情提示:欢迎关注公众号[芋道源码].????关注后,拉你进[源码圈]微信群和[肥朝]搞基嗨皮. 友情 ...

最新文章

  1. [学习笔记]矩阵乘法及其优化dp
  2. 从HTML5移动应用现状谈发展趋势
  3. Hive创表异常,FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask.
  4. 能够使用StringBuilder类的常用方法操纵字符串 1215
  5. hdu5141 线段树
  6. 苹果宣布CEO乔布斯辞职 COO库克接任
  7. 《绯雨骑士团》Demo
  8. 97. ExtJS之EditorGridPanel afteredit属性
  9. 2016服务器系统驱动,windows sever2016驱动大家是怎么装的啊
  10. Win7 64位系统下Auto CAD 2010注册激活,出现警告:Make sure you can write to current directory...
  11. 过去分词做宾语补足语
  12. macos 如何优美地打开知网caj文件 - macos 如何打开caj文件
  13. emlog5.3.1后台暴力破解
  14. IE 主页被恶意篡改的解决方法
  15. 【R语言】Studio的下载及安装及RStudio打开后空白的解决
  16. 数据分析学习笔记——第4天
  17. 为什么小型软件外包公司很难盈利(一)
  18. 将 JPG 或 PNG 图像转换为 Dicom
  19. 近期整活之相关软件之安装说明
  20. 使用openfeign调用报错java.io.IOException: too many bytes written,以及调用过程中参数传递为空等问题

热门文章

  1. 在vue中使用鼠标事件@mousedown、@mouseenter等失效的解决办法,以及PC端长按实现
  2. java 内存 监控_监控JVM内存使用情况
  3. 解决surface的幽灵触控
  4. 获取二维数组的长度和宽度
  5. 【译】Learn D3 入门文档:Joins
  6. docker安装报错:docker-ce conflicts with 2:docker-1.13.1-208.git7d71120.el7_9.x86_64
  7. pr中轨道遮罩键的使用
  8. java下载文件时文件名中文乱码
  9. 微信小程序手把手接入腾讯地图
  10. 基于Python的OCR图像识别