Dubbo源码之SPI

Dubbo 源码分析

1. Dubbo的扩展机制

在Dubbo的官网上,Dubbo描述本身是一个高性能的RPC框架。今天我想聊聊Dubbo的另外一个很棒的特性, 就是它的可扩展性。 如同罗马不是一天建成的,任何系统都必定是从小系统不断发展成为大系统的,想要从一开始就把系统设计的足够完善是不可能的,相反的,咱们应该关注当下的需求,而后再不断地对系统进行迭代。在代码层面,要求咱们适当的对关注点进行抽象和隔离,在软件不断添加功能和特性时,依然能保持良好的结构和可维护性,同时容许第三方开发者对其功能进行扩展。在某些时候,软件设计者对扩展性的追求甚至超过了性能。java

在谈到软件设计时,可扩展性一直被谈起,那到底什么才是可扩展性,什么样的框架才算有良好的可扩展性呢?它必需要作到如下两点:mysql

  • 做为框架的维护者,在添加一个新功能时,只须要添加一些新代码,而不用大量的修改现有的代码,即符合开闭原则。
  • 做为框架的使用者,在添加一个新功能时,不须要去修改框架的源码,在本身的工程中添加代码便可。 Dubbo很好的作到了上面两点。这要得益于Dubbo的微内核+插件的机制。接下来的章节中咱们会慢慢揭开Dubbo扩展机制的神秘面纱。

2.可扩展的几种解决方案

一般可扩展的实现有下面几种:sql

  • Factory模式
  • IoC容器
  • OSGI容器 Dubbo做为一个框架,不但愿强依赖其余的IoC容器,好比Spring,Guice。OSGI也是一个很重的实现,不适合Dubbo。最终Dubbo的实现参考了Java原生的SPI机制,但对其进行了一些扩展,以知足Dubbo的需求。

3.Java SPI机制

既然Dubbo的扩展机制是基于Java原生的SPI机制,那么咱们就先来了解下Java SPI吧。了解了Java的SPI,也就是对Dubbo的扩展机制有一个基本的了解。若是对Java SPI比较了解的同窗,能够跳过。设计模式

Java SPI(Service Provider Interface)是JDK内置的一种动态加载扩展点的实现。在ClassPath的META-INF/services目录下放置一个与接口同名的文本文件,文件的内容为接口的实现类,多个实现类用换行符分隔。JDK中使用java.util.ServiceLoader来加载具体的实现。 让咱们经过一个简单的例子,来看看Java SPI是如何工做的。bash

  • 1.定义一个接口IRepository用于实现数据储存负载均衡

    public interface IRepository { void save(String data); }框架

  • 2.提供IRepository的实现 IRepository有两个实现。MysqlRepository和MongoRepository。dom

    public class MysqlRepository implements IRepository { public void save(String data) { System.out.println("Save " + data + " to Mysql"); } }ide

    public class MongoRepository implements IRepository { public void save(String data) { System.out.println("Save " + data + " to Mongo"); } }工具

  • 3.添加配置文件 在META-INF/services目录添加一个文件,文件名和接口全名称相同,因此文件是META-INF/services/com.demo.IRepository。文件内容为:

    com.demo.MongoRepository com.demo.MysqlRepository

  • 4.经过ServiceLoader加载IRepository实现

    ServiceLoader<IRepository> serviceLoader = ServiceLoader.load(IRepository.class);
      Iterator<IRepository> it = serviceLoader.iterator();
      while (it != null && it.hasNext()){
          IRepository demoService = it.next();
          System.out.println("class:" + demoService.getClass().getName());
          demoService.save("tom");
      }
    复制代码

在上面的例子中,咱们定义了一个扩展点和它的两个实现。在ClassPath中添加了扩展的配置文件,最后使用ServiceLoader来加载全部的扩展点。 最终的输出结果为:

class:testDubbo.MongoRepository Save tom to Mongo 
class:testDubbo.MysqlRepository Save tom to Mysql
复制代码

4.Dubbo的SPI机制

Java SPI的使用很简单。也作到了基本的加载扩展点的功能。但Java SPI有如下的不足:

  • 须要遍历全部的实现,并实例化,而后咱们在循环中才能找到咱们须要的实现。
  • 配置文件中只是简单的列出了全部的扩展实现,而没有给他们命名。致使在程序中很难去准确的引用它们。
  • 扩展若是依赖其余的扩展,作不到自动注入和装配
  • 不提供相似于Spring的IOC和AOP功能
  • 扩展很难和其余的框架集成,好比扩展里面依赖了一个Spring bean,原生的Java SPI不支持

因此Java SPI应付一些简单的场景是能够的,但对于Dubbo,它的功能仍是比较弱的。Dubbo对原生SPI机制进行了一些扩展。接下来,咱们就更深刻地了解下Dubbo的SPI机制。

5.Dubbo扩展点机制基本概念

