dubbo SPI机制在项目中的应用

dubbo SPI机制在项目中的应用

1.原理

SPI(Service Providor Interface),服务提供接口,在项目中碰到这个概念之前,还是第一次接触,我的理解是,一句话将这玩意解释清楚,就是利用的Java中多态以及面向接口的特性,将一个接口的实现类的全限定名存在一个配置文件中,当需要调用这个接口的时候,加载这个配置文件,获取这个接口的实现类,这样做,可以更加灵活的完成对接口的实现,避免了程序中硬编码带来的问题

一个典型的应用就是在JDBC中,我们都知道,jdbc是Java提供给各大数据库厂商的一个通用接口,数据库厂商分别实现这个接口,实现自己的数据库驱动,那么这里就使用到了SPI这种思想,完成动态配置,可以更加方便的实现程序功能的扩展

在实现简易RPC框架的过程中,也是使用到了SPI机制,具体使用在了以下的几个地方:

  • 序列化方式:目前支持的序列化方式是Kryo,通过这种灵活的配置方式,后面可以陆续加入ProtoBuff或者Avro的序列化方式进行序列化比较,因为既然市面上的序列化方式既然这么多元化,没有一家独大的现象,那么说明就没有一个序列化框架能够满足所有的应用场景,所以后面我们要根据业务场景来灵活的选择序列化方式

  • 传输方式

    目前支持两种方式,分别是BIO和Netty两种方式,一开始实现的时候,没有引入Netty这个框架,只是使用了Java原生的API实现了BIO,但是BIO可能存在一些问题,例如每个连接都对应一个线程来处理,这样无法解决C10K问题,要知道线程可是一个非常宝贵的资源,现在开源项目中的共识还是直接使用Netty,考虑到NIO的epooll bug问题,没有理由选用其他的,当然了,虽然说Netty不仅性能优秀,而且api非常易用,但是我希望自己还是不要满足于使用层面上

  • 服务注册和发现的方式

    这个情况也应该考虑到,虽然说目前使用的是ZooKeeper作为注册中心,但是也应该支持其他方式的实现

具体使用的是dubbo中的SPI机制,这和Java原生的SPI机制有些不同,总之就是有过之而无不及,说到这里,又想感慨几句,Java团队的一些东西,例如原生序列化方式,BIO、NIO,以及目前所讲的SPI,虽然是当初这些领域的先行者,但是现在已经被其他框架的相同实现远远超越,甚至看起来还有一些鸡肋,,算了不说了😂

2.Java SPI示例

例子参考自dubbo官方文档:https://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

定义接口:

public interface Robot {
    void sayHello();
}

定义两个实现类:

public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:

org.apache.spi.OptimusPrime
org.apache.spi.Bumblebee

测试:

public class JavaSPITest {

    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(Robot::sayHello);
    }
}

img

演示完毕~

3.dubbo SPI

上面的原生SPI存在一个问题是,如果一个配置文件中配置了很多的实现类,那么我只想使用其中的一个或者若干个怎么办?原生SPI没法做到,只能全部加载,但是dubbo提供了可以根据配置名进行选择的功能,配置文件中的形式是键值对形式的:

zk=github.javaguide.registry.zk.ZkServiceRegistry

下面结合项目中的场景,来看一下是怎么运用的:

首先的万恶之源就是这里:

serviceRegistry = ExtensionLoader.getExtensionLoader(ServiceRegistry.class).getExtension("zk");

上面这一行代码想要获取一个服务注册实例,来看一下是怎么实现的:

ExtensionLoader这个类是dubbo实现SPI的核心类,这里面拿来复用,没有引入dubbo,而是只是取出其中的SPI 源码,上面获取一个ExtensionLoader实例:

image-20200908201058963

然后就获取拓展实例:

image-20200908201303935

image-20200908201530647

怎么加载配置文件的呢?往下追:

image-20200908201700653

看来里面还是调用了loadDirectory(classes);方法,看方法名已经很接近了:

image-20200908201927829

来看加载资源这个方法:

image-20200908202354250

至此,我们已经追完了自始至终的所有过程,虽然图片很多,但是整体看来还是比较清晰的,因为一般源码都是网状结构的,很容易把人搞晕,但是这个源码都是线性结构的,而且我也是根据自己的理解增加了比较多的注释

4.这是结尾

其实dubbo中具体的SPI机制比上面还要复杂的多,而我的项目中引入SPI也只是刚刚满足了实际的场景需求,dubbo在实现上面还有自适应拓展等功能,这里并没有详细列出,以后阅读源码的时候,一定会遇到,后面再出一篇文章专门来追一追,See you next blog!