七万字长文带你搞定Dubbo!超详细解析助你过春招!

本文深入探讨了Dubbo的核心架构原理,包括节点角色、调用关系、分层设计,以及与Spring Cloud的对比。详细阐述了Dubbo的SPI实现,解释了为何不直接使用JDK的SPI,并剖析了SPI机制的 adaptive 原理。此外,文章还覆盖了Dubbo的动态编译、与Spring的融合、服务发布和服务调用过程、网络通信的IO异步同步转换、编解码机制,以及注册机制。同时,介绍了Dubbo中使用的设计模式,如工厂方法、责任链、观察者等。最后,讨论了如何设计一个类似Dubbo的RPC框架,提供了一条实现思路。
摘要由CSDN通过智能技术生成

Table of Contents generated with DocToc

1、Dubbo的架构原理

Dubbo架构图

节点角色说明

节点 角色说明
Provider 暴露服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次数和调用时间的监控中心
Container 服务运行容器

调用关系说明

  1. provider启动时,会把所有接口注册到注册中心,并且订阅动态配置configurators
  2. consumer启动时,向注册中心订阅自己所需的providers,configurators,routers
  3. 订阅内容变更时,registry将基于长连接推送变更数据给consumer,包括providers,configurators,routers
  4. consumer启动时,从provider地址列表中,基于软负载均衡算法,选一台provider进行调用,如果调用失败,再选另一台调用,建立长连接,然后进行数据通信(consumer->provider)
  5. consumer、provider启动后,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到monitor

整体架构分层设计


Dubbo框架设计一共划分了10个层,而最上面的Service层是留给实际想要使用Dubbo开发分布式服务的开发者实现业务逻辑的接口层。图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口, 位于中轴线上的为双方都用到的接口。

  • 接口服务层(Service):该层与业务逻辑相关,根据 provider 和 consumer 的业 务设计对应的接口和实现
  • 配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心
  • 服务代理层(Proxy):服务接口透明代理,生成服务的客户端 Stub 和 服务端的Skeleton,以 ServiceProxy 为中心,扩展接口为 ProxyFactory
  • 服务注册层(Registry):封装服务地址的注册和发现,以服务 URL 为中心,扩展接 口为 RegistryFactory、Registry、RegistryService
  • 路由层(Cluster):封装多个提供者的路由和负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster、Directory、Router 和 LoadBlancce
  • 监控层(Monitor):RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接 口为 MonitorFactory、Monitor 和 MonitorService
  • 远程调用层(Protocal):封装 RPC 调用,以 Invocation 和 Result 为中心,扩展 接口为 Protocal、Invoker 和 Exporter
  • 信息交换层(Exchange):封装请求响应模式,同步转异步。以 Request 和 Response 为中心,扩展接口为 Exchanger、ExchangeChannel、ExchangeClient 和 ExchangeServer
  • 网络传输层(Transport):抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel、Transporter、Client、Server 和 Codec
  • 数据序列化层(Serialize):可复用的一些工具,扩展接口为 Serialization、 ObjectInput、ObjectOutput 和 ThreadPool

根据官方提供的,对于上述各层之间关系的描述,如下所示:

  1. 在RPC中,Protocol是核心层,也就是只要有Protocol + Invoker + Exporter就可以完成非透明的RPC调用,然后在Invoker的主过程上Filter拦截点。
  2. 图中的Consumer和Provider是抽象概念,只是想让看图者更直观的了解哪些分类属于客户端与服务器端,不用Client和Server的原因是Dubbo在很多场景下都使用Provider、Consumer、Registry
    、Monitor划分逻辑拓普节点,保持概念统一。
  3. 而Cluster是外围概念,所以Cluster的目的是将多个Invoker伪装成一个Invoker,这样其它人只要关注Protocol层Invoker即可,加上Cluster或者去掉Cluster
    对其它层都不会造成影响,因为只有一个提供者时,是不需要Cluster的。
  4. Proxy层封装了所有接口的透明化代理,而在其它层都以Invoker为中心,只有到了暴露给用户使用时,才用Proxy将Invoker转成接口,或将接口实现转成Invoker,也就是去掉Proxy层RPC是可以Run
    的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。
  5. 而Remoting实现是Dubbo协议的实现,如果你选择RMI协议,整个Remoting都不会用上,Remoting内部再划为Transport传输层和Exchange信息交换层,Transport层只负责单向消息传输,是对Mina
    、Netty、Grizzly的抽象,它也可以扩展UDP传输,而Exchange层是在传输层之上封装了Request-Response语义。
  6. Registry和Monitor实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。

