Dubbo源码深度解析(七)

        接上一篇博客《Dubbo源码深度解析(六)》,上篇博客主要从服务消费方开始讲起,主要讲:如果类中的属性或者方法,如果被@DubboReference注解所修饰,Dubbo是怎么处理的,处理逻辑类似Spring框架提供的@Autowired注解。核心就是ReferenceAnnotationBeanPostProcessor类,最终会生成一个对应的ReferenceBean对象,并放入Spring容器,当然最终给属性进行依赖注入的并非ReferenceBean对象,而是它的 ref属性,如果该属性为空,就需要调用Protocol#refer()方法(Dubbo的SPI机制找到Protocol接口的实现类,调用实现类的ref()方法)创建Invoker,最终生成代理对象还没讲到,这篇博客会讲到。然后就是主要讲如何创建Invoker对象,也涉及到从注册中心拉取服务提供方的地址,以及Netty Client是如何跟服务提供方建立连接的逻辑等。

        本篇博客是Dubbo源码讲解的最后一篇,在本篇博客中,会接着上篇的内容继续讲,上篇最后讲到了服务消费方跟服务提供方建立连接,其实讲到这里,Invoker对象的创建也基本讲完了。有一个核心的方法没有讲到,即RegistryProtocol#doCreateInvoker()方法中指向的这一行代码:

7942cdce655b4b46b41965e3b893dc2f.png

        前面也讲到了,这里的cluster对象实际上是MockClusterWrapper对象,而MockClusterWrapper对象的cluster是FailoverCluster对象,看看MockClusterWrapper#join()方法,代码如下:

5d4ba704d43e41c1acca5fcb51eb96a1.png

c97c8d6dd771450389e061e291daf17f.png

        先看看FailoverCluster#doJoin()方法,代码如下:

bbdf8b180c114658a440526e1187337f.png

        再看看AbstractCluster#buildClusterInterceptors()方法,代码如下:

85281aa2585b4973b649e9812830c40f.png        先看看ClusterInterceptor接口,代码如下:

a823d87057484816a6311475ea1a5193.png

        org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor文件的内容为:

3fdc294f0ea94025855300e505aefa69.png

        实现类的代码分别是:

332131c6332d42428474e3d9fcf412c0.png

01755f7b80864adcb654b483907ed188.png

        讲ExtensionLoader#getActivateExtension()方法的时候,有一点没有讲到,也就是过滤条件的这个地方:

bbe214c11a99407eac41224f447a5269.png

e2fbe0ed70a34fac9e25472e8a871f76.png

        因此最终获取到的只有ConsumerContextClusterInterceptor对象,最终创建的InterceptorInvokerNode对象的interceptor属性为ConsumerContextClusterInterceptor对象,next属性为FailoverCluster对象。断点验证,结果如下:

754bb6bdb23b4fd0934323dc12c1d0b9.png

        到这里为止,生成Invoker对象的逻辑讲完了。总结一下,最终在ReferenceConfig得到的Invoker是什么样的?或者说是经过了多少层包装?

21d243f2a8f94c65b7026eea480d0940.png

408667afb490480c9689ca6fa94a74cb.png

06ed93d331254511bed33829ba0c80c1.png

3943cb93542f473685bbc3b1a7def96c.png

4b3d5e8b869d415889ee59f745dee9b3.png

32f35f70cca548feb1ddf416d4ab3bdc.png

49565b4724024d7dad3ea04e02a9e941.png

        通过上面这些截图可以知道,最终生成的Invoker对象为:MigrationInvoker对象,而MigrationInvoker对象的invoker属性为MockClusterInvoker对象;而MockClusterInvoker的对象的invoker属性为InterceptorInvokerNode对象;而InterceptorInvokerNode对象interceptor属性为ConsumerContextClusterInterceptor对象,next为FailoverClusterInvoker对象;FailoverClusterInvoker对象里面有个urlInvokerMap属性,key为URL,value为InvokerDelegate对象,而InvokerDelegate的invoker属性为FilterNode对象;而FilterNode对象的filter属性为ConsumerContextFilter属性,它的next属性仍是FilterNode对象;而这个FilterNode对象的filter属性为FutureFilter对象,它的next属性仍是FilterNode对象;这个FilterNode对象的filter属性为MonitorFilter对象,next属性为ListenerInvokerWrapper对象;而ListenerInvokerWrapper对象的invoker属性为AsncToSyncInvoker对象;AsncToSyncInvoker对象的invoker属性为DubboInvoker对象,其中DubboInvoker的clients存放的对象,才是真正跟服务提供方建立连接的客户端(有朋友可能好奇,整理清楚这层层的包装有啥用?用处大了,理顺了才能知道调用链路,也能知道每一层包装的作用是啥,可能其中某一层就做了服务的负债均衡)。断点验证,结果为:

