快来!我从源码中学习到了一招Dubbo的骚操作! (下)

简介: 快来!我从源码中学习到了一招Dubbo的骚操作! (下)

EchoService实现原理-小心求证

先看截图:


demoService 这个服务引用是一个动态代理的类。


可以清楚的看到,它其实是有三个方法的:


EchoService 的 $echo 方法。这个方法就是我们要找的方法。


DemoService 的 sayHello 方法。这个方法是我们提供的方法。


Destroyable 的 $destory 方法。这个方法可以先不关心,最后我会简单的说一下。


所以,接下来,我们只需要找到生成动态代理类的地方,把 Dubbo 给我们生成的动态代理类打印出来,看一下就知道了是怎么回事了。


那么,我们在哪里创建的代理对象呢?


代码的入口为:


org.apache.dubbo.rpc.ProxyFactory#getProxy(org.apache.dubbo.rpc.Invoker<T>)


可以看到,这是一个 SPI 接口:



其默认实现是 javassist 的方式。


这个 SPI 接口的实现类有下面这三个:


stub 是做本地存根用的,不是本文重点,大家了解一下就行,其对应的官网介绍如下:

http://dubbo.apache.org/zh-cn/docs/user/demos/local-stub.html


jdk 和 javassist 是代理工厂的具体实现。


那为什么没有用 CGLIB 呢?


别问,问就是:别慌,等下再说。



到这里,面试题也就随之而来了:


请问 Dubbo 提供了哪些动态代理的实现方式?其默认实现是什么呢?


记住啦,只有 jdk 和 javassist 的实现方法,没有 CGLIB。其默认实现是 javassist。


所以,接下来我们主要看看 javassist 的实现过程:


在下面方法的第 79 行打上断点:


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


标号为 ① 的地方是获取 interfaces 配置,本文中示例为 null,所以不会走进该 if 分支中。


标号为 ② 的地方是判断是否需要泛化调用,默认是 false


标号为 ③ 的地方才是我们需要关注的地方。


哟,这不是巧了吗,这不是?


这里有我们自己的接口 DemoService,还有我们要找的接口 EchoService。


接下来 79 行会去调用 82 行的抽象方法 getProxy:


而这个方法,前面我们说了,有两个实现类。我们主要看默认实现 javassist。

最终会走到这个方法来:


org.apache.dubbo.common.bytecode.Proxy#getProxy(java.lang.ClassLoader, java.lang.Class<?>...)


这个方法的代码特别长,而且很难读懂。所以我就不带着大家一行行的解读了。


先看个大概:


主要是要理解 136 行的 ccp 和 ccm 是干啥的。这是这个方法最重要的东西。


ccp 用于为服务接口生成代理类,我们示例中的 DemoService 接口的动态代理对象,就是由 ccp 生成的。


ccm 用于为 org.apache.dubbo.common.bytecode.Proxy 抽象类生成子类,主要是实现 Proxy 类的 newInstance 抽象方法。


我常常说源码之下无秘密,这两个类是由源码生成的源码,不能直观的看到。


接下来,配合 idea 的 Evaluate Expression 计算表达式窗口教大家一个骚操作。


在 Debug 模式下,按快捷键 Alt + F8 就可以打开Evaluate Expression计算表达式窗口。


先看 ccp,通过 debugWriterFile 命令就能把生成的代理类写到本地(注意是首字母小写的 proxy0):


同理,ccm 也可以这样取出来,这里我们换一个目录(注意是首字母大写的 Proxy0)::


然后我们把生成在本地的代理类打开看一下,D 盘这个 Proxy0.class 就是 ccm 生成的,很简单,大家看一下就行:


玄机就藏在 13 行这个 proxy0 里面,而这个 proxy0,就是 ccp 生成的动态代理对象,也就是我们放在 E 盘的 proxy0:


从 15 行可以看出,这个代理类不仅实现了我们的 DemoService 接口,还悄悄帮我们实现了 EchoService 接口。


所以我们之前的第一个猜测是正确的。DemoService 这个服务引用是由框架帮我们实现了 EchoService 接口。


这样,强制类型转换的时候就不会有问题了。


那么这个接口的方法 $echo 是怎么实现的呢?


你只有一个动态代理也没有用啊,没有地方去实现这个方法,真正调用的时候也会出错的呀。


这个时候就要祭出 Dubbo 的 Filter 链了:


在 EchoFilter 这个拦截器里面,判断了如果调用方法是 $echo,有且仅有一个参数,就直接把参数返回。


走到这个 EchoFilter 拦截器了,就说明服务是可用的了,探测任务已经完成,也就不需要继续往下走了。


在这个过程中,这个 EchoFilter 拦截器相当于是方法的具体实现了。


动态代理的类里面有这个方法,但实际上这个方法没有具体实现。


这是障眼法啊,这操作够骚啊。


所以,我们前面的这个猜测是不正确的:框架帮我们实现了 $echo 方法,方法的逻辑是保证其出参和入参一致。


框架并没有帮我们实现 $echo 方法,而是基于其拦截链机制,拦截到是这个方法后,就返回入参,相当于另外一种方法的实现。


有的同学就说了,我的系统里面倒是用到了动态代理,但是我也没有这种拦截链的机制啊。