Dubbo和Spring Cloud区别

  1. 最大的区别就是通信方式不同,Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输,配合hession序列化进行RPC调用
  2. SpringCloud是基于Http协议+Rest接口调用远程过程的通信,相比之下,Http拥有更大的报文,rest比rpc更加灵活,不存在代码级别的强依赖

2、Dubbo自己的SPI实现

Dubbo内核包括四个:SPI(模仿JDK的SPI)、AOP(模仿Spring)、IOC(模仿Spring)、compiler(动态编译)

SPI的设计目标

  1. 面向对象的设计里,模块之间基于接口编程,模块之间不对实现类进行硬编码(硬编码:数据直接嵌入到程序)
  2. 一旦代码涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码
  3. 为了实现在模块中装配的时候,不在模块里写死代码,这就需要一种服务发现机制
  4. 为某个接口寻找服务实现的机制,有点类似IOC的思想,就是将装配的控制权转移到代码之外

SPI的具体约定

  1. 当服务的提供者(provide),提供了一个接口多种实现时,一般会在jar包的META_INF/services/目录下,创建该接口的同名文件,该文件里面的内容就是该服务接口的具体实现类的名称
  2. 当外部加载这个模块的时候,就能通过jar包的META_INF/services/目录的配置文件得到具体的实现类名,并加载实例化,完成模块的装配

dubbo为什么不直接使用JDK的SPI?

  1. JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
  2. dubbo的SPI增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其他扩展点,JDK的SPI是没有的

dubbo的SPI目的:获取一个实现类的对象

途径:ExtensionLoader.getExtension(String name)
实现路径:

  1. getExtensionLoader(Class< Type > type)就是为该接口new 一个ExtensionLoader,然后缓存起来。
  2. getAdaptiveExtension() 获取一个扩展类,如果@Adaptive注解在类上就是一个装饰类;如果注解在方法上就是一个动态代理类,例如Protocol$Adaptive对象。
  3. getExtension(String name) 获取一个指定对象。

ExtensionLoader

ExtensionLoader扩展点加载器,是扩展点的查找,校验,加载等核心逻辑的实现类,几乎所有特性都在这个类中实现

从ExtensionLoader.getExtensionLoader(Class< Type > type)讲起

-----------------------ExtensionLoader.getExtensionLoader(Class<T> type)
ExtensionLoader.getExtensionLoader(Container.class)
  -->this.type = type;
  -->objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
     -->ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
       -->this.type = type;
       -->objectFactory =null;

执行以上代码完成了2个属性的初始化,是每个一个ExtensionLoader都包含了的2个值type和objectFactory

  • Class< ? > type: 构造器 初始化要得到的接口名

  • ExtensionFactory objectFactory :

    1、构造器 初始化AdaptiveExtensionFactory[ SpiExtensionFactory, SpringExtensionFactory]

    2、new一个ExtensionLoader存储在ConcurrentMap< Class< ? >, ExtensionLoader< ? > > EXTENSION_LOADERS

关于objectFactory的一些细节

  1. objectFactory 就是ExtensionFactory ,也是通过ExtensionLoader.getExtensionLoader(ExtensionFactory.class)来实现,但objectFactory = null;
  2. objectFactory 的作用就是为dubbo的IOC提供所有对象

3、SPI 与 API的区别

API (Application Programming Interface)

  • 大多数情况下,都是实现方来制定接口并完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现。

SPI (Service Provider Interface)

  • 而如果是调用方来制定接口,实现方来针对接口来实现不同的实现。调用方来选择自己需要的实现方。