51bf4ca38adc48e1adee01df5ff0d1a6.png        回到ReferenceConfig#createProxy()方法,最终得到的Invoker就是那个上面我讲的,位置为:

376734797e474c6caee7c9152827e391.png

        这里的PROXY_FACTORY属性即为ProxyFactory$Adaptive,即调用ProxyFactory$Adaptive#getProxy()方法,代码如下:

8ab33fa5af6e494bbb6e355fab3a77d5.png

318af42c2fb5454c8c58004b486dc345.png

94e18b8961b944f2a37d2d852c9f4b91.png

        很明显这里的InvokerInvocationHandler实现了InvocationHandler接口,因此在真正执行的是时候,调用的是InvokerInvocationHandler#invoke()方法,断点看看依赖注入的结果:

7badb719ee734c5c95379efb32e85eb3.png

        接下来就是发起远程调用的,即调用HelloService#sayHello()方法,最终调用的是InvokerInvocationHandler#invoke()方法,代码如下:

e2e7df8909a142a0b43f0924b2948a17.png

        有前面讲的可知,InvokerInvocationHandler对象的invoker属性为MigrationInvoker对象,因此直接看MigrationInvoker#invoke()方法,代码如下:

190ec70d4e4c4721a792d7217e493077.png

        看看MigrationInvoker#checkInvokerAvailable()方法,代码如下:

abcd6fac3945407a81da617ec4c3fab9.png

3171e6cddc7744ab92850be6e39ba056.png

811eb5aba37a47dc8dad1db19aff67c4.png

        由于是取反,因此最终调用invoker#invoke()方法,代码如下:

6f97da8aa7f84a1fa4381c40fda0f24c.png

e15147381e354a558b7a383dad7f6fd7.png

0cbb2c84a7d149b5b778d9ec0b39998c.png

        有前面可知,构建的InterceptorInvokerNode链只有一个,而这里的interceptor属性是ConsumerContextClusterInterceptor对象,因此可以看看ConsumerContextClusterInterceptor的before()方法和after()方法,代码如下:

ecc1a7f1e7344a85b4be92b1a379da56.png

        重点看看ClusterInterceptor#intercept()方法,代码如下:

c3dd5f439c244ff6b2df62bf102238c6.png

da1edffe5d754389801e9f24685ad90a.png

        先看看是如何获取Invoker的集合的,看看AbstractClusterInvoker#list()方法,代码如下:

be723f35d34447d79576e85c9dc952e0.png

700b3e58d36c43849027a893532036e5.png

9d3aaf37ab8e4e7786c4227f39d52c45.png

        看看RouterChain#route()方法,代码如下:

ae96f4488e414abc89289d52321f4706.png

        上一篇博客讲到过,此时routers属性中有四个Router对象,顺序分别是:MockInvokersSelector、TagRouter、AppRouter和ServiceRouter。常用的有TagRouter和ConditionRouter,但默认是不加载ConditionRouter的,需要指定。可以通过环境变量来指定,核心方法为:AbstractConfig#refresh()方法,ReferenceBean是其类,看看该方法,代码如下:

6a733f02800843daa39dfebc9041d57a.png

c47f735d71f24c5a80c68d9d03c3d9f8.png

        再看看CompositeConfiguration#getString()方法,传入的当然是解析后的Setter方法的名字,如setRouter() => "router",代码如下:

42f1cc2e60184069bc8cba398301797f.png

d6bb57823d1a42508e1d528b8811e244.png

2b08931a604b427fa80ac2b3309f6a02.png

2cdd1e09fc1647cb826835fba1b1abd2.png

7baee3a227d64f7b8f5e2861aaf388cf.png

        实际上就是在AbstractConfig#refresh()方法中创建的CompositeConfiguration对象,回去看看,代码如下:

33a34eec500f4be9b7716b77c4f37545.png

b09a4bd78799403dbf8b49a6297176bf.png

1ed264cb548f46f49fdc111def89711d.png

        因此可以知道 ,prefix的值为:dubbo.reference.接口全限定名,因此可以这样设置,代码如下:

