最近看了一下dubbo的extension部分,作为微核心重要组成的加载实际类对象的通用组件,类似于spring IOC的基础地位,确实是非常重要的功能。
本文重点介绍dubbo的容器,微核心化,以及微核心如何在运行时,从容器中选择实例,变成真核心的过程。
希望看过的朋友可以留言,不管是什么写的太差,或者哪里错了,或者没看懂,或者点赞。通过反馈来不断提高水平,写更好的文章。
网上看了一些人的分析,大多数主要功能都分析到了,这个我不写了。我只是在反思自己碰到这样的要求,要怎么做?目前项目中会怎么做?以及如何达到那样的水平?
1. 首先基本功能是什么呢,需要包含些什么内容呢?
我们已经有基于接口的微核心,在运行时,我们需要用真正的接口的实现来工作,根据接口找到类实例。
这说明,我们要设计一个接口与实现的仓库,放置好所有的接口与实现。这个仓库是一个静态类,或者有些静态属性map,有方法从中取到需要的实现对象。
如同士兵上阵了,根据各个人的功能不同,从军/火库拿自己用的装备。
怎么实现呢?简单的话:一个配置文件中写上接口与实现类,仓库启动时,可以读一下,用map存放接口与类名,获取的时候,让类new 一下就可以了。new 好了,也可以用map缓存下实例对象。下次就不用new了。
这样,首先要有一个仓库,存着每一个接口,这里的每一个接口,要存类,要存一下实例。仓库有功能,从配置中加载接口与类的对应关系文件。
如果知道java的SPI,差不多就是这个思路。我觉得一般应用中,写到这个程序就可以了。后面有时候单独做一个完善的项目时可以考虑。
一般项目中,可以把名称与处理类对象起来,放在一个统一的地方,按需要找到即可。当然你可能会直接用spring的@Autowired Map来弄。我基本对接口编程后,就这么弄的,因为实现的类都是spring里的。
更深入一点,你可以通过配置文件,在spring中创建BeanDefination,它会帮你实例化并放在容器中的。
2. 增加难度,既然是微核心,接口的话,一般都有多个类,那又如何处理呢?
如果多个类中,随便选择一个,那可以从实现类的对象中,随机选择一个返回(有明确适配类)。
如果多个类中,需要根据方法的参数,从参数中选择一个具体的实现类,那怎么处理呢?(运行时动态适配类)
如果象日志适配,与本地配置有关,那如何得到与配置有关的实现类呢?(启动时明确适配类)
那也好办啊,我们可以配置时,有一个具体类与某个参数对应关系表,先找到相关的实现类,再找与这个参数对应的实现类。如同超级适配器,先按接口找,再按参数找。
可是,为什么dubbo对参数控制的实现类,要动态生成一个个接口适配器类呢?而且所有从ExtentionLoader加载接口,基本上都返回适配类实例(至少日志除外)。动态生成的适配器类的方法中,还是仓库中找真实的实现类啊。
目前只能抽象理解成,领东西的人太多了,按类型设置一个代理人。尤其是下面提到引用,而且多层引用的时候,你用适配器类建立起层层引用关系,它们各自找自己的实现类。否则就很乱了,算是一个逻辑抽象需要吧。要不你选择通讯用某一个实现,选择传输用某一个实现,选择序列化用某一个实现,这样组合太多,因为是具体类只能现场装配引用,很仓促,哪里有问题也很难找。
再想想,如果我在spring容器中想实现一层适配器,怎么办呢?就是在接口与实现类中插一脚。那可以动态代理吧?代理的类拿到类,方法,参数后,然后再从容器中找想要的实现的类,再真正调用。
3. 再增加难度,如果我的接口实现类中,引用了另一个接口,需要配那个接口的实现类呢?
通过反射机制设置啊,循环方法名,引用的接口再从仓库里找适配的对象啊。为什么不找实际的对象(inject中的getExtension,还是从仓库取的adaptive)?因为上面提到的逻辑抽象需要啊,特别是层层引用的时候。没有适配器层,你无法预先建立起层层的引用关系。什么?“每次用到再配置实例引用”,这太麻烦也太乱了。
同样想想,如果在spring中,已经产生了动态代理类,那么动态代理类之间如何建立引用关系呢?我们知道,接口中可以有静态常量,可以设置为一个名字,动态代理时注入一个动态代理?好象不可以。除非是类,接口好象不可以相互引用吧。
4. 再增加难度,spring用的地方太多了,我的接口是一个spring实现呢?
spring本身就是一个仓库啊,里面按名字,按类型,都可以找到实现对象的。只要你给我spring的上下文,我就能找到。
上面的仓库里,要不要引用一个spring的仓库呢?,应该可以吧。
但是我们看dubbo的代码,它只是在注入另一个接口时,从适配器中选择SPI或者spring。说明最外面的一层,是不可能从spring中取的啊。
再看看dubbo中使用ExtensionLoader,基本上都返回的是adaptive的实现。说明一个spring的接口实现,外面也是套一层adaptive的。
动态实现的adaptive,里面的方法都是从仓库得到getExtension,非adaptive的。说明同一个接口不会嵌套同类adaptive,否则就是死循环了。
这样,从spi与spring中找,可以统一成一个适配器。每个接口注入引用的时候,都从这个适配器中找,通常不用spring时,还是从本仓库找。
5. 最后说明
因为都有缓存,所以适配器什么的,只产生一次,再说也是按需加载,不会太慢,也不会一下吃内存。
目前的项目中还停留在第一阶段,就是微核心接口的实现,都从spring中来的。没有适配层的需要,如果有,也会手工写一个实现接口的适配器,不会动态编译产生。如果有引用关系,用set方法设置。
6. dubbo中特色的接口实现:
1.common包里的compiler
本身有一个适配器,spi有值javassist,表示默认,解析时在loader中设置了默认。不需要动态生成适配器类。因为这个由自己来默认或者设置值决定,而不是被动的调用中由方法参数中决定,但也不是如log中,配置中定了就确定不改了。
mata文件中,只有一个适配器与两个实现。
2.common包里的logger
虽然名字叫loggerAdapter,但不是实现接口的适配,而是包装每一种日志方式的适配。这个接口有4种日志实现。是通过从loggerFacory中读配置来加载实现类,本身实现类上没有适配类。loader加载是getExtension,不是getAdaptiveExtention。
3.common包里的Serialization
这个接口中有方法注了Adaptive,所以是有适配类动态生成的,想想这个由对方告诉我应该如何序列化,也是有道理的。它有好几个实现类。
实现类要对已有的产品,比如已有的日志,已有的序列化工具进行抽象,产生给系统通用的接口与包装类。因为已有的产品长相各不相同,要统一一下。
4.common包里的ThreadPool
这个接口有默认的实现类fix,也有动态生成适配类。根据外部的请求生成适合的。
因为实现接口的几种线程池都是java本身的,不需要每种实现进行额外抽象包装,所以相对简单。
所以完整的有适配器(动态或者已有),有包装抽象接口,有对个性化产品对象的包装抽象接口实现。这些实现就是适配器选择后,得到的真正实现类。
5.rpc包里的Protocol
前面说的几个是非常简单的东东,后面这两个就非常复杂了。首先rpc是远程调用,是应用层,http、rmi,webservice都是已经有的东东。dubbo不仅自己实现了一个协议,还抽象了一层rpc,抽象的都在rpc-api模块里的。
为了把各种已经有的协议包装起来,一方面抽象出一堆接口在api里面,另一方面对每个协议进行封装,放在rpc-***里面。
rpc-api中还有通用的一些本模块的工具,比如proxyFactory,这又是一个SPI接口,又有动态适配产生。真正的实现类有两种:jdk与javassist。实现类中公共的部分产生了抽象类。
6 remote包里的Transporter
传输也有有很多已有的工具的,比如netty,mina等。同样要抽象出一层接口,并对netty与mina进行包装,以实现api。
传输对外提供的接口就是Transporter,必须在api包里,看它确实是SPI,默认是netty。也是动态生成适配器类的。
每种工具都不可能直接用,所以也包装一下,就分别在remote-***包里。
另外,remote都是传输工具,但不是都给rpc用的,也可能给register用,比如zookeeper。ZookeeperRegistryFactory里setZookeeperTransporter(*)这个方法,应该是被loader在inject时装配好的吧。这个还不确定。那dubbo用传输工具时是不是这样呢?DubboInvoker有一个构造中传入了ExchangeClient[] clients。如果是多个可用的客户端,就是clients[index.getAndIncrement() % clients.length]--轮询方式。但是还不知道是什么时候调用,把传输客户端给RPC来用的???
应该是dubbo调用时,根据url中的type,来生成的。找到:getExchanger(url).connect(url, handler);
ExtensionLoader.getExtensionLoader(Exchanger.class).getExtension(type);
再比如netty配置codec时,也会从loader,根据URL中的值来选择。说明一是拆的很开,二是从URL中得到很多值来确定多层次的具体实现类。
String codecName = url.getParameter(Constants.CODEC_KEY, "telnet");
return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);
7. 从具体到抽象的思考
dubbo的结构都是一个api模块,好几个实现模块。就以rpc为例思考一下从具体到抽象的过程。
首先要用真正的实现熟悉这个过程,才能了解都有什么对象参与其中。再进行抽象。这时,rpc是远程方法调用,算是TCP传输层之上的应用层协议(protocol)设计。dubbo的客户端(client),基本上是这个过程。启动中,一个接口要被代理(invoker),代理用来通过传输工具(transport)与远程服务进行连接。调用的时候,把调用有关的请求(invocation)数据,发过去。除了接口,里面还有一些通用的对象与操作,又要弄出抽象类。此工作非常的复杂。
这部分工作由于要清楚多个现成的外部工具,比如netty,mina都搞熟悉了,才可以准确抽取出公共部分。形成一个功能模板(以抽象类,接口为主),这个结果就是产生remote-api模块。接着需要对已有的工具(引入的netty.jar)进行包装,以满足标准模板的样子,结果就是产生相应的实现模块remote-default。这样,其它地方就可以通过配置,以通用的方式操作这些工具了。
我们平时写一些操作,如果有通用的部分,就会想到抽象类中写,就是简单的从具体到抽象的过程。但很复杂的工具中抽取接口,抽象类,通用的模板设计模式。要把工具摸透了,而且抽象思维才行。
8. 从抽象到具体的思考
主要的接口与关系概念就有了。当你有很多实现可能的时候,又如何从抽象到具体呢?运行的时候,可都是具体的对象在工作啊,接口是不能工作的。而最外层的确定往往是配置文件。
比如定了用dubbo协议,客户端肯定要产生一个动态代理用的invoker,这时明确是dubboInvoker了,可以new出来实例,但返回的一定要是接口。
invoker要把请求内容出去,这时肯定要确定好传输实例是谁了。getExchanger(url).connect(url, handler);,还是从url中找到,默认是header,还是来源于配置传过来的参数URL。
new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))));
header只是一层包装,如何确定包的真正client是谁?还是从url中找。
上面header中参数里跟踪找到getTransporter().connect(url, handler);//connect是一个adaptive,也是从url中找,找不到就是SPI中默认的了。
当new NettyClient(url, listener);时,又把url传进去,设置为属性了。HeaderExchangeClient包了一个NettyClient,当设置自己的心跳时,又从NettyClient的url中取值了。
我们知道netty要设置序列化的,是不是也从url中确认实例呢?
new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);果然是传进去了。
this.codec = getChannelCodec(url);
发现在new NettyClient过程中不断super时,就有了coder属性,this.codec = getChannelCodec(url);所以new NettyCodecAdapter时,就从getCodec中拿到已经实例好的序列化工具了。里面有:return ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(codecName);,还是用loader从容器仓库中取,但不是adaptive,是一个明确的实例了。
所以启动时,用到的材料都已经在Client生成时明确好了,就等着装配了。而所有的接口的实现类确定,基本上都是从url参数中来的,而且一层层传递到内部的。
注意:这个url是dubbo的,就是定位容器资源的。与java的URL无关。
从上可以看到,从外部置好以后,一层层不确定的东西都是靠url传进去的,url就是一个实例化手册。手册上找不到的,一定要找到默认的,否则要出错的。
9. END
这样分析一遍,LOADER差不多是整个netty的IOC容器啊,与spring容器一样,会从配置中读信息,类似记录下beanDefination一样,记录下loader的接口,默认类,适配器类,还有实例,还进行了缓存。
我目前也写比较简单的微核心功能,不过容器利用spring容器,启动时根据配置,加载到微核心中运行。再次分析dubbo此功能后,感觉整体架构能力好象提高了不少啊。
分享到:
相关推荐
本文将深入探讨Dubbo的核心概念、设计模式以及源码实现,旨在帮助读者理解其内在机制,提升在实际开发中的应用能力。 一、Dubbo概述 Dubbo的主要目标是提供一个高性能、透明化的RPC(远程过程调用)框架,使得服务...
在这样的工程中,开发者通常会复用和理解Dubbo源码中的ExtensionLoader工作原理,通过编写自己的代码来实现类似的功能,以支持自定义的扩展点和插件。 ExtensionLoader在Dubbo中的主要功能包括: 1. **加载器机制*...
通过这两本书的学习,开发者不仅可以掌握Dubbo的基本使用,还能深入理解其内部工作原理,从而在实际项目中更好地应用和定制Dubbo,提升微服务架构的设计和实施能力。对于Java开发者而言,这是一份宝贵的资源,能助其...
Dubbo是阿里巴巴开源的分布式服务化治理框架(微服务框架),久经阿里巴巴电商平台的大规模复杂业务的高并发考验,到目前为止Dubbo仍然是开源界中体系最完善的...是你学习和了解现今最流行的“微服务架构”的首选教程。
本文对dubbo源码进行了深入的解析,涵盖了dubbo的架构、核心机制分析、扩展点加载流程、代理机制、远程调用流程、集群和容错处理、监控机制等多个方面。通过阅读和理解这些内容,可以更好地掌握dubbo的内部工作机制...
总结,"dubbo示例源码及相关文档"是学习和理解Dubbo的宝贵资源。通过分析源码,我们可以了解Dubbo如何通过Spring整合,如何定义和消费服务,以及服务注册、发现、调用等过程。同时,配合文档,能更全面地掌握Dubbo的...
《Dubbo 2.0 源码解读》是一份深度剖析Dubbo核心机制和技术细节的资料,旨在帮助开发者深入理解这一著名Java微服务框架的工作原理。以下是对这份资料主要知识点的详细阐述: 1. **源码阅读路径**:源码阅读是提升...
通过对Apache Dubbo 3.0.7源码的阅读和分析,开发者可以学习到如何设计高性能的RPC框架,理解服务治理的核心原理,以及如何通过SPI机制实现系统的高度可扩展性。这对于构建大型分布式系统或微服务架构具有极大的价值...
《Dubbo源码分析系列》是一份深入探讨Java开源...通过对《Dubbo源码分析系列》的学习,开发者不仅可以掌握Dubbo的基本使用,还能深入理解其设计思想,从而更好地在实际项目中应用和优化Dubbo,提高系统的稳定性和效率。
6. **设计模式**:设计模式在Dubbo的实现中扮演着极其重要的角色,通过学习常见的设计模式,可以更好地理解Dubbo的设计思路。 #### 二、背景 Dubbo是一个高性能、轻量级的开源Java RPC框架,它提供了服务自动注册...
通过下载并分析dubbo2.5.8的源代码,开发者可以更好地了解服务治理、远程调用、集群容错等核心模块的实现,这对于提升Java后端开发能力,尤其是分布式系统设计和微服务架构的理解非常有益。 【标签】"java dubbox...
最新Dubbo的分布式架构视频教程最新Dubbo的分布式架构视频教程
总结,Dubbo的源码学习不仅可以帮助我们理解分布式服务治理的实现原理,还能提升我们的系统设计能力。通过深入分析`Provider`、`Consumer`、`Registry`、`Proxy`等核心组件,我们可以更好地运用Dubbo解决实际项目中...
Dubbo采用了Service、Provider、Consumer、Registry、Monitor五种核心角色,它们共同构成了Dubbo的服务治理架构。Service是业务接口,Provider是服务提供方,Consumer是服务消费方,Registry是服务注册中心,而...
《深入剖析淘淘商城项目:基于SSM+Dubbo+Sorl的分布式架构解析》 在IT行业中,分布式架构已经成为大型互联网应用的基石,而Dubbo作为阿里巴巴开源的一款高性能、轻量级的服务治理框架,备受业界关注。本篇文章将...
在当前快速发展的软件行业中,微服务架构作为一种重要的设计模式,已经被广泛采纳。它通过将复杂的单体应用拆分为一系列相互独立的小型服务,极大地提高了系统的可维护性和可扩展性。本文将重点探讨Dubbo与Spring ...
Dubbo的详细介绍、设计思路、以及4大适用场景
总的来说,`dubboDemo`是一个学习和实践Dubbo框架的理想起点,它涵盖了从创建服务到消费服务的基本步骤,同时也展示了Dubbo在微服务架构中的核心作用。通过深入理解和操作这个示例,开发者可以更好地理解分布式服务...