当我们选择在调用方和实现方中间引入接口。我们有三种选择:

  1. 接口位于实现方所在的包中
  2. 接口位于调用方所在的包中
  3. 接口位于独立的包中

1. 接口位于【调用方】所在的包中

对于类似这种情况下接口,我们将其称为 SPI, SPI的规则如下:

  • 概念上更依赖调用方。
  • 组织上位于调用方所在的包中。
  • 实现位于独立的包中。

常见的例子是:插件模式的插件。如:

  • 数据库驱动 Driver
  • 日志 Log
  • dubbo扩展点开发

2. 接口位于【实现方】所在的包中

对于类似这种情况下的接口,我们将其称作为API,API的规则如下:

  • 概念上更接近实现方。
  • 组织上位于实现方所在的包中。

3. 接口位于独立的包中

如果一个“接口”在一个上下文是API,在另一个上下文是SPI,那么你就可以这么组织

4、SPI机制的adaptive原理

先来看一下adaptive的源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
   ElementType.TYPE, ElementType.METHOD})  //只能注解在类、接口、方法上面
public @interface Adaptive {
   

    String[] value() default {
   };

}

@adaptive注解在类和方法上的区别

  1. 注解在类上:代表人工实现编码,即实现了一个装饰类(设计模式中的装饰模式),例如:ExtensionFactory
  2. 注解在方法上:代表自动生成和编译一个动态的adpative类,例如:Protocol$adpative

再来看生成Protocol的源码

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class)
            .getAdaptiveExtension();

进入getAdaptiveExtension(),进行代码跟踪与解析

-----------------------getAdaptiveExtension()
 -->getAdaptiveExtension()   //目的为  cachedAdaptiveInstance赋值
   -->createAdaptiveExtension()
     -->getAdaptiveExtension()
       -->getExtensionClasses()   //目的为cachedClasses赋值
         -->loadExtensionClasses()  //加载
           -->loadFile()  //加载配置信息(主要是META_INF/services/下)
      -->createAdaptiveExtensionClass()  //下面的调用有两个分支
         // *********** 分支1 *******************  在找到的类上有Adaptive注解
        ->getExtensionClasses()
              ->loadExtensionClasses()
                ->loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
          // *********** 分支2 ******************* 在找到的类上没有Adaptive注解
        -->createAdaptiveExtensionClassCode()//通过adaptive模板生成代码
          -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();//编译
          -->compile(String code, ClassLoader classLoader) //自动生成和编译一个动态的adpative类,这个类是个代理类
       -->injectExtension()//作用:进入IOC的反转控制模式,实现了动态入注,这是 ExtensionFactory 类的作用之所在 

上述的调用中使用loadFile()加载配置信息,下面关于loadFile() 的一些细节:

loadFile()的目的

把配置文件META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol的内容存储在缓存变量里面,下面四种缓存变量:

  1. cachedAdaptiveClass //如果这个类有Adaptive注解就赋值,而Protocol在这个环节是没有的
  2. cachedWrapperClasses //只有当该class无Adaptive注解,并且构造函数包含目标接口(type),例如protocol里面的spi就只有ProtocolFilterWrapper、ProtocolListenerWrapper能命中
  3. cachedActivates //剩下的类包含adaptive注解
  4. cachedNames //剩下的类就存储在这里

getExtension() 方法执行逻辑:

-----------------------getExtension(String name)
getExtension(String name) //指定对象缓存在cachedInstances;get出来的对象wrapper对象,例如protocol就是ProtocolFilterWrapper和ProtocolListenerWrapper其中一个。
  -->createExtension(String name)
    -->getExtensionClasses()
    -->injectExtension(T instance)//dubbo的IOC反转控制,就是从spi和spring里面提取对象赋值。
      -->objectFactory.getExtension(pt, property)
        -->SpiExtensionFactory.getExtension(type, name)
          -->ExtensionLoader.getExtensionLoader(type)
          -->loader.getAdaptiveExtension()
        -->SpringExtensionFactory.getExtension(type, name)
          -->context.getBean(name)
    -->injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))//AOP的简单设计
    