在深刻学习Dubbo的扩展机制以前,咱们先明确Dubbo SPI中的一些基本概念。在接下来的内容中,咱们会屡次用到这些术语。

  • 5.1 扩展点(Extension Point)

    是一个Java的接口。

  • 5.2 扩展(Extension)

    扩展点的实现类。

  • 5.3 扩展实例(Extension Instance)

    扩展点实现类的实例。

  • 5.4 扩展自适应实例(Extension Adaptive Instance)

    第一次接触这个概念时,可能不太好理解(我第一次也是这样的...)。若是称它为扩展代理类,可能更好理解些。扩展的自适应实例其实就是一个Extension的代理,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪一个扩展。好比一个IRepository的扩展点,有一个save方法。有两个实现MysqlRepository和MongoRepository。IRepository的自适应实例在调用接口方法的时候,会根据save方法中的参数,来决定要调用哪一个IRepository的实现。若是方法参数中有repository=mysql,那么就调用MysqlRepository的save方法。若是repository=mongo,就调用MongoRepository的save方法。和面向对象的延迟绑定很相似。为何Dubbo会引入扩展自适应实例的概念呢?

    • Dubbo中的配置有两种,一种是固定的系统级别的配置,在Dubbo启动以后就不会再改了。还有一种是运行时的配置,可能对于每一次的RPC,这些配置都不一样。好比在xml文件中配置了超时时间是10秒钟,这个配置在Dubbo启动以后,就不会改变了。但针对某一次的RPC调用,能够设置它的超时时间是30秒钟,以覆盖系统级别的配置。对于Dubbo而言,每一次的RPC调用的参数都是未知的。只有在运行时,根据这些参数才能作出正确的决定。
    • 不少时候,咱们的类都是一个单例的,好比Spring的bean,在Spring bean都实例化时,若是它依赖某个扩展点,可是在bean实例化时,是不知道究竟该使用哪一个具体的扩展实现的。这时候就须要一个代理模式了,它实现了扩展点接口,方法内部能够根据运行时参数,动态的选择合适的扩展实现。而这个代理就是自适应实例。 自适应扩展实例在Dubbo中的使用很是普遍,Dubbo中,每个扩展都会有一个自适应类,若是咱们没有提供,Dubbo会使用字节码工具为咱们自动生成一个。因此咱们基本感受不到自适应类的存在。后面会有例子说明自适应类是怎么工做的。
  • 5.5 @SPI

    @SPI注解做用于扩展点的接口上,代表该接口是一个扩展点。能够被Dubbo的ExtentionLoader加载。若是没有此ExtensionLoader调用会异常。

  • 5.6 @Adaptive

    @Adaptive注解用在扩展接口的方法上。表示该方法是一个自适应方法。Dubbo在为扩展点生成自适应实例时,若是方法有@Adaptive注解,会为该方法生成对应的代码。方法内部会根据方法的参数,来决定使用哪一个扩展。 @Adaptive注解用在类上表明实现一个装饰类,相似于设计模式中的装饰模式,它主要做用是返回指定类,目前在整个系统中AdaptiveCompiler、AdaptiveExtensionFactory这两个类拥有该注解。

  • 5.7 ExtentionLoader

    相似于Java SPI的ServiceLoader,负责扩展的加载和生命周期维护。

  • 5.8 扩展别名

    和Java SPI不一样,Dubbo中的扩展都有一个别名,用于在应用中引用它们。好比

    random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
    roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
    复制代码
  • 5.9 一些路径

    和Java SPI从/META-INF/services目录加载扩展配置相似,Dubbo也会从如下路径去加载扩展配置文件:

    • META-INF/dubbo/internal
    • META-INF/dubbo
    • META-INF/services

6.Dubbo的LoadBalance扩展点解读

在了解了Dubbo的一些基本概念后,让咱们一块儿来看一个Dubbo中实际的扩展点,对这些概念有一个更直观的认识。

咱们选择的是Dubbo中的LoadBalance扩展点。Dubbo中的一个服务,一般有多个Provider,consumer调用服务时,须要在多个Provider中选择一个。这就是一个LoadBalance。咱们一块儿来看看在Dubbo中,LoadBalance是如何成为一个扩展点的。

  • 6.1 LoadBalance接口

    @SPI(RandomLoadBalance.NAME)
      public interface LoadBalance {
      
          @Adaptive("loadbalance")
          <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
      }
    复制代码

    LoadBalance接口只有一个select方法。select方法从多个invoker中选择其中一个。上面代码中和Dubbo SPI相关的元素有:

    • @SPI(RandomLoadBalance.NAME) @SPI做用于LoadBalance接口,表示接口LoadBalance是一个扩展点。若是没有@SPI注解,试图去加载扩展时,会抛出异常。@SPI注解有一个参数,该参数表示该扩展点的默认实现的别名。若是没有显示的指定扩展,就使用默认实现。RandomLoadBalance.NAME是一个常量,值是"random",是一个随机负载均衡的实现。 random的定义在配置文件META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.LoadBalance中:

      random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
        roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
        leastactive=com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
        consistenthash=com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance
      复制代码

    能够看到文件中定义了4个LoadBalance的扩展实现。因为负载均衡的实现不是本次的内容,这里就不过多说明。只用知道Dubbo提供了4种负载均衡的实现,咱们能够经过xml文件,properties文件,JVM参数显式的指定一个实现。若是没有,默认使用随机。

    • @Adaptive("loadbalance") @Adaptive注解修饰select方法,代表方法select方法是一个可自适应的方法。Dubbo会自动生成该方法对应的代码。当调用select方法时,会根据具体的方法参数来决定调用哪一个扩展实现的select方法。@Adaptive注解的参数loadbalance表示方法参数中的loadbalance的值做为实际要调用的扩展实例。 但奇怪的是,咱们发现select的方法中并无loadbalance参数,那怎么获取loadbalance的值呢?select方法中还有一个URL类型的参数,Dubbo就是从URL中获取loadbalance的值的。这里涉及到Dubbo的URL总线模式,简单说,URL中包含了RPC调用中的全部参数。URL类中有一个Map<String, String> parameters字段,parameters中就包含了loadbalance。
相关文章
相关标签/搜索