Dubbo

详解Dubbo设计结构和实现原理。

前言

之前已经有许多介绍Dubbo的文章,但大部分内容比较粗糙或者内容是直接从Dubbo官网复制的,也有少部分内容详实的专题系列,却有不免于流水账式分析Dubbo的源代码。于是计划写一篇关于Dubbo设计结构和实现原理的总结,寄希望帮助读者阅读Dubbo源码时可以清晰明白各个模块如此设计的原理,进而可以轻松阅读Dubbo源码。

设计结构

Dubbo是一个分布式的支持服务治理功能的RPC框架,整个系统必然无法逃脱标准的RPC设计结构,如下图。

上图即是Dubbo的基础结构,所有模块都是以此为核心扩展出来,完整的Dubbo结构如下图:

系统结构

上图左侧为Dubbo的组成模块,右侧淡蓝色部分是使用的技术(思想)。

组成模块

Remote Communication(通信层):负责数据传输,Client发送Request,Server返回Response。

Protocol(编解码协议):负责数据编码、解码,协议层接收到数据后,将数据编码后交给通信层,解码后交给Invoker层。

Invoker(逻辑层):RPC的主要逻辑都在此实现,所有服务治理的接口都抽象为Invoker,组成一个Invoker链,每个完整的请求都会层层经过此链,链中每个invoker完成各自的责任。

Proxy(代理层 ):负责将Client的调用转为Invoker链的调用。

Interface Implement(实现层):即接口的实现,也叫做业务逻辑层,是Client最终期望引用的远程逻辑。

Spring Config(配置层):负责将Spring配置实例化为对象,使开发人员只需进行简单的spring配置就完成服务暴露、引用代理实例化,具体实现逻辑透明化。

底层技术

SPI(Service Provider Interface):字面意思是服务提供方接口,核心系统规定服务接口,所有实现遵循服务接口规定,当系统需要升级时不需要改动核心代码而是切换服务接口的实现。Dubbo就是基于此思想实现微内核+插件的设计结构,所有模块均可以自行实现和替换。

Proxy:我认为代理模式是RPC的核心模式,通过Client端的代理来调用Server端的实现。

Class Loader:类加载器。Dubbo重新实现SPI,不同模块在程序运行时按需加载到容器中,为了支持此行为,Class Loader自然而然被引入。

NIO:Dubbo数据传输层默认使用NIO框架Netty,NIO降低了服务端的线程数量,进而提高资源利用率。

Spring Schema Ext:Spring Schema扩展实现比较简单,按特定规则完成配置项的定义和解析即可,却实现了通过简单配置替代编码的目的。

SPI

如上图,基于SPI设计结构的系统一般有两部分:核心程序(主程序)与接口实现程序(插件)。

主程序负责定义SPI(即Interface)和按特定规则加载插件;插件负责实现主程序定义的SPI并按特定规则定义配置文件。

我们几乎每天都在使用标准Java SPI是JDBC定义的java.sql.Driver,用的时候只需要把目标数据库依赖的jar放入Classpath,JDBC主程序会自动找到各DB Driver,准备后续使用。

SPI例子

主程序

 定义SPI

 在主程序内部加载并调用SPI

插件

 为SPI提供实现逻辑

 在固定位置声明自身为SPI的提供者

 

下面介绍一个加密程序的例子:为了达到信息安全防止被破解的目的,设计的一套加密程序并计划不定时更换加密算法,为了在不改动主程序的情况下灵活切换、新增加密算法,于是使用SPI设计结构,具体实现如下。

主程序

/**
 * 此接口为标准Java SPI,
   所有加密算法实现实现应当遵循此SPI。
 */
public interface Encryption {

    /**
     * 对data进行加密,然后返回加密数据。
     * 
     * @param data
     *            字节数组
     * @return 加密后的数据
     */
    byte[] encrypt(byte[] data);
}
/**
 * 加密器主程序
 * 负责加载、调用加密算法的Provider。
 */