d131ec5f874e490c9f4fa9eb0507d272.png

        断点验证,结果为:

d4f98cb15d8d4714897210a133810432.png

3080d4c9d3be46b9bab883d285ce97d9.png

        当然,还可以用Dubbo的配置文件,即dubbo.properties,如下:

c3ede789ebdb4e5cb61ea50f8f324e9b.png

ef7382a5982b428abf222865eda6063a.png

ee4aa0905dd54866a4a796321291c577.png

98829c9effee4d858dd59113c9d6fa71.png

033815b27af5461ab49944790bdaa9f2.png

        当然也可以通过@DubboReference注解指定,由于注解中没有直接提供 router属性,因此需要这样指定:

fd5b27ba91b94b7380a4b5f96d51899d.png

        处理@DubboReference注解的parameters属性的代码在这里:

772fcbcb19b54d1abc630c44eee28c2c.png

1f7bf4fee45d4ecc90a4373ec162eecf.png

f1387458c1464f9e9da390fb78d6d04e.png

        回到之前说的,主要看TagRouter和ConditionRouter的使用方法。先说TagRouter,这是给服务消费方和服务提供方发打上标签,只有服务消费方的tag和服务提供方的tag一直才能调用,前置准备,先是服务提供方配置:

83f6eaf4152b4e77bf6e815b9153c6d2.png

a549aedc000847bba42e94eb470de879.png

        启动服务提供方服务,再在做如下修改:

c230d03d3a714c52842361784330f017.png

0a5652eeab7c49688d7701b637b04f25.png

daa3bfdd1087410282f71493089f3cf3.png

        再次启动服务提供方的服务,再看看Nacos,结果如下:

95fe0816bceb4716bc12dd4cbf378266.png

22d21a22c736474690b9a6b0d56514c4.png

        再配置服务消费方,配置如下:

330f5d9d4e9a4de3964e6b7a322429f2.png

        启动服务消费方服务,直接看RegistryDirectory#doList()方法,并打断点,结果如下:

c3d8d4a82c5b4a02a41b451a8eed6638.png

ac96bbe92c3e4496a8089cf833c54610.png

02243d7632ae4508bca139fa259d84c4.png

002cdfb977144110a3e0e6591c18d0eb.png

    接着说ConditionRouter的使用方法。它的配置很灵活,参考官方文档:条件路由使用规则

3adcadbaace346fe86201bdb8250ec71.png

        按照官方文档的说法,"=>"之前是针对服务消费方,"=>"之后针对服务提供方,后面的参数名支持所有很多,包括URL中的,比如我举的例子,=> port = 20881,意思是服务消费方调用的应该是port为20881的服务提供方节点。配置如下:

a7fe45cb0b104d2984ea69c8ae13d1a7.png

        同样的服务提供方有两个节点,port分别是20880和20881,Nacos上的服务信息如下:

aa45e60f8c984e7dbf143b0c4114fe20.png

        再看看ConditionRouter#的代码,如下:

d849a0af038d498fb6609fcddf05f8bf.png

        解析的代码较复杂就不一点点讲了,直接看ConditionRouter#route()方法,代码如下:

36c53f90f49f428cafc4526f09acfd01.png

91b828aacf564fbbb8fa4d781a5e5bad.png

        断点看最终的结果:

bc54b22615fe44a593855802357526df.png

        剩下的谷规则,有兴趣的可以自己看,或者参看官方文档,我就不继续讲了。回到AbstractClusterInvoker#invoke()方法,Invoker的集合经过了一系列规则之后,如果还有多个,那就需要进行负载均衡算法,选择一个最合适的Invoker对象,发起远程调用了,代码如下:

06917693419e4156840325c78dde1f38.png

85f5f08864f345138768c18c22f111f3.png

0e50eeeeb95a40208197db592341f521.png

660ff99eb35f41e2b8eedfd64a763eef.png

80e4ae070a1b433f883573375006f774.png

a2236421af5342b5b934b38c935470ee.png

3ed66c673d114a3ca7b2895cfbc4f8bb.png

5d7a66ff3290469ebeb420cb845021b9.png

        回到FailoverClusterInvoker#doInvoke()方法,根据随机的负载均衡策略,得到了一个Invoker对象,接着就是调用Invoker#invoke()方法了,由前面讲的可知这里的Invoker对象是InvokerDelegate,即调用InvokerDelegate#invoke()方法,代码如下:

