这周去苏州见大佬,没想到遇到一堆女粉丝,其中竟然还有澡堂子堂妹,堂妹一遇到我就说敖丙哥哥我超级喜爱你写的 dubbo 系列,你能跟我好好讲一下他的服务裸露过程么?
我笑了笑:傻瓜,你想看怎么不早点说呢?
我明天来就带大家看看 Dubbo 服务裸露过程,这个过程在 Dubbo 中其实是很外围的过程之一,关乎到你的 Provider 如何能被 Consumer 得悉并调用。
明天还是会进行源码解析,毕竟咱们须要深刻的去理解 Dubbo 是如何做的,只有深刻它能力理解它。
不必放心源码问题,因为不仅仅有源码解析,敖丙也会通过画图和总结性的语言帮忙大家了解,而且在面对面试官的时候,总结性的语言才是最重要的,因为不见得面试官也懂得或者记得具体的细节。
对了,源码是 2.6.5 版本。
URL
不过在进行服务裸露流程剖析之前有必要先谈一谈 URL,有人说这 URL 和 Dubbo 啥关系?有关系,有很大的关系!
一般而言咱们说的 URL 指的就是对立资源定位符,在网络上个别指代地址,实质上看其实就是一串蕴含非凡格局的字符串,规范格局如下:
protocol://username:password@host:port/path?key=value&key=value
Dubbo 就是采纳 URL 的形式来作为约定的参数类型,被称为公共契约,就是咱们都通过 URL 来交互,来交换。
你想一下如果没有一个束缚,没有指定一个都公共的契约那么不同的接口就会以不同的参数来传递信息,一会儿用 Map、一会儿用特定分隔的字符串,这就是导致整体很乱,并且解析不能对立。
而 用了一个对立的契约之后,那么代码就更加的规范化、造成一种对立的格局,所有人对参数就高深莫测,不必去推测一些参数的格局等等。
而且 用 URL 作为一个公共束缚充沛的利用了咱们对已有概念的印象,通俗易懂并且容易扩大,咱们晓得 URL 要加参数只管往后面拼接就完事儿了。
因而 Dubbo 用 URL 作为配置总线,贯通整个体系,源码中 URL 的身影无处不在。
URL 具体的参数如下:
- protocol:指的是 dubbo 中的各种协定,如:dubbo thrift http
- username/password:用户名 / 明码
- host/port:主机 / 端口
- path:接口的名称
- parameters:参数键值对
配置解析
个别罕用 XML 或者注解来进行 Dubbo 的配置,我略微说一下 XML 的,这块其实是属于 Spring 的内容,我不做过多的剖析,就略微讲一下大略的原理。
Dubbo 利用了 Spring 配置文件扩大了自定义的解析,像 dubbo.xsd 就是用来束缚 XML 配置时候的标签和对应的属性用的,而后 Spring 在解析到自定义的标签的时候会查找 spring.schemas 和 spring.handlers。
spring.schemas 就是指明了束缚文件的门路,而 spring.handlers 指明了利用该 handler 来解析标签,你看好的框架都是会预留扩大点的,讲白了就是去固定门路的固定文件名去找你扩大的货色,这样能力让用户灵便的应用。
咱们再来看一下 DubboNamespaceHandler 都干了啥。
讲白了就是将标签对应的解析类关联起来,这样在解析到标签的时候就晓得委托给对应的解析类解析,实质就是为了生成 Spring 的 BeanDefinition,而后利用 Spring 最终创立对应的对象。
服务裸露全流程
咱们在深刻源码之前来看下总的流程,有个大抵的印象看起来比拟不容易晕。
从 代码的流程 来看大抵能够分为三个步骤(本文默认都须要裸露服务到注册核心)。
第一步是检测配置,如果有些配置空的话会默认创立,并且组装成 URL。
第二步是裸露服务,包含裸露到本地的服务和近程的服务。
第三步是注册服务至注册核心。
从 对象构建转换的角度 看能够分为两个步骤。
第一步是将服务实现类转成 Invoker。
第二部是将 Invoker 通过具体的协定转换成 Exporter。
服务裸露源码剖析
接下来咱们进入源码分析阶段,从下面配置解析的截图标红了的中央能够看到 service 标签其实就是对应 ServiceBean,咱们看下它的定义。
这里又波及到 Spring 相干内容了,能够看到它实现了 ApplicationListener<ContextRefreshedEvent>
,这样就会 在 Spring IOC 容器刷新实现后调用 onApplicationEvent
办法,而这个办法外面做的就是服务裸露,这就是服务裸露的启动点。
能够看到,如果不是提早裸露、并且还没裸露过、并且反对裸露的话就执行 export 办法,而 export 最终会调用父类的 export 办法,咱们来看看。
次要就是查看了一下配置,确认须要裸露的话就裸露服务,doExport 这个办法很长,不过都是一些检测配置的过程,虽说不可或缺不过不是咱们关注的重点,咱们重点关注外面的 doExportUrls 办法。
能够看到 Dubbo 反对多注册核心,并且反对多个协定,一个服务如果有多个协定那么就都须要裸露,比方同时反对 dubbo 协定和 hessian 协定,那么须要将这个服务用两种协定别离向多个注册核心(如果有多个的话)裸露注册。
loadRegistries 办法我就不做剖析了,就是依据配置组装成注册核心相干的 URL,我就给大家看下拼接成的 URL 的样子。
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=7960&qos.port=22222®istry=zookeeper×tamp=1598624821286
咱们接下来关注的重点在 doExportUrlsFor1Protocol 办法中,这个办法挺长的,我会截取大抵的局部来展现外围的步骤。
此时构建进去的 URL 长这样,能够看到走得是 dubbo 协定。
而后就是要依据 URL 来进行服务裸露了,咱们再来看下代码,这段代码我就间接截图了,因为须要断点的解释。
本地裸露
咱们再来看一下 exportLocal 办法,这个办法是 本地裸露,走的是 injvm 协定,能够看到它搞了个新的 URL 批改了协定。
咱们来看一下这个 URL,能够看到协定曾经变成了 injvm。
这里的 export 其实就波及到上一篇文章讲的自适应扩大了。
Exporter<?> exporter = protocol.export(proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
Protocol 的 export 办法是标注了 @ Adaptive 注解的,因而会生成代理类,而后代理类会依据 Invoker 外面的 URL 参数得悉具体的协定,而后通过 Dubbo SPI 机制抉择对应的实现类进行 export,而这个办法就会调用 InjvmProtocol#export 办法。
咱们再来看看转换失去的 export 到底长什么样子。
从图中能够看到实际上就是具体实现类层层封装,invoker 其实是由 Javassist 创立的,具体创立过程 proxyFactory.getInvoker 就不做剖析了,对 Javassist 有趣味的同学自行去理解,之后可能会写一篇,至于 dubbo 为什么用 javassist 而不必 jdk 动静代理是 因为 javassist 快。
为什么要封装成 invoker
至于为什么要 封装成 invoker 其实就是想屏蔽调用的细节,对立暴露出一个可执行体,这样调用者简略的应用它,向它发动 invoke 调用,它有可能是一个本地的实现,也可能是一个近程的实现,也可能一个集群实现。
为什么要搞个本地裸露呢
因为可能存在同一个 JVM 外部援用本身服务的状况,因而 裸露的本地服务在外部调用的时候能够间接生产同一个 JVM 的服务防止了网络间的通信。
能够有些同学曾经有点晕,没事我这里立马搞个图带大家过一遍。
对 exportLocal 再来一波时序图剖析。
近程裸露
至此本地裸露曾经好了,接下来就是近程裸露了,即上面这一部分代码
也和本地裸露一样,须要封装成 Invoker,不过这里相对而言比较复杂一些,咱们先来看下 registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()) 将 URL 拼接成什么样子。
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo://192.168.1.17:20880/com.alibaba.dubbo.demo.DemoService….
因为很长,我就不截全了,能够看到走 registry 协定,而后参数里又有 export=dubbo://,这个走 dubbo 协定,所以咱们能够得悉会先通过 registry 协定找到 RegistryProtocol 进行 export,并且在此办法外面还会依据 export 字段失去值而后执行 DubboProtocol 的 export 办法。
大家要挺住,就快要实现整个流程的解析了!
当初咱们把眼光聚焦到 RegistryProtocol#export 办法上,咱们先过一遍整体的流程,而后再进入 doLocalExport 的解析。
能够看到这一步次要是将下面的 export=dubbo://… 先转换成 exporter,而后获取注册核心的相干配置,如果须要注册则向注册核心注册,并且在 ProviderConsumerRegTable 这个表格中记录服务提供者,其实就是往一个 ConcurrentHashMap 中将塞入 invoker,key 就是服务接口全限定名,value 是一个 set,set 外面会存包装过的 invoker。
咱们再把眼光聚焦到 doLocalExport 办法外部。
这个办法没什么难度,次要就是依据 URL 上 Dubbo 协定暴露出 exporter,接下来就看下 DubboProtocol#export 办法。
能够看到这里的要害其实就是关上 Server,RPC 必定须要近程调用,这里咱们用的是 NettyServer 来监听服务。
再上面我就不跟了,我总结一下 Dubbo 协定的 export 次要就是依据 URL 构建出 key(例如有分组、接口名端口等等),而后 key 和 invoker 关联,关联之后存储到 DubboProtocol 的 exporterMap 中,而后如果是服务首次裸露则会创立监听服务器,默认是 NettyServer,并且会初始化各种 Handler 比方心跳啊、编解码等等。
看起来如同流程完结了?并没有,Filter 到当初还没呈现呢?有暗藏的措施,上一篇 Dubbo SPI 看的认真的各位就晓得在哪里触发的。
其实下面的 protocol 是个代理类,在外部会通过 SPI 机制找到具体的实现类。
这张图是上一篇文章的,能够看到 export 具体的实现。
温习下上一篇的要点,通过 Dubbo SPI 扫包会把 wrapper 结尾的类缓存起来,而后当加载具体实现类的时候会包装实现类,来实现 Dubbo 的 AOP,咱们看到 DubboProtocol 有什么包装类。
能够看到有两个,别离是 ProtocolFilterWrapper 和 ProtocolListenerWrapper
对于所有的 Protocol 实现类来说就是这么个调用链。
而在 ProtocolFilterWrapper 的 export 外面就会把 invoker 组装上各种 Filter。
看看有 8 个在。
咱们再来看下 zookeeper 外面当初是怎么样的,关注 dubbo 目录。
两个 service 占用了两个目录,别离有 configurators 和 providers 文件夹,文件夹外面记录的就是 URL 的那一串,值是服务提供者 ip。
至此服务流程裸露差不多完结了,能够看到还是有点内容在外面的,并且还须要把握 Dubbo SPI,不然有些点例如自适应什么的还是很难了解的。最初我再来一张残缺的流程图带大家再过一遍,具体还是有很多细节,不过不是骨干我就不做剖析了,不然文章就有点散。
而后再援用一下官网的时序图。
总结
还是倡议大家本人打断点过一遍,这样可能更加的清晰,到时候面试官问起来一点都不虚,不过只有你认真看了这篇文章也差不多了,总的流程能说进去能证实你看过源码,一些细节记不住的,你想想看你本人写的代码过一两个月你记得住不?更别说他人写的了。
其实我能够不源码剖析,我能够间接口述 + 画图,观赏性更佳,然而为什么我还是贴代码呢?
想带着大家从源码级别来过一遍流程,这样能让大家更有底气,毕竟你看图了解了是一回事,真正的看到源码,就会很直观的晓得一些点,例如,缓存原来就是放一个 map 中,这过滤链原来是这样拼接的等等等等。
总的而言服务裸露的过程起始于 Spring IOC 容器刷新实现之时,具体的流程就是依据配置失去 URL,再利用 Dubbo SPI 机制依据 URL 的参数抉择对应的实现类,实现扩大。
通过 javassist 动静封装 ref (你写的服务实现类),对立暴露出 Invoker 使得调用不便,屏蔽底层实现细节,而后封装成 exporter 存储起来,期待消费者的调用,并且会将 URL 注册到注册核心,使得消费者能够获取服务提供者的信息。
明天这个就差不多了,Dubbo 系列预计还有几篇,到时候再来个面试汇总,等着吧!
我是敖丙,你晓得的越多,你不晓得的越多,咱们下期见!
人才 们的 【三连】 就是敖丙创作的最大能源,如果本篇博客有任何谬误和倡议,欢送人才们留言!