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的设计思想的确很优秀,只是代码质量不堪入目,实在太差了。