21e8e101b8e54a60b0d8b7b3410e2431.png

9e21b16f976b45188cea8b29b04dabf3.png

0681d44eaa164778abedf67337d4529f.png

dbe9502773a24ac2ab9ffb35aa0221b7.png

aa4981c90801442f925aefeab48124ca.png

84e395e1ee594ee6bf8dc516f5b458ab.png

b3936ad62ff14b6fbd2f57f94d741637.png

a1ba975565bb468db8334018901d795a.png

        重点看DubboInvoker#invoke()方法(方法在父类AbstractInvoker中),代码如下:

6f270b32ca2648bf9f9738ae03de2706.png

9267bd59fbeb422fb4ac426bf836f58f.png

88601a416c484969929c3472f29ad4f2.png

f987046600014471a2d62ec96a50a342.png

        此时得到的isOneWay为false,因此走else的逻辑,重点看ExchangeClient#request()方法,代码如下:

89e05f8e53704029bce04c74a6594813.png

        看看这个channel是什么对象,代码如下:

04c2e37a81674ccfa9a3ab80c2c51748.png

        还要再看看client是什么对象,代码如下:

37db35fa11264a848890874137a935c1.png

5129cfedf92b46df878b00513f332fdf.png

        再看看Transporters#connect()方法,代码如下:

16c72f861b1d4960a4dbd19a6dcabfc7.png

00280d602e4e4524b8e3f3e5d19abf18.png

        因此这里的client属性为NettyClient对,回到HeaderExchangeClient#request()方法,代码如下:

b9375c542e8a46a6a80bdc20f489cf36.png

f8f8c22f0df741e2999862bd3aaa5172.png

9f33dacc0dbd4efa9dcf08a87c19fabe.png

fd6eaa5e03244313bbb280d78948ded8.png

        看看NettyClient#getChannel()方法,获取的Channel对象是什么,代码如下:

724107c3e8aa41e28d000c8fcdf71aa7.png

2240d46bf6d74b049996bbe6252edb31.png

        NettyClient#getOrAddChannel方法入参 ch即为下面赋值的结果:

b9e99209ee4344b3a46fbf50d5242fc4.png

        回到AbstractClient#send()方法,因此channel#send()方法的结果如下:

e25a8512d93d454da89839746e590f32.png

d54bf5e250464092873a5f32dcce4aa2.png

        这里的channel对象就是前面截图中跟服务提供方建连的newChannel对象。由Netty的源码可知(参看《Netty源码深度解析》),如果消息向外写出,会经过一系列的ChannelOutboundHandler对象,看看之前往ChannelPipeline中添加了哪些ChannelOutboundHandler对象,看NettyClient#doOpen()方法,代码如下:

a359529f709e42a3b1ae83d58f2ee290.png

        其中IdleStateHandler和NettyClientHandler,即实现了ChannelInboundHandler接口,也实现了ChannelOutboundHandler接口,也就是说读取数据以及写数据都要讲过这两个ChannelHandler对象。我这里当然是重点讲NettyClientHandler。由于是向服务提供方写数据,因此顺序是NettyClientHandler->IdleStateHandler->InternalEncoder(从tail到head)。因此,先看NettyClientHandler#write()方法,代码如下:

        看看NettyClientHandler父类的write()方法,代码如下:

        获取下一个AbstractChannelHandlerContext对象,准确地讲是DefaultChannelHandlerContext,它的handler属性即为ChannelOutboundHandler对象,代码如下:

        顺便聊一下IdleStateHandler是如何实现心跳机制的,回到NettyClient#doOpen()方法,代码如下:

        如果没有设置心跳间隔,默认是一分钟,看看IdleStateHandler的有参构造,代码如下:

        看看IdleStateHandler#initialize()方法,代码如下:

        既然是定时任务,核心当然是看IdleStateHandler.ReaderIdleTimeoutTask#run()方法了,代码如下:

        看看IdleStateHandler#channelIdle()方法,代码如下:

        其实还有一个问题,lastReadTime只是在IdleStateHandler#initialize()方法中赋值了,这个属性应该还在其他的地方放赋值才对,比如IdleStateHandler#channelRead(),实际上是在IdleStateHandler#channelReadComplete()方法中进行赋值的,代码如下:

        回到IdleStateHandler#write()方法,最终会调到InternalEncoder#write()方法,代码如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值