Dubbo源码解析-——服务引用

前言

在之前我们讲过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方法

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值