第一次听到dubbo都忘记是啥时候了,好像是是dubboX来着,没记错的话还是当当网拿去弄的,再到后面第一次使用dubbo开发项目,已经是18年底,那时候公司架构走向SOA,作为职场小白的我调调别人写的API,管他啥是dubbo,能用就行,可开心了(这种态度非常非常非常不可取!!别人家优秀的人已经更加优秀,自己却还在傻乐,这怎么行!!)
一转眼两年过去了,着手了解了解它是怎么玩的,挺好~
总有人说源代码有啥好看的,看了没多久就忘了,又费时又费力,有那时间,多了解几个组件的运用不香吗
看个人吧,就像以前我们背文言文,不是也费时费力,现在也没记住几句话,但是不能否认,它潜移默化的加强了我们对中华文字的理解不是??看源码也是这个道理吧。
本文 dubbo 版本 2.7.10-SNAPSHOT
参考资料:
《深入理解Apache Dubbo与实战》
《深入刨析Apache Dubbo核心技术内幕》
网易云课堂 《Dubbo分布式底层rpc实现》
bilibili 《没人比我更了解dubbo源码!手把手带你读dubbo源码底层》
腾讯课堂 《dubbo系列公开课》
dubbo 官方文档 (官方文档的版本竟然是旧的!!!!!)
ps(由于资料的时效性等原因,以上资料版本与博主编写的版本差距有些大,实现代码也有较大偏差,故只能参考其大致原理,以下博文均博主自行理解,若有偏差欢迎指正)
首先需要下载源码进行阅读:
可是github下载太慢了,我这边附上码云的连接,咱上码云下就好了
https://gitee.com/apache/dubbo
话说这官网的描述以前好像是高性能RPC框架来着…现在变成java服务框架了,看来他们在下一盘大棋!!
要了解 dubbo ,那么除了dubbo的导出引用等,它的 SPI 机制是绕不过去的一道坎。
度娘上有几张觉得还不错的图,那我就直接引用不自己画了,毕竟老是被吐槽画图画的丑也挺打击人的 ┑( ̄Д  ̄)┍
执行的流程图如下:
那么先简单描述一下什么是 SPI :将接口的实现类配置在文件中,再由系统读取加载,用来达到动态替换接口实现类的结果
dubbo 的 SPI 算是 java SPI 的加强版,支持 按需加载(根据参数等获取所需要的实现类,AOP 等。
举个例子,dubbo 是支持多协议的,比如 发布了一个服务,可支持 http,rest 等等一系列协议
我们在使用的时候却只要配置 在 dubbo 提供的 @Service 中 ,protocol 配上对应的值就可以了,那么是不是很神奇?接下来就以rest 为例:
Protocol 是 协议的一个接口,它下面有上面图片所示的那么多实例,那么当使用的时候,具体要加载哪一种实例呢,上面的例子加载的就是 rest 协议的具体实现,加载之后会再经过装饰着模式对它赋予更多的职能,所以加载完的对象实体是
加载的实体配置在这读取:
接下来就是具体描述如何加载出这一实体的过程。主要分为两部分。1:获得扩展加载器 / 2:获得实体
主要方法 ExtensionLoader.getExtensionLoader(Class type)
核心代码比较简单,EXTENSION_LOADERS 是一个 ConcurrentHashMap,当使用的时候会去里面找,找到了就进行获取,没有找到就进行加载,继而进行获取,加载方法如下:
关于 对象工厂 objectFactory,本质是利用ExtensionFactory得到某个扩展的对象实例,这个后续在详细说明。
主要方法 getExtension,根据名称获得实例
重点方法 createExtension
对于是否创建包装器,这边默认是true,只有配置的时候才是false;
先讲讲 getExtensionClasses ,毕竟是由这一步读取了相关的配置进行加载的。
在接口上 加上 SPI 注解就是在这里解析的。
这是三种加载方式,那么这三种方式是哪里加载的呢?
其中读取了我们配置路径的,是它!! DubboInternalLoadingStrategy
就这样,我们写在配置文件里面的实现类,被加载了
紧接着我们回到方法最初
上面的文件 读取了key 为 rest value 为 org.apache.dubbo.rpc.protocol.rest.RestProtocol 的实体类
我们继续观察ioc注入的过程
里面最主要的是从对象工厂获取对象是怎么操作的,此处对于对象工厂加以详细的描述。
首先对象工厂的创建是在创建扩展类加载器的时候创建的。
ExtensionFactory 有三个实现,但是 objectFactory 创建的时候调用了 getAdaptiveExtension 是调用了AdaptiveExtensionFactory 的 getExtension,为什么呢? 因为
而程序对这个注解进行了解析
但是 objectFactory 是如何拿到 实体进行注入的呢?? 其实很简单,它回去spring 中寻找,找不到会在 加上 SPI注解中寻找。
过程如下
若没有在Spring 的工厂找到实体,那么就会寻找 Adaptive 注解生成代理类。
最后讲解一下装饰者的操作。
这边加载了两个装饰者,也是通过配置读取的。
至此,dubbo 的 SPI 源码讲解到此结束。
从这边开始就进入了dubbo主流程的分析。
无论是我还是周围使用dubbo的人,大都认为,dubbo 所进行的 就是生成服务之后,注册到注册中心进行调用,但是这个观念再了解源代码之后,严谨的说,是不对的,原本这一章节只是想讲注册,但是原本认为的注册是包括可调用,可是真实情况的注册 只是包含了一小部分功能,远没有到消费者可以调用的地步,它对于这个流程有个名词,叫 服务导出!!
服务导出包括以下的部分
URL 的描述还是需要讲解一下的,毕竟后面URL是核心。
URL 是一个抽象的概念,是包含着dubbo服务信息的路径,dubbo将配置的信息,以及具体实现代理类的信息参数都放在URL上,最终将这信息保存在注册中心中。先有这么一个概念就行了,接下来我们开始看代码。
首先我们从官方提供的demo进行代码的跟踪
dubbo 服务启动的时候,会将DubboBootstrapApplicationListener注册为监听器,监听ContextRefreshedEvent事件
接下来对其进行分析
ApplicationModel.initFrameworkExts();
Environment是FrameworkExt实现类。Environment的initialize方法首先从ConfigManager获取默认的配置中心对象ConfigCenterConfig,如果存在ConfigCenterConfig对象,那么将ConfigCenterConfig的externalConfiguration和appExternalConfiguration两个属性值设置到Environment的名字一致的两个属性上。
startConfigCenter()
refresh 主要做的就是配置的设置,高优先级的配置会覆盖低优先级的配置。
覆盖的优先级,从大到小为系统变量->配置中心应用配置->配置中心全局配置->注解或xml中定义->dubbo.properties文件
该方法是将DubboBootstrap对象注册为监听器。
本质是用于暴露服务,服务端使用。
exportMetadataService() 暴露本地元数据服务,服务暴露后客户端可以使用MetadataService接口方法查看dubbo实例的元数据。
registerServiceInstance() 将dubbo实例注册到专用于服务发现的注册中心
referServices 执行服务引入,用于处理ReferenceConfig对象,以及异步处理的任务添加操作。
现在讲讲具体的 export 过程
首先在上面的 exportServices 中我们有说名了这是核心代码,我们在这可以看出ServiceConfig 设置了bootstarp,所以后续不需要进行bootstarp的初始化。
doExportUrlsFor1Protocol 这个方法有点长,没有看的很懂,但是精髓还是找得到的!!!!它做的事情就是构建 URL ,用于待会服务的具体导出工作。
这边就体现出了 URL 的重要性,它包括了注册的信息以及具体实现服务的信息。
至此,服务导出的大致源码分析完了。
上面大致分析了服务端的相关代码,服务的启动,以URL形式注册到注册中心等等,dubbo实质分为两大块,一块是服务端,还有一块就是客户端了,接下来讲解客户端相关的操作。
流程大致是:构建 Invoker ,接着创建服务目录,服务目录从注册中心上所有相关的数据,比如一个服务有对应两个提供者,那么这些数据就会存储在服务目录里面,后续调用直接查找目录而不用查找注册中心了,服务目录会实时的去注册中心上更新数据。
Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。
服务引用的入口方法为 ReferenceBean 的 getObject 方法,该方法定义在 Spring 的 FactoryBean 接口中,ReferenceBean 实现了这个方法。实现代码如下:
接下来讲解一下 init 方法,有上面讲过的代码旧不重复赘述了。
checkAndUpdateSubConfigs();resolveFile();
这里得到实体对应的方法进行保存,若没有方法则会报错,这也是 dubbo服务 若没有具体调用方法会报错的原因。
最终的包装信息如下:
从字面意思上来看,createProxy 似乎只是用于创建代理对象的。但实际上并非如此,该方法还会调用其他方法构建以及合并 Invoker 实例
这里详细看一下 ConfigValidationUtils.loadRegistries ,这个还蛮重要的
多注册中心的URL是在这进行处理的
Invoker 是 Dubbo 的核心模型,代表一个可执行体。在服务提供方,Invoker 用于调用服务提供类。在服务消费方,Invoker 用于执行远程调用。Invoker 是由 Protocol 实现类构建而来。
DubboProtocol refer
getClients 这个方法用于获取客户端实例,实例类型为 ExchangeClient。ExchangeClient 实际上并不具备通信能力,它需要基于更底层的客户端实例进行通信。比如 NettyClient、MinaClient 等,默认情况下,Dubbo 使用 NettyClient 进行通信。
RegistryProtocol refer
上面代码首先为 url 设置协议头,然后根据 url 参数加载注册中心实例。然后获取 group 配置,根据 group 配置决定 doRefer 第一个参数的类型。这里的重点是 doRefer 方法,如下:
doRefer 构建了一个消费者URL,根据SPI机制获取注册监听集合进行监听,最终返回生成的 invoker
Invoker 创建完毕后,接下来要做的事情是为服务接口生成代理对象。有了代理对象,即可进行远程调用。代理对象生成的入口方法为 ProxyFactory 的 getProxy。
有两种具体实现,jdk的是没玩过,javassist的是按照规则前些一系列字符串生成.class然后再通过 classLoad 去加载的,并不怎么好玩。
首先是通过 Proxy 的 getProxy 方法获取 Proxy 子类,然后创建 InvokerInvocationHandler 对象,并将该对象传给 newInstance 生成 Proxy 实例。InvokerInvocationHandler 实现 JDK 的 InvocationHandler 接口,具体的用途是拦截接口类调用。
最后。
这个(大概率)就是进行网络的分发相关机制有关。
无论是书籍还是视频还是官方文档,对于这一块的讲解,说实话我没怎么看懂,只能理解个大概,那么主要分两块讲解,消费端,服务端。
在进行源码分析之前,我们先来通过一张图了解 Dubbo 服务调用过程。
首先上面的章节说过了,服务再导出的时候,会根据协议创建Server ,比如协议是 dubbo,那么它就会创建 NettyServer,消费端会创建NettyClient 通过 socket 连接 进行数据的传输。
还记得之前讲过,服务导出的时候走的是export方法
然后是再 openServer(url); 中创建 server
Exchangers.bind 就是去启动 NettyServer的 关键方法,里面还有个requestHandler,这个是请求处理器,就是请求消费端发过来的请求的。它有响应结果:
最终方法里的
Result result = invoker.invoke(inv);
进行服务的调用。
最终的 NettyService 在这边创建,传输层 Transporters ,我们看到绑定的时候封装了多个handler,所以处理请求的时候会执行所经过的handler,后续还嵌套了许多的 handler,最后嵌套了一个 NettyServerHandler 这里使用了责任链模式。
当消费者的请求发起后,会进入服务端 NettyServerHandler 的channelRead 方法。,这里单独的创建了一个 NettyChannel,可以把它理解为一个 socket,是为了处理请求,以及处理完请求返回数据用的。
后续会经过一个 HeartbeatHandler ,调用它的 received 方法,其中
setReadTimestamp(channel);
就是设置时间,用来判断本次调用的执行时间,判断是否超时。
这里有两个判断,分别是判断是服务消费者发起的真实请求,还是心跳检测机制,若是真实的,则继续往下走
之后走到 AllChannelHandler 的 received 方法,这边做了些蛮重要的事情,与它对应的线程模型有关,一开始接收消费者的请求线程到这边就算结束了,会把数据交给另外一批线程池进行数据的处理 executor.execute,官方文档中说:可以进行配置,使得请求在一个io线程执行,而不是像这边一样分开线程执行。
接下来会进入 DecodeHandler ,这个 Handler 主要是做解码的工作,判断是否需要解码
再往下走会进入到 HeaderExchangeHandler 将调用的结果封装成指定的对象
future.whenComplete((appResult, t) -> {
try {
//判断执行是否有异常,没有就把数据返回
if (t == null) {
res.setStatus(Response.OK);
res.setResult(appResult);
} else {
//有异常直接toString抛给消费者,让消费者看到
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(t));
}
channel.send(res);
} catch (RemotingException e) {
logger.warn("Send result to consumer failed, channel is " + channel + ", msg is " + e);
}
});
当请求完后,结果会经过另一个责任链 ,filter的责任链,对结果是否超时,异常等等进行处理,这边就不进行赘述了(逻辑和handler差不多)。
消费者端服务发送的时候会 使用 InvokerInvocationHandler 这个类
调用 invoke 方法
其中最后的
invoker.invoke(rpcInvocation).recreate();
尤为重要
它是具体构建了RPC请求进行服务端的访问,得到数据后会对数据进行判断,看是否包含exception信息,是的话就抛出异常
返回的对象是 AppResponse ,recreate 进行了异常的操作
public Object recreate() throws Throwable {
if (exception != null) {
// fix issue#619
try {
Object stackTrace = InvokerInvocationHandler.stackTraceField.get(exception);
if (stackTrace == null) {
exception.setStackTrace(new StackTraceElement[0]);
}
} catch (Exception e) {
// ignore
}
throw exception;
}
return result;
}
接下来会走到 AbstractClusterInvoker 中调用 invoke (还是责任链)
负载均衡主要是 doSelect 方法。然后经过一系列的 invoke 发消息发出去,有些注解 @DubboReference 中的配置就在里面进行业务操作。
紧接着往下走了进入到了 AsyncToSyncInvoker 的 invoker 方法。
具体的发送是在得到服务结果代码进行的
Result asyncResult = invoker.invoke(invocation);
之后流程走到 DubboInvoker 的 doInvoke 方法
CompletableFuture<AppResponse> appResponseFuture =
currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
进入到 HeaderExchangeChannel进行具体的发送 , 此处的timeout是消费端的timeout
最终 AsyncToSyncInvoker 的阻塞什么时候结束,取决于 DefaultFuture 什么时候能拿到消费端的结果。
还记得服务端提到的 NettyClient 和 NettyService,它们是相互的,Client 消费的时候会往服务端发消息,而 Server消费完的时候会往Client发消息。
至此,从dubbo服务端的启动,到消费者端消费服务就讲解完了,虽然没有每一点都讲清楚,但是总的流程走了一遍。还是那个问题,现在网络上的书籍视频乃至官网文档版本都不是最新的,代码还是有一定的差别。本篇博客是博主对着资料自行理解源代码,如果有不正确的地方,欢迎指出。
忆往昔~ ,博主第一篇博客就是讲dubbo的,现在回过头,讲的神马玩意!!
这篇算是了了一桩心愿,哈哈,完结篇?
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_29064815/article/details/114399924
内容来源于网络,如有侵权,请联系作者删除!