可以根据方法的调用得出dubbo的spi流程

adaptive动态编译使用的模板

package <扩展点接口所在包>;
 
public class <扩展点接口名>$Adpative implements <扩展点接口> {
   
    public <@Adaptive注解的接口方法>(<方法参数>) {
   
        if(是否有URL类型方法参数?) 使用该URL参数
        else if(是否有方法类型上有URL属性) 使用该URL属性
        # <else 在加载扩展点生成自适应扩展点类时抛异常,即加载扩展点失败!>
         
        if(获取的URL == null) {
   
            throw new IllegalArgumentException("url == null");
        }
 
              根据@Adaptive注解上声明的Key的顺序,从URL获致Value,作为实际扩展点名。
               如URL没有Value,则使用缺省扩展点实现。如没有扩展点, throw new IllegalStateException("Fail to get extension");
 
               在扩展点实现调用该方法,并返回结果。
    }
 
    public <@Adaptive注解的接口方法>(<方法参数>) {
   
        throw new UnsupportedOperationException("is not adaptive method!");
    }
}

例如com.alibaba.dubbo.rpc.Protocol接口的动态编译根据模板生成的扩展类Protocol$Adpative为:

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements Protocol {
   

    public void destroy() {
   
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
   
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Exporter export(Invoker invoker) throws RpcException {
   
        if (invoker == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (invoker.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = invoker.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
        return extension.export(invoker);
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws RpcException {
   
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

总结

1、获取某个SPI接口的adaptive实现类的规则是:

  • 实现类的类上面有Adaptive注解的,那么这个类就是adaptive类
  • 实现类的类上面没有Adaptive注解,但是在方法上有Adaptive注解,则会动态生成adaptive类

2、生成的动态类的编译类是:com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler类

3、动态类的本质是可以做到一个SPI中的不同的Adaptive方法可以去调不同的SPI实现类去处理。使得程序的灵活性大大提高,这才是整套SPI设计的一个精华之所在。

5、Dubbo的动态编译

Compile接口定义:

@SPI("javassist")
public interface Compiler {
   

    Class<?> compile(String code, ClassLoader classLoader);

}
  • @SPI(“javassist”):表示如果没有配置,dubbo默认选用javassist编译源代码,Javassist是一款字节码编辑工具,同时也是一个动态类库,它可以直接检查、修改以及创建 Java类。
  • 接口方法compile第一个入参code,就是java的源代码
  • 接口方法compile第二个入参classLoader,按理是类加载器用来加载编译后的字节码,其实没用到,都是根据当前线程或者调用方的classLoader加载的

AdaptiveCompiler是Compiler的设配类,它的作用是Compiler策略的选择,根据条件选择使用何种编译策略来编译动态生成SPI扩展 ,默认为javassist.

AbstractCompiler是一个抽象类,它通过正则表达式获取到对象的包名以及Class名称。这样就可以获取对象的全类名(包名+Class名称)。通过反射Class.forName()来判断当前ClassLoader是否有这个类,如果有就返回,如果没有就通过JdkCompiler或者JavassistCompiler通过传入的code编译这个类。

      com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
  • getExtensionLoader():new一个ExtensionLoader对象,用到单例模式、工厂模式,然后换成起来。
  • getAdaptiveExtension():为了获取扩展装饰类或代理类的对像,不过有个规则:如果@Adaptive注解在类上就是一个装饰类;如果注解在方法上就是一个动态代理类。
public abstract class AbstractCompiler implements Compiler {
   

    private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+([$_a-zA-Z][$_a-zA-Z0-9\\.]*);");

    private static final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s+");

    public Class<?> compile(String code, ClassLoader classLoader) {
   
        code = code.trim();
        Matcher matcher = PACKAGE_PATTERN.matcher(code);//包名  
        String pkg;
        if (matcher.find()) {
   
            pkg = matcher.group(1);
        } else {
   
            pkg = "";
        }
        matcher = CLASS_PATTERN.matcher(code);//类名  
        String cls;
        if (matcher.find()) {
   
            cls = matcher.group(1);
        } else {
   
            throw new IllegalArgumentException("No such class name in " + code);
        }
        String className = pkg != null && pkg.length() > 0 ? pkg + "." + cls : cls;
        try {
   
            return Class.forName(className, true, ClassHelper.getCallerClassLoader(getClass()));//根据类全路径来查找类  
        } catch (ClassNotFoundException e) {
   
            if (!code.endsWith("}")) {
   
                throw new IllegalStateException("The java code not endsWith \"}\", code: \n" + code + "\n");
            }
            try {
   
                return doCompile(className, code);//调用实现类JavassistCompiler或JdkCompiler的doCompile方法来动态编译类  
            } catch (RuntimeException t) {
   
                throw t;
            } catch (Throwable t) {
   
                throw new IllegalStateException("Failed to compile class, cause: " + t.getMessage() + ", class: " + className + ", code: \n" + code + "\n, stack: " + ClassUtils.toString(t));
            }
        }
    }

    protected abstract Class<?> doCompile(String name, String source) throws Throwable;

}

AbstractCompiler:在公用逻辑中,利用正则表达式匹配出源代码中的包名和类名:

  • PACKAGE_PATTERN = Pattern.compile(“package\s+([ a − z A − Z ] [ _a-zA-Z][ azAZ][_a-zA-Z0-9\.]*);”);

  • CLASS_PATTERN = Pattern.compile(“class\s+([ a − z A − Z ] [ _a-zA-Z][ azAZ][_a-zA-Z0-9]*)\s+”);

然后在JVM中查找看看是否存在:Class.forName(className, true, ClassHelper.getCallerClassLoader(getClass()));存在返回,不存在就使用JavassistCompiler或者是JdkCompiler来执行编译。

6、dubbo和spring完美融合

dubbo采取通过配置文件来启动container容器,dubbo是使用spring来做容器

dubbo实现通过下面的配置schema自定义配置

完成一个spring的自定义配置一般需要以下5个步骤:

  1. 设计配置属性和JavaBean
  2. 编写XSD文件 全称就是 XML Schema 它就是校验XML,定义了一些列的语法来规范XML
  3. 编写NamespaceHandler和BeanDefinitionParser完成解析工作
  4. 编写两个类spring.handlers和spring.schemas串联起所有部件
  5. 在Bean文件中应用

1. 设计配置属性和JavaBean

先配置属性dubbo.xml,可以看出一个service对象,有属性包括,interface、ref等

<!-- 和本地bean一样实现服务 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>

2. 编写XSD文件 全称就是 XML Schema 它就是校验XML,定义了一些列的语法来规范XML

下面是dubbo.xsd文件,作用是约束interface和ref属性,比如ref只能是string类,输入其他类型就提示报错

 <xsd:element name="service" type="serviceType">
        <xsd:annotation>
            <xsd:documentation><![CDATA[ Export service config ]]></xsd:documentation>
        </xsd:annotation>
    </xsd:element>

  <xsd:complexType name="serviceType">
        <xsd:complexContent>
            <xsd:extension base="abstractServiceType">
                <xsd:choice minOccurs="0" maxOccurs="unbounded">
                    <xsd:element ref="method" minOccurs="0" maxOccurs="unbounded"/>
                    <xsd:element ref="parameter" minOccurs="0" maxOccurs="unbounded"/>
                    <xsd:element ref="beans:property" minOccurs="0" maxOccurs="unbounded"/>
                </xsd:choice>
                <xsd:attribute name="interface" type="xsd:token" use="required">
                    <xsd:annotation>
                        <xsd:documentation>
                            <![CDATA[ Defines the interface to advertise for this service in the service registry. ]]></xsd:documentation>
                        <xsd:appinfo>
                            <tool:annotation>
                                <tool:expected-type type="java.lang.Class"/>
                            </tool:annotation>
                        </xsd:appinfo>
                    </xsd:annotation>
                </xsd:attribute>
                <xsd:attribute name="ref" type="xsd:string" use
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值