朋友,思维发散点,别只盯着拦截链呀。


给大家简单的看一下 $destory 方法的操作方式,你就明白了。


这是 Dubbo 2.7.5 版本之后加入的停机相关的方法,也是所有代理对象都自动实现 Destroyable 接口。


给大家上一个对比图吧,左边是 Dubbo 2.7.4.1 版本生成的动态代理类,右边是Dubbo 2.7.7 版本生成的动态代理类:


$destory 这个方法的障眼法是怎么使的呢?还是基于 Filter 吗?


你想一想,现在是要销毁这个代理了,是不是应该在方法调用的时候就立即触发了,还花这么大劲走到 Filter 里面去干啥?

给大家演示一下:


这个方法是怎么被拦截的呢?

请看,直接在 invoke 里面,方法调用的入口处就“硬编码”的拦截住了,就是这么灵性:


和destory和echo 的实现差不多,只是拦截时机不同而已。


所以,其实这就是一种思想,基于动态代理我们可以搞很多事情,接口里面的方法,也不是非得实现,只要我们能拦截到这个方法就行。


关键是,你得分析清楚,在什么时机去拦截。


所以,我们能从 Dubbo 源码中学到的这个骚操作是在创建动态代理对象的时候,可以神不知鬼不觉的给代理对象加一个接口,而且不需要真正的去实现接口里面的方法,只需要拦截下来就行。


这个时候,你再回想回想 Mybatis ,是不是也是只有接口,没有实现类,也是通过动态代理的方式把接口和 SQL 关联起来的。


你就想,多联想,品一品这个味道。自己多咂摸咂摸。


Filter里面搞点事情


$echo 既然它是基于 EchoFilter 的,而 Filter 又是一个 SPI 接口。那我们又可以搞事情了。


比如我们小小的改动一下,返回这个请求是负载到了哪个服务提供者中:


需要注意的是我们的自定义 Filter 需要在框架的 EchoFilter 之前执行。


所以,我们的 order 需要比 EchoFilter 小一点。


至于怎么配置让我们自定义的 WhyEchoFilter 生效,这里就不介绍了,大家可以去查一下。


配置好之后,跑一下测试用例,就会走到我们自定义的 WhyEchoFilter 中:


可以看到,输出的时候带出了这个请求是负载到了 20882 端口的服务提供者。

这里只是一个小例子,invoker 参数里面的信息非常的丰富,大家可以自由发挥。 集群模式怎么搞


不知道大家有没有发现一个问题。


一次请求只会调用到一个服务提供者(负载均衡配置的是广播模式的不在这次的考虑范围内)。


一般来说我们都有两个以上的服务提供者。


基本本文的需求,我们一次探测,应该调用到所有的服务提供者,这样才放心。


所以,核心问题是要获取到所有的服务提供者,那我们怎么实现这个需求呢?


首先肯定不能在 Filter 里面搞事情了,因为走到 Filter 的时候,已经经过负载均衡后选定了某一个服务提供者了。


我这里没有去实现这个需求,但是提供两个思路,源码里面都有,我们可以照葫芦画瓢:

第一个思路是看看 Dubbo 源码里面怎么获取到所有 invokers 的:


org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke


第二个思路是看看 Dubbo-Admin 管理台对应的源码里面是怎么获取到这个列表的:


实现起来可能会有点麻烦,但是源码都摆在上面的两个思路里面了。借鉴一下就行了。

目录
相关文章
|
7月前
|
Dubbo Java 应用服务中间件
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
|
7月前
|
缓存 Dubbo Java
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
|
7月前
|
Dubbo Java 应用服务中间件
从源码全面解析 dubbo 服务端服务调用的来龙去脉
从源码全面解析 dubbo 服务端服务调用的来龙去脉
|
7月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
2月前
|
存储 负载均衡 监控
dubbo学习一:zookeeper与dubbo的关系,下载安装启动zookeeper(解决启动中报错)
这篇文章是关于Apache Dubbo框架与Zookeeper的关系,以及如何下载、安装和启动Zookeeper的教程,包括解决启动过程中可能遇到的报错问题。
67 3
dubbo学习一:zookeeper与dubbo的关系,下载安装启动zookeeper(解决启动中报错)
|
2月前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。
|
2月前
|
监控 Dubbo Java
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
这篇文章详细介绍了如何将Spring Boot与Dubbo和Zookeeper整合,并通过Dubbo管理界面监控服务注册情况。
101 0
dubbo学习三:springboot整合dubbo+zookeeper,并使用dubbo管理界面监控服务是否注册到zookeeper上。
|
2月前
|
Dubbo IDE Java
dubbo学习二:下载Dubbo-Admin管理控制台,并分析在2.6.1及2.6.1以后版本的变化
这篇文章是关于如何下载和部署Dubbo管理控制台(dubbo-admin)的教程,并分析了2.6.1版本及以后版本的变化。
52 0
dubbo学习二:下载Dubbo-Admin管理控制台,并分析在2.6.1及2.6.1以后版本的变化
|
7月前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
543 0
|
7月前
|
Dubbo Java 应用服务中间件
微服务框架(十七)Dubbo协议及编码过程源码解析
  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   本文为Dubbo协议、线程模型、和其基于Netty的NIO异步通讯机制及源码