Dubbo之服务引用源码分析

服务引用有两种:
1、直连方式引用
2、基于注册中心引用

引用服务时机

1、 ReferenceBean 的 afterPropertiesSet 方法时引用服务
org.apache.dubbo.config.spring.ReferenceBean#afterPropertiesSet
由于ReferenceBean实现了InitializingBean,这里是spring bean生命周期中,调用afterPropertiesSet执行初始化方法的一个切入点。


image.png

调用shouldInit方法查看init参数是否为true,如果为true将会在这里初始化。默认是false
2、 ReferenceBean 对应的服务提供者类被注入到服务消费者类中的引用(默认情况)
org.apache.dubbo.config.ReferenceConfig#get
其主要逻辑是在init方法,经历获取服务配置、Invoker 创建、代理类创建等步骤。


image.png

init

org.apache.dubbo.config.ReferenceConfig#init


image.png

1、检查本地存根checkStubAndLocal
2、检查Mock checkMock


image.png

3、添加 side、协议版本信息、时间戳和进程号等信息到 map 中,如果不是泛化服务将获取版本,获取接口方法列表,并添加到 map 中,将 ApplicationConfig、ConsumerConfig、ReferenceConfig 等对象的字段信息添加到 map 中
image.png

4、methods参数配置处理,


image.png

5、获取服务消费者 ip 地址、存储 attributes 到系统上下文中
image.png

6、调用createProxy创建代理类ref

引用服务

org.apache.dubbo.config.ReferenceConfig#createProxy


image.png

1、本地JVM引用


image.png

2、url不为空,则是点对点的服务地址。@Reference中指定了url属性
image.png

3、如果是注册中心地址,则在url中添加一个refer参数。
首先获取注册url。然后把引入的map数据属性放到refer后与注册url拼接,并添加到urls中。
如果是服务地址,有可能url中配置了参数,map中表示的服务消费者消费服务时的参数,这里所以需要合并mergeUrl

registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-consumer-application&dubbo=2.0.2&pid=9484&refer=application%3Ddubbo-demo-consumer-application%26dubbo%3D2.0.2%26group%3Dg1%26interface%3Dorg.apache.dubbo.demo.DemoService%26lazy%3Dfalse%26methods%3DsayHello%26pid%3D9484%26register.ip%3D192.168.1.2%26release%3D2.7.0%26revision%3D1.1.1%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1596342012598%26version%3D1.1.1&registry=zookeeper&release=2.7.0&timestamp=1596342266342
image.png

4、@Reference中的protocol属性表示使用哪个协议调用服务,如果不是本地调用协议,则把注册中心地址找出来
如果只有一个注册服务url,则直接调用Protocol的自适应类,调用ref引用方法,这里会调用到RegistryProtocol、DubboProtocol


image.png

5、如果有多个注册服务url,则会进行遍历urls。生成的执行体invoker添加到invokers集合中。并对最后一个registryURL,进行Cluster包裹


image.png

6、得到执行体invoker,并通过ProxyFactory生成代理类
生成的invoker实例,就是为了调用。这里最终的包裹
image.png

MockClustInvoker包裹服务字典RegistryDirectory和集群容错Cluster的FailoverClusterInvoker,FailoverClusterInvoker也包裹了服务字典RegistryDirectory

所以说,在根据PROXY_FACTORY.getProxy获取invoker的代理类之前,生成invoker才是代理类的核心。

动态服务目录

RegistryProtocol的包裹类ProtocolFilterWrapper和ProtocolListenerWrapper(最外包裹)就不看了,也是利用SPI
1、refer
org.apache.dubbo.registry.integration.RegistryProtocol#refer


image.png

①、把url地址由registry://开头转换成 zookeeper://开头

zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-consumer-application&dubbo=2.0.2&pid=10732&refer=application%3Ddubbo-demo-consumer-application%26dubbo%3D2.0.2%26group%3Dg1%26interface%3Dorg.apache.dubbo.demo.DemoService%26lazy%3Dfalse%26methods%3DsayHello%26pid%3D10732%26register.ip%3D192.168.1.2%26release%3D2.7.0%26revision%3D1.1.1%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1596345426916%26version%3D1.1.1&release=2.7.0&timestamp=1596345429789

②、从url获取注册类registry


image.png

③、 qs表示 查询字符, 表示url中的参数,表示消费者引入服务所配置的参数。如果有group有多个值,则这里的cluster为MergeableCluster
④、doRefer方法调用
2、doRefer
org.apache.dubbo.registry.integration.RegistryProtocol#doRefer


image.png

image.png