public class EncryptorApplication {

    public static void main(String[] args) {

        //准备加密数组
        byte[] data = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        //加载所有加密算法
        ServiceLoader<Encryption> encryptions = ServiceLoader.load(Encryption.class);
        Iterator<Encryption> encryptionIter = encryptions.iterator();

        if (!encryptionIter.hasNext()) {
            System.out.println("there is no provider for encryption algorithm.");
        }
        //进行组合加密
        while (encryptionIter.hasNext()) {
            Encryption encryption = encryptionIter.next();
            data = encryption.encrypt(data);
        }
        //输出加密后数据
        for (int i = 0; i < data.length; i++) {
            System.out.print(data[i] + ",");
        }
    }
}

插件(加密算法)

/**
 * 遍历所有字节,对所有字节+1。
 */
public class IncrementAlgorithm implements Encryption {

    /**
     * 对数组中所有字节+1,返回修改后的字节数组。
     */
    @Override
    public byte[] encrypt(byte[] data) {
        if (null == data || data.length == 0) {
            return data;
        }
        for (int i = 0; i < data.length; i++) {
            data[i] = (byte) (data[i] + 1);
        }

        return data;
    }
}
配置文件META-INF/services/tutorial.jdk.spi.Encryption
    tutorial.jdk.spi.ReverseAlgorithm
    tutorial.jdk.spi.IncrementAlgorithm

为了介绍方便,这个例子将主程序与插件放在同一个project里,线上产品是将各加密算法拆分为单独的project,主程序希望调用哪个算法或者进行组合调用,就将对应的.jar放入Classpath,部署起来要方便的多。

Proxy

代理模式是每一个RPC框架的核心设计模式。

Dubbo消费者的配置如下:

<dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService" />

此配置信息在消费者端生成接口DemoService的代理DemoServiceProxy,DemoServiceProxy完成了网络数据通信的具体过程,且Proxy的内部逻辑对开发人员完全透明,使用起来就像调用本地的接口一样方便。

代理模式也是每天都会用到,最多的应该是Spring的事务。Proxy的实现技术有多个:最强大的是Aspectj,还有Cglib和基于接口的JDK Proxy。

Proxy例子

调用方

public class Client {

    public static void main(String[] args) {
        //通过代理工厂获取proxy
        HelloWorld proxy = HelloWorldProxyFactory.getProxy();
        //调用proxy代理的方法
        proxy.sayHelloWorld();
    }
}

Proxy的回调方法

/**
 * 该类对应HelloWorld Proxy的具体调用逻辑。
 */
public class HelloWorldHandler implements InvocationHandler {

    //被代理的原始对象
    private Object obj;

    public HelloWorldHandler(Object obj) {
        super();
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        //调用sayHelloWorld方法前的动作
        System.out.println("Firstly, open mouth!");
        //调用sayHelloWorld方法
        result = method.invoke(obj, args);
        //调用sayHelloWorld方法后续动作
        System.out.println("At last, close mouth!");

        return result;
    }
}

代理工厂

/**
 * 代理工厂类
 * 代理工厂可参考:org.springframework.aop.framework.ProxyFactory
 */
public class HelloWorldProxyFactory {

    /**
     * 获得HelloWorld的代理对象。
     */
    public static HelloWorld getProxy() {
        //接口的实现类
        HelloWorld helloWorld = new HelloWorldImpl();

        //proxy的调用类
        InvocationHandler handler = new HelloWorldHandler(helloWorld);

        //生成proxy
        HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
                helloWorld.getClass().getClassLoader(),
                helloWorld.getClass().getInterfaces(),
                handler);

        return proxy;
    }
}

基于Proxy如何实现RPC框架?请参考《RPC》

顺便说一句,Dubbo的设计思想的确很优秀,只是代码质量不堪入目,实在太差了。