我们在平常开发中使用 dubbo 是需要从注册中心拉取服务信息的,然后进行数据组装,路由过滤,负载均衡,发起网络请求一系列步骤进行远程调用,这一系列操作对于我们来说如同黑盒,所以我们在平时开发之余,也需要对dubbo消费者如何进行服务导入有所了解,本篇我们就一块对 dubbo 服务导入的原理进行探索
依据之前spring和Dubbo的整合原理,我们知道,当通过@Reference 注解引入一个Dubbo服务的时候,会生成一个ReferenceBean和代理对象,然后对属性进行赋值代理对象,ReferenceBean是一个FactoryBean,重写FactoryBean的getObject方法会返回一个代理对象并交给Spring管理,那么ReferenceBean是如何创建这个代理对象的,咱们就来看看这个ReferenceBean的getObject方法。
根据前置知识,我们知道服务消费端创建代理对象会调用ReferenceBean的父类ReferenceConfig的get方法
checkAndUpdateSubConfigs和ServiceBean那里一样,检查和更新参数,把ReferenceBean里的属性的值更新为优先级最高的参数值,接着会调用init方法创建动态代理对象,并付给ref属性,然后返回ref。
init()是消费端创建动态代理对象的入口
在init方法中,主要做了
1.把读取到的配置都放在一个map中,后续根据这个map中的参数去从注册中心查找服务
2.调用createProxy方法生成代理对象
在createProxy方法中,会获取注册中心配置URL,代码如下
这里获取所有的注册中心的URL,并把配置信息的map也加到注册中心URL中,接下来就是一个分支逻辑:
1.如果消费者配了一个注册中心:
那么直接调用Protocol的refer(interfaceClass, urls.get(0));得到一个Invoker对象。
2.如果消费者配了多个注册中心:
遍历每个注册中心,每次调用Protocol的refer(interfaceClass, url);得到一个Invoker对象添加到invokers中,然后把invokers调用CLUSTER.join(new StaticDirectory(u, invokers))把所有invokers进行封装得到一个invoker,这里是通过SPI机制,使用RegistryAwareCluster的join方法实现的,会把invoker对象封装成RegistryAwareClusterInvoker,在服务调用的时候,会遍历所有的注册中心的invoker,只要有一个是可用的就直接返回。
最后在createProxy方法的最后,根据最终得到的invoker对象调用PROXY_FACTORY.getProxy(invoker);得到一个代理对象,并返回,这个代理对象就是ref
1.2.1 Invoker对象的生成
每个注册中心都会调用REF_PROTOCOL.refer(interfaceClass, url)生成一个Invoker对象,我们就看看是如何生成Invoker对象的。
refer方法有两个参数:
type:表示引入的服务
url:注册中心URL(包含我们配置信息生成的url)
REF_PROTOCOL有两个包装类,在调用最终的protocol类之前,会经过两次包装类的refer方法,进行AOP增强
1.ProtocolFilterWrapper
这里用于添加dubbo protocol对应的invoker的过滤器链,自己实现的Filter扩展类就是在这里加载的,服务调用的时候会详讲,这里是registry protocol,所以只会走进第一个if中。
2.ProtocolListenerWrapper
这里用于给dubbo protocol对应的invoker添加监听器,用于处理结果,这里可以进行扩展,这里是registry protocol,所以只会走进第一个if中。
最终会调用RegistryProtocol#refer方法
refer方法主题逻辑就是
1.把注册URL的协议换为zookeeper,方便拿到zk的具体实现类
2.调用doRefer方法
1.2.3 创建动态目录
继续调用====:
在一个服务目录中包含了:
- serviceType:表示服务接口
- serviceKey:表示引入的服务key,serviceclass+version+group
- queryMap:表示引入的服务的参数配置
- configurators:动态配置信息
- routerChain:路由链
- invokers:表示服务目录当前缓存的服务提供者Invoker
- ConsumerConfigurationListener:监听本应用的动态配置
- ReferenceConfigurationListener:监听所引入的服务的动态配置
在这里的主逻辑为
1.创建一个动态服务目录
2.生成消费者URL并注册到注册中心
3.构建路由链,为后续消费端进行服务路由做准备
4.注册监听并从注册中心拉取配置信息以及服务提供者URL
5.生成invoker对象
1.2.3 构建路由链
在RegistryProtocol.doRefer方法的directory.buildRouterChain(subscribeUrl),会根据消费者URL构建路由链
接着看RouterChain.buildChain(url),会调用RouterChain的构造方法
1.获取RouterFactory接口的所有实现类
// 0 = {MockRouterFactory@2880}
// 1 = {TagRouterFactory@2881} // 标签路由
// 2 = {AppRouterFactory@2882} // 应用条件路由
// 3 = {ServiceRouterFactory@2883} // 服务条件路由
2.调用每个Factory的getRouter方法,应用条件路径和服务条件路由相似,以AppRouterFactory为例
创建路由会来到AppRouterFactory.createRouter方法
DynamicConfiguration.getDynamicConfiguration()代表拿到配置中心实例,url代表消费者URL
AppRouter继承了ListenableRouter,这里会来到ListenableRouter的构造方法
调用ListenableRouter.init方法
这里会绑定监听并主动读取一次路由信息,读取完路由信息,生成的规则会赋值给其父类的成员变量。
服务路由和应用路由类似,这里就不解读了,至于标签路由的监听是在读取服务提供者URL信息的时候进行监听的,可以继续往下看。
1.2.4 监听节点拉取配置
在RegistryProtocol.doRefer方法的上边这块代码,会进行消费端的监听器的注册以及主动拉取注册中心服务提供者的信息。
CONSUMER_CONFIGURATION_LISTENER和ReferenceConfigurationListener和生产者那一块的逻辑是一样的,都是新版本的监听器,在构造方法中调用initWith方法进行监听器的绑定以及首次读取服务的应用级别和服务级别的动态配置信息
// 当前所引入的服务的消费应用目录:/dubbo/config/dubbo/dubbo-demo-consumer-application.configurators
// 当前所引入的服务动态配置目录:/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService:1.1.1:g1.configurators
而 registry.subscribe(url, this)负责的是监听老版本动态配置目录的信息以及主动从注册中心拉取服务提供者URL信息。
这里的registry是ZookeeperRegistry,它会调用父类的subscribe方法,最终会来到自己的doSubscribe方法,下边是进行注册监听的代码
可以看到在注册的时候,手工的调用notify方法拉取配置,这里会来到RegistryDirectory.notify(List urls)
在这里会读取一下三个目录的信息,这里主要是读取服务提供者配置信息,会调用RegistryDirectory.refreshOverrideAndInvoker方法
“/dubbo/org.apache.dubbo.demo.DemoService/providers”
“/dubbo/org.apache.dubbo.demo.DemoService/configurators”
“/dubbo/org.apache.dubbo.demo.DemoService/routers”
overrideDirectoryUrl主要是根据动态配置信息重新动态目录URL信息,我们重点看refreshInvoker方法,这里会把服务提供者URL传到方法中去。
在refreshInvoker方法中,会调用toInvokers方法,生成inwoker对象,这个方法是重点,主要逻辑有:
1.服务提供者可能有多个,可能是dubbo协议也有可能是rest协议,如果消费者配置了具体的协议,先根据协议过滤掉一些服务提供者URL
2.调用mergeUrl对服务提供者进行重写,主要包括消费者本身的配置信息,动态配置信息对服务提供者URL进行重写覆盖
3.判断当前服务提供者是否已经生成invoker对象,如果没有,调用==invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl)==生成一个Dubbo Invoker对象,这里根据url会调用DubboProtocol类的refer方法,再次之前会先调用wrapper类,进行AOP增强。
4.toInvokers调用完毕,生成一个map,key是我们服务提供者的url,value就是生成的invoker
然后把生成的invoker集合设置到路由链中,并赋值给动态目录的属性invokers,注意这里的invoker都是dubbo invoker 代表的是一个服务的多个提供者。
5.监听标签路由信息
在routerChain.setInvokers(newInvokers)放中,除了把新生成的invoker赋值给路由链,在这一过程中还有进行标签路由配置信息的监听,为什么要在这里监听呢?因为标签路由是针对服务提供者打标签,而我们消费者引入服务的时候是不知道服务的应用信息的,所以只能从服务提供者URL中获取到服务应用信息,然后再进行监听。
在这里会遍历所有的路由规则,调用notify方法,其实只有标签路由实现了这个方法,我们再看下TagRouter.notify方法
1.2.5 生成DubboInvoker对象
在监听拉取配置的时候,在RegistryDirectory.toInvokers方法中会拿到最终更新的服务提供者URL生成一个Invoker对象
url这里是根据不同的服务提供者协议,调用不同的protocol类,比如我们服务提供者使用的是dubbo协议,那么就会调用==,但是我们之前分析过是有包装的类的,和,所以这里会依次调用=>=>,
我们重点说下
会给DubboProtocol的url对应的invoker添加Filter过滤器,这些过滤器在服务调用的时候会执行对应的方法,这里我们先简单了解一下即可。
接着就是调用方法,但是DubboProtol没有实现refer方法,会调用其父类
protocolBindingRefer是由DubboProtol实现的
invoker在层层包装下会形成一下结构
而且在生成DubboInvoker的时候,就会去创建一个NettyClient,和服务端建立Socket连接,主要方法在getClients(url)方法上。
1.2.6 构建传输链路建立Netty连接
在Dubbo协议中, 是基于Netty进行数据传输的,生产者和消费者是可以互相传输数据的,Dubbo在此之上抽象了一个数据交换层,用于区分请求和响应
我们继续在创建DubboInvoker,追踪
真正创建我们交换层client的代码在client = Exchangers.connect(url, requestHandler); // connect
这里是调用getExchanger方法根据SPI机制获取Exchanger接口的扩展点拿到具体实现,默认是HeaderExchanger,我们继续追踪HeaderExchanger的connect方法
DecodeHandler把handler(ExchangeHandlerAdapter)包装两层,接着Transporters.connect来创建Client连接对象:
调用完getTransporter().connect(url, handler)方法,会把NettyClient作为参数返回,然后调用HeaderExchangeClient的构造方法,同时开启重连任务和心跳检测任务。
至此我们服务导出链路走完,
1.2.7 获取最终invoker对象
我们重新回到:
还有重要的一步cluster.join(directory),把我们的服务目录作为参数,调用join方法,cluster是一个接口,默认实现为FailoverCluster,还有一个包装类型的MockClusterWrapper
FailoverCluster.join
MockClusterWrapper.join
所以我们最终返回的invoker对象是这样的结构
MockClusterInvoker
invoker->FailoverClusterInvoker
directory->
List DubboInvoer集合
1.2.8 根据invoker创建动态对象
现在做个小结,总结一下服务引入到底都做了那些事情?
1.扫描加了@Reference注解的地方,为其进行属性注入
2.根据@Reference注解的信息,生成一个ReferenceBean对象
3.ReferenceBean是一个Factory,会通过getObject创建一个代理对象
4.创建代理对象需要获取一个Invoker对象,其创建过程为:
调用
1.创建动态目录
2.构建路由链
3.注册监听并拉取配置
4.根据服务提供者URL生成DubboInvoker对象
5.构建Netty客户端,并和Server建立连接