①、创建动态服务目录动态RegistryDirectory,并设置registry、protocol值。RegistryDirectory实现了 NotifyListener 接口。当注册中心服务配置发生变化后,RegistryDirectory 可收到与当前服务相关的变化。收到变更通知后,RegistryDirectory 可根据配置变更信息刷新 Invoker 列表。在服务消费端最核心的就是这个服务目录

②、获取订阅subscribeUrl、并注册到zookeeper

consumer://192.168.1.2/org.apache.dubbo.demo.DemoService?application=dubbo-demo-consumer-application&dubbo=2.0.2&group=g1&interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello&pid=10732&release=2.7.0&revision=1.1.1&side=consumer&sticky=false&timestamp=1596345426916&version=1.1.1

a、register
org.apache.dubbo.registry.support.FailbackRegistry#register


image.png

b、doRegister
org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister


image.png

注册路径是:
/dubbo/org.apache.dubbo.demo.DemoService/consumers/consumer%3A%2F%2F192.168.1.2%2Forg.apache.dubbo.demo.DemoService%3Fapplication%3Ddubbo-demo-consumer-application%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.0.2%26group%3Dg1%26interface%3Dorg.apache.dubbo.demo.DemoService%26lazy%3Dfalse%26methods%3DsayHello%26pid%3D10732%26release%3D2.7.0%26revision%3D1.1.1%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1596345426916%26version%3D1.1.1

③、构建路由链
④、服务目录需要订阅的几个路径
⑤、cluster包裹服务目录

构建路由链

路由链是动态服务目录的一个属性值,在引入服务时可以根据路由条件进行过滤
org.apache.dubbo.registry.integration.RegistryDirectory#buildRouterChain


image.png

org.apache.dubbo.rpc.cluster.RouterChain#buildChain


image.png

org.apache.dubbo.rpc.cluster.RouterChain#RouterChain
image.png

根据路由工厂RouterFactory接口的扩展实现类,得到四个:

MockRouterFactory、TagRouterFactory 标签路由、AppRouterFactory应用条件路由、ServiceRouterFactory服务条件路由
利用RouterFactory根据url生成各个类型的Router、并把routers按priority进行排序
MockInvokersSelector(priority=负最大整数)、TagRouter(priority=100)、ServiceRouter(priority=140)、AppRouter(priority=150)

服务目录订阅路径

服务目录需要订阅的几个路径
服务提供者目录:/dubbo/org.apache.dubbo.demo.DemoService/providers
老版本动态配置目录:/dubbo/org.apache.dubbo.demo.DemoService/configurators
老版本路由器目录:/dubbo/org.apache.dubbo.demo.DemoService/routers
0、subscribe
org.apache.dubbo.registry.integration.RegistryDirectory#subscribe


image.png

此时有url多了category=providers,configurators,routers参数

consumer://192.168.1.2/org.apache.dubbo.demo.DemoService?application=dubbo-demo-consumer-application&category=providers,configurators,routers&dubbo=2.0.2&group=g1&interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello&pid=10224&release=2.7.0&revision=1.1.1&side=consumer&sticky=false&timestamp=1596347742946&version=1.1.1

1、ConsumerConfigurationListener监听器,
ConsumerConfigurationListener是RegistryDirectory的一个属性值,在创建RegistryDirectory对象时创建


image.png

key是dubbo-demo-consumer-application.configurators,所以监听应用动态配置路径是/dubbo/config/dubbo/dubbo-demo-consumer-application.configurators
2、ReferenceConfigurationListener监听器


image.png

key是org.apache.dubbo.demo.DemoService:1.1.1:g1.configurators,所以监听路径服务动态配置目录是/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService:1.1.1:g1.configurators
3、subscribe
org.apache.dubbo.registry.support.FailbackRegistry#subscribe
image.png

①、subscribe
org.apache.dubbo.registry.support.FailbackRegistry#subscribe


image.png

②、doSubscribe
org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe
a、首先判断服务接口是不是*,这里不会走
image.png

image.png

b、走else逻辑
从消费者url根据category获取路径(category=providers,configurators,routers)
image.png

得到服务提供者端配置路径(providers服务提供者、configurators服务提供者配置路径、routers路由配置路径)
/dubbo/org.apache.dubbo.demo.DemoService/providers
/dubbo/org.apache.dubbo.demo.DemoService/configurators

/dubbo/org.apache.dubbo.demo.DemoService/routers


image.png

对下面三个路径进行遍历,并创建路径,在添加urls之前,会首先通过toUrlsWithEmpty进行Empty协议替换。
image.png

c、toUrlsWithEmpty
org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#toUrlsWithEmpty
为空,则用
image.png

