Dubbo源码学习——核心概念

以前人们计算个数是1+1+1+1...+1,简单有效,等到每次计算个数并且量越来越大的时候,这种计算模型显然是不合适的,于是就有人发明了乘法,把冗长的加法问题用一个简单的乘法符号所代替,这是概念的不断抽象带来的好处,当然我就是举个例子。在Dubbo所处的领域里面,到处都是A服务的方法method1去调用某个B服务的method2,这个method2的方法名可能会变,可能没有参数,可能有参数,而且参数的个数也可能发生变化,要对这样的远程调用做管理,那么必须抽象这种调用,必须用一种模型(概念)描述这种调用,然后基于这个抽象(概念)调用,架构出整个服务治理体系,如果抽象的不好,也就是没有涵盖每一种远程调用类型,那么你这套框架就是不完备的,必然存在特例情况,你要为这个特例情况付出沉痛代价,甚至推倒重新设计。

 

Dubbo里面的抽象(Java的接口)很多,我就不一一列举了,我把我认为重要的抽象说明下,然后再之后的博客都是围绕这些核心抽象构筑Dubbo整个体系,完成一个功能集合体。 

 

  1. URL(Uniform Resource Locator 统一资源定位):这个不是接口,但是很重要,贯穿整个Dubbo体系,功能类似Java原生的URL,但Dubbo重写了它 ,Java原生的URL专注与互联网资源的定位,Dubbo需要携带更多的东西,甚至附加的属性也放到里面,有时候可以认为它就是一个资源配置与参数的包装体即可,Dubbo对此的解释是统一参数配置,规划配置,不要使用额外的类去携带,URL就是统一的消息总线,需要什么配置,什么参数,就放到上面好了,需要定制化的东西,想都不用想,取URL上的配置即可
  2. Node:从取名上看就是个节点,如果里面没有方法,那抽象的可太离谱了,万物皆节点都可以这么理解(好比Linux系统里的一切皆文档类似),不好意思,人家里面用方法定义
    /**
     * get url.
     *
     * @return url.
     */
    URL getUrl();
    
    /**
     * is available.
     *
     * @return available.
     */
    boolean isAvailable();
    
    /**
     * destroy.
     */
    void destroy();

    加了方法,就相当于加了限定,我这个节点是能取出配置那种(getUrl),还可以判断是否有效(isAvailable),并且还能销毁(detroy),那肯定的,销毁了就出现有效失效的问题了,这就把万物皆节点的概念往小括,因为把【厕所】当成一个Node来说的话,明显超出Dubbo的管辖范围,概念是有作用域的,我得限制我的Node在服务治理这块。这个Node抽象程度就比较高了,如果它不是放到Dubbo里面,我完全有理由认为万物皆Node,那【厕所】可以归属于这个Node下,我destroy了厕所,你看看它还available不avaliable,getUrl么,不就是厕所是蹲坑式还是坐便,提不提供厕纸之类,很合理嘛

  3. Protocol:字面意思是协议,就是需要一定的共识,我还写进Protocol里了,是Dubbo的核心承诺,大家看一下

    /**
     * Protocol. (API/SPI, Singleton, ThreadSafe)
     */
    @SPI("dubbo")
    public interface Protocol {
    
        /**
         * Get default port when user doesn't config the port.
         *
         * @return default port
         */
        int getDefaultPort();
    
        /**
         * Export service for remote invocation: <br>
         * 1. Protocol should record request source address after receive a request:
         * RpcContext.getContext().setRemoteAddress();<br>
         * 2. export() must be idempotent, that is, there's no difference between invoking once and invoking twice when
         * export the same URL<br>
         * 3. Invoker instance is passed in by the framework, protocol needs not to care <br>
         *
         * @param <T>     Service type
         * @param invoker Service invoker
         * @return exporter reference for exported service, useful for unexport the service later
         * @throws RpcException thrown when error occurs during export the service, for example: port is occupied
         */
        @Adaptive
        <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    
        /**
         * Refer a remote service: <br>
         * 1. When user calls `invoke()` method of `Invoker` object which's returned from `refer()` call, the protocol
         * needs to correspondingly execute `invoke()` method of `Invoker` object <br>
         * 2. It's protocol's responsibility to implement `Invoker` which's returned from `refer()`. Generally speaking,
         * protocol sends remote request in the `Invoker` implementation. <br>
         * 3. When there's check=false set in URL, the implementation must not throw exception but try to recover when
         * connection fails.
         *
         * @param <T>  Service type
         * @param type Service class
         * @param url  URL address for the remote service
         * @return invoker service's local proxy
         * @throws RpcException when there's any error while connecting to the service provider
         */
        @Adaptive
        <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
    
        /**
         * Destroy protocol: <br>
         * 1. Cancel all services this protocol exports and refers <br>
         * 2. Release all occupied resources, for example: connection, port, etc. <br>
         * 3. Protocol can continue to export and refer new service even after it's destroyed.
         */
        void destroy();
    
    }

    我就不翻译注释了,我英文没那么好,况且强行翻译一波可能会导致偏离原作者的意图(其实是懒?),里面核心就两个方法,一个是暴露服务(export),另一个是引用服务(refer),返回结果也是两个接口(抽象概念,接下来会解释),这个Protocol打通了两端,服务提供方和服务消费方均需要一个“协议”来通讯,不然你打10086投书电信服务可能话务员会顺着电磁波打你,如果你看过源代码的话,Dubbo提供了几种协议实现,比如hessian或者http,还有很多,这里不展开。这个Protocol的抽象高于远程调用的抽象的,像个工厂一样,调用方想用何种形式调用提供方,只要提供方有用过相应的协议形式暴露过服务

  4. Invoker:调用者,看这样子是消费方调用服务费的抽象,但是Dubbo赋予这个Invoker的含义远不止于此,它抽象一个调用过程,细想消费方不能像调用本地方法那样直接调用远程服务方法,必然会经过网络传输,简单理解就是一次远程调用,分两段,一段是消费方调用方法转成网络请求,另一段是网络请求转换为被调服务提供方的本地的执行,一次调用,两次转换,每次转换都是Invoker的一次invoke,看下源码

    /**
     * Invoker. (API/SPI, Prototype, ThreadSafe)
     *
     * @see org.apache.dubbo.rpc.Protocol#refer(Class, org.apache.dubbo.common.URL)
     * @see org.apache.dubbo.rpc.InvokerListener
     * @see org.apache.dubbo.rpc.protocol.AbstractInvoker
     */
    public interface Invoker<T> extends Node {
    
        /**
         * get service interface.
         *
         * @return service interface.
         */
        Class<T> getInterface();
    
        /**
         * invoke.
         *
         * @param invocation
         * @return result
         * @throws RpcException
         */
        Result invoke(Invocation invocation) throws RpcException;
    
    }

    返回接口(getInterface)不必说,暴露的服务必须是接口契约,不会暴露具体实现的,这个invoke方法便是我认为Dubbo整个体系的最核心的抽象方法,甚至向注册中心注册也被抽象成此方法,实话说,Dubbo的好多功能就是代理此方法或者装饰增强此方法实现的,好比SpringAOP中PointCut的存在,参数必然也得抽象出来

  5. Invocation:为了能应付Java任何方法的调用,那么参数也得是泛化的,看下源码豁然开朗

    /**
     * Invocation. (API, Prototype, NonThreadSafe)
     *
     * @serial Don't change the class name and package name.
     * @see org.apache.dubbo.rpc.Invoker#invoke(Invocation)
     * @see org.apache.dubbo.rpc.RpcInvocation
     */
    public interface Invocation {
    
        /**
         * get method name.
         *
         * @return method name.
         * @serial
         */
        String getMethodName();
    
        /**
         * get parameter types.
         *
         * @return parameter types.
         * @serial
         */
        Class<?>[] getParameterTypes();
    
        /**
         * get arguments.
         *
         * @return arguments.
         * @serial
         */
        Object[] getArguments();
    
        /**
         * get attachments.
         *
         * @return attachments.
         * @serial
         */
        Map<String, String> getAttachments();
    
        void setAttachment(String key, String value);
    
        void setAttachmentIfAbsent(String key, String value);
    
        /**
         * get attachment by key.
         *
         * @return attachment value.
         * @serial
         */
        String getAttachment(String key);
    
        /**
         * get attachment by key with default value.
         *
         * @return attachment value.
         * @serial
         */
        String getAttachment(String key, String defaultValue);
    
        /**
         * get the invoker in current context.
         *
         * @return invoker.
         * @transient
         */
        Invoker<?> getInvoker();
    
    }

    泛化调用参数其实就三个就行,方法名,方法参数类型列表,方法参数实体列表即可,如果用JDK反射的话,你还得知道被调用的实体对象,当然持有这个Invocation的Invoker对象的实体肯定是知道被调用者是谁的,设计也非常复杂(你应该想到了,各种代理)。然后还非常贴心的把获取调用方参数一些方法也定义了,并且把执行的Invoker也缓存了,可以做一些校验什么的

  6. Exporter:这个是服务提供方使用协议Protocol暴露(export)服务后给的返回,看看有什么东西

    /**
     * Exporter. (API/SPI, Prototype, ThreadSafe)
     *
     * @see org.apache.dubbo.rpc.Protocol#export(Invoker)
     * @see org.apache.dubbo.rpc.ExporterListener
     * @see org.apache.dubbo.rpc.protocol.AbstractExporter
     */
    public interface Exporter<T> {
    
        /**
         * get invoker.
         *
         * @return invoker
         */
        Invoker<T> getInvoker();
    
        /**
         * unexport.
         * <p>
         * <code>
         * getInvoker().destroy();
         * </code>
         */
        void unexport();
    
    }

    上面说过,服务提供方的本地方法也是需要Invoker代理调用的,还支持反悔,不想给消费方调用了,unexport一下

  7. IOC/DI:你没有看错,Dubbo是有IOC/DI的,有类似与Spring的Bean容器扩展点工厂

    /**
     * ExtensionFactory
     */
    @SPI
    public interface ExtensionFactory {
    
        /**
         * Get extension.
         *
         * @param type object type.
         * @param name object name.
         * @return object instance.
         */
        <T> T getExtension(Class<T> type, String name);
    
    }

    Dubbo是对Java原生的SPI机制进行了改进,给每个SPI接口的实现类配一个名字,那上面那个扩展点工厂举例

    adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
    spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory

    叫adaptive这个适配扩展点可以认为是默认实现,且一个SPI接口只有一个,有些SPI接口在方法有这个@Adaptive注解,这个需要特别说明下,一旦SPI接口的方法有这个注解,那么这个SPI的实现就得启用代理了,具体用字节码生成一个新类,然后把带有@Adaptive的方法的实现进行增强,具体就是@Adaptive方法的参数中获取那个消息总线URL中与当前SPI接口实例参数,允许在运行时使用指定的扩展来执行方法,举个例子:Protocol也是SPI接口,运行时有的服务提供方有的方法想用http协议暴露,有的想用hessian协议暴露,能不能Protocol实现类自己适配下,根据相应的协议调用相应协议具体实现类进行暴露,这就是ExtensionLoader.getAdaptiveExtension()返回的就是个自适应代理类,在(export)方法执行的时候,动态选取期望的实现类再再去执行(export)方法。Dubbo在获取扩展点实例的时候,如果候选实现中有当前SPI接口的构造方法,则认为该实现类是个Wrapper类,类似Spring的构造器注入,最后返回多个实现类那种装饰器模式的效果一个Wrapper类,当然也会检测SPI实现类中的setter方法,目前Dubbo只支持setter的注入,一个SPI实现类的setter方法的接口一般也是扩展点,那么就取出实例给你注入进去,实现了DI

 

以上我挑选出来的比较重要的类和接口,接下的博客来将围绕这些重要抽象看看Dubbo是怎么一步一步把远程调用串联起来的。 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值