d、toUrlsWithoutEmpty
对引入/dubbo/org.apache.dubbo.demo.DemoService/providers所有服务提供者进行遍历
org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#toUrlsWithoutEmpty
image.png

e、isMatch
org.apache.dubbo.common.utils.UrlUtils#isMatch
服务提供者与消费者引入是否匹配
image.png

首先根据consumerInterface和providerInterface
image.png

根据category、根据enabled,默认是true
consumerUrl中category=providers,configurators,routers
providerUrl 得到默认category=providers
consumerUrl = "consumer://192.168.1.2/org.apache.dubbo.demo.DemoService?application=dubbo-demo-consumer-application&category=providers,configurators,routers&dubbo=2.0.2&group=g1&interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello&pid=9320&release=2.7.0&revision=1.1.1&side=consumer&sticky=false&timestamp=1596349730827&version=1.1.1"
providerUrl =  "dubbo://192.168.1.2:20881/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-provider1-application&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService:1.1.1:g1&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=g1&interface=org.apache.dubbo.demo.DemoService&logger=log4j&methods=sayHello&pid=9596&release=2.7.0&revision=1.1.1&sayHello.loadbalance=random&sayHello.return=true&side=provider&timeout=3000&timestamp=1596340240853&version=1.1.1"

根据group、version、classifier。三个同时符合则返回true


image.png

③、notify
org.apache.dubbo.registry.support.FailbackRegistry#notify


image.png

a、doNotify
org.apache.dubbo.registry.support.FailbackRegistry#doNotify
image.png

b、notify
org.apache.dubbo.registry.support.AbstractRegistry#notify
分类category


image.png

image.png

c、对routers、configurators、providers调用notify方法
org.apache.dubbo.registry.integration.RegistryDirectory#notify
image.png

获取动态配置URL,生成configurators、获取老版本路由URL,生成Router,并添加到路由链中、获取服务提供者URL
d、refreshOverrideAndInvoker
这里只要看providers,根据动态配置手动触发一次刷新invoker。
org.apache.dubbo.registry.integration.RegistryDirectory#refreshOverrideAndInvoker
image.png

e、refreshInvoker
org.apache.dubbo.registry.integration.RegistryDirectory#refreshInvoker
这里会Protocol进行过滤,并且调用DubboProtocol.refer方法得到Invoker


image.png

image.png

得到的newInvokers设置到RegistryDirectory的routerChain和invokers中
f、toInvokers
org.apache.dubbo.registry.integration.RegistryDirectory#toInvokers
遍历当前服务所有的服务提供者URL、当前消费者如果手动配置了Protocol,那么则进行匹配
image.png

当前Protocol是否在应用中存在对应的扩展点
image.png

如果当前服务提供者URL缓存中localUrlInvokerMap没有,则之前没有生产过Invoker。则会调用Protocol的refer方法生成一个Invoker
image.png

会调用到DubboProtocol#refer方法,DubboProtocol继承了AbstractProtocol,DubboProtocol不存在refer方法。会调用到父类AbstractProtocol的refer方法
客户端ExchangeClient创建
DubboInvoker创建

1、refer
org.apache.dubbo.rpc.protocol.AbstractProtocol#refer
DubboInvoker是异步的,而AsyncToSyncInvoker会封装为同步的


image.png

2、protocolBindingRefer
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#protocolBindingRefer


image.png

通过getClients(url)方法获取ExchangeClient客户端。这里是个数组形式, 在DubboInvoker发送请求时会轮询clients去发送数据,如果有多个的话。ExchangeClient 实际上并不具备通信能力,它需要基于更底层的客户端实例进行通信例如NettyClient
3、getClients
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getClients
image.png

connections表示建立几个socket连接,在DubboProtocol中,每构造一个Client就会去和Server建立一个Socket连接
如果connections为0,这时会去获取shareconnections参数,默认为1。默认情况下,使用共享客户端实例。如果不使用共享客户端会调用initClient方法,getSharedClient内部也会调用initClient
4、getSharedClient
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getSharedClient


image.png

image.png

image.png

5、buildReferenceCountExchangeClientList
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#buildReferenceCountExchangeClientList
image.png

6、buildReferenceCountExchangeClient
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#buildReferenceCountExchangeClient
image.png

initClient生成一个ExchangeClient,并用ReferenceCountExchangeClient包装。ReferenceCountExchangeClient是引用计数功能的 ExchangeClient
7、initClient
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#initClient
image.png

从url得到client值,默认为netty。添加编解码和心跳包参数到 url 中,编码方式是DubboCodec.NAME、心跳heartbeat值是60 * 1000ms。并检测客户端类型是否存在,不存在则抛出异常。


image.png

获取 lazy 配置,并根据配置值决定创建的客户端类型,LazyConnectExchangeClient
Exchangers建立连接,把url和requestHandler传入。requestHandler是DubboProtocol的内部类
8、connect
org.apache.dubbo.remoting.exchange.Exchangers#connect
得到一个HeaderExchanger(Exchanger的SPI默认是header)去connect
image.png

9、connect
org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#connect
这里进行以下调用,分别如下:
创建HeaderExchangeHandler 对象、 创建 DecodeHandler 对象、通过 Transporters 构建 Client 实例、 创建 HeaderExchangeClient 对象。
用NettyTransporter去connect。DecodeHandler解码,是把InputStream解析成AppResponse对象
image.png

10、connect
org.apache.dubbo.remoting.Transporters#connect
image.png

11、connect
org.apache.dubbo.remoting.transport.netty4.NettyTransporter#connect
image.png

12、NettyClient
org.apache.dubbo.remoting.transport.netty4.NettyClient#NettyClient
初始化和开始netty
image.png

13、AbstractClient
org.apache.dubbo.remoting.transport.AbstractClient#AbstractClient
image.png

14、doOpen
org.apache.dubbo.remoting.transport.netty4.NettyClient#doOpen
netty启动
image.png

15、connect

org.apache.dubbo.remoting.transport.AbstractClient#connect


image.png

16、doConnect
org.apache.dubbo.remoting.transport.netty4.NettyClient#doConnect
image.png

所以得到的DubboInvoker结构是
image.png

MockClusterInvoker
 RegistryDirectory
  Collections$UnmodifiableRandomAccessList
 routerChain
  MockInvokersSelector
  TagRouter
  AppRouter
   ServiceRouter

Collections$UnmodifiableRandomAccessList
 RegistryDirectory$InvokerDelegate
  ListenerInvokerWrapper
   ProtocolFilterWrapper$CallbackRegistrationInvoker
    ProtocolFilterWrapper$1
     AsyncToSyncInvoker
      DubboInvoker

DubboInvoker
 ExchangeClient[1]
  ReferenceCountExchangeClient
   HeaderExchangeClient
    NettyClient

创建代理

PROXY_FACTORY.getProxy(invoker)
1、getProxy
org.apache.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper#getProxy(org.apache.dubbo.rpc.Invoker<T>)


image.png

2、getProxy
org.apache.dubbo.rpc.proxy.AbstractProxyFactory#getProxy(org.apache.dubbo.rpc.Invoker<T>)


image.png

3、getProxy
org.apache.dubbo.rpc.proxy.AbstractProxyFactory#getProxy(org.apache.dubbo.rpc.Invoker<T>, boolean)
image.png

image.png

4、getProxy
org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getProxy


image.png

invoker通过InvokerInvocationHandler包装 最终生成的代理类
这里也是调用的开始,通过sayHello方法会执行InvocationHandler handler的invoke方法
package org.apache.dubbo.common.bytecode;

public class proxy0 implements org.apache.dubbo.demo.DemoService {

    public static java.lang.reflect.Method[] methods;

    private java.lang.reflect.InvocationHandler handler;

    public proxy0() {
    }

    public proxy0(java.lang.reflect.InvocationHandler arg0) {
        handler = $1;
    }
    public java.lang.String sayHello(java.lang.String arg0) {
        Object[] args = new Object[1];
        args[0] = ($w) $1;
        Object ret = handler.invoke(this, methods[0], args);
        return (java.lang.String) ret;
    }
}

总结

本片主要分析了dubbo消费端的从注册中心引入的过程。需要明白一点,引入是为调用做准备的。
引入层级较为多,其核心思想就是Invoker的生成。最终ref引入的是Invoker经过InvokerInvocationHandler包装的代理类,通过Javassist生成。
其中,Invoker是通过Protocol.ref生成。具体Protocol通过SPI原理实现,首先经过RegistryProtocol#refer、再经过DubboProtocol#refer方法。
在RegistryProtocol中主要是生成动态服务目录RegistryDirectory,并为RegistryDirectory设置registry、protocol、消费url等,并设置路由链。由于服务目录实现了NotifyListener,是个监听器。需要对新老版本的服务动态配置路径及服务配置路径、路由等的监听。动态服务目录RegistryDirectory订阅后,会直接触发一次,这次会调用到DubboProtocol的refer方法。最终得到的RegistryDirectory需要cluster进行包装。
在DubboProtocol引入中,主要是对客户端实例的获取。涉及到Exchanger 、传输协议Transporter等SPI,最终会调用NettyClient创建netty(默认)客户端,并进行连接。对DubboInvoker包装主要是AsyncToSyncInvoker

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342