Table of Contents generated with DocToc
- 1、Dubbo的架构原理
- 2、Dubbo自己的SPI实现
- 3、SPI 与 API的区别
- 4、SPI机制的adaptive原理
- 5、Dubbo的动态编译
- 6、dubbo和spring完美融合
- 7、服务发布过程
- 8、服务调用过程
- 9、Dubbo把网络通信的IO异步变同步
- 10、Dubbo网络通信的编解码
- 11、Dubbo注册机制
- 12、Dubbo使用的设计模式
- 13、Dubbo优雅关机
- 14、扩展:如何自己设计一个类似dubbo的rpc框架?
1、Dubbo的架构原理
Dubbo架构图
节点角色说明
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
调用关系说明
- provider启动时,会把所有接口注册到注册中心,并且订阅动态配置configurators
- consumer启动时,向注册中心订阅自己所需的providers,configurators,routers
- 订阅内容变更时,registry将基于长连接推送变更数据给consumer,包括providers,configurators,routers
- consumer启动时,从provider地址列表中,基于软负载均衡算法,选一台provider进行调用,如果调用失败,再选另一台调用,建立长连接,然后进行数据通信(consumer->provider)
- 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
根据官方提供的,对于上述各层之间关系的描述,如下所示:
- 在RPC中,Protocol是核心层,也就是只要有Protocol + Invoker + Exporter就可以完成非透明的RPC调用,然后在Invoker的主过程上Filter拦截点。
- 图中的Consumer和Provider是抽象概念,只是想让看图者更直观的了解哪些分类属于客户端与服务器端,不用Client和Server的原因是Dubbo在很多场景下都使用Provider、Consumer、Registry
、Monitor划分逻辑拓普节点,保持概念统一。 - 而Cluster是外围概念,所以Cluster的目的是将多个Invoker伪装成一个Invoker,这样其它人只要关注Protocol层Invoker即可,加上Cluster或者去掉Cluster
对其它层都不会造成影响,因为只有一个提供者时,是不需要Cluster的。 - Proxy层封装了所有接口的透明化代理,而在其它层都以Invoker为中心,只有到了暴露给用户使用时,才用Proxy将Invoker转成接口,或将接口实现转成Invoker,也就是去掉Proxy层RPC是可以Run
的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。 - 而Remoting实现是Dubbo协议的实现,如果你选择RMI协议,整个Remoting都不会用上,Remoting内部再划为Transport传输层和Exchange信息交换层,Transport层只负责单向消息传输,是对Mina
、Netty、Grizzly的抽象,它也可以扩展UDP传输,而Exchange层是在传输层之上封装了Request-Response语义。 - Registry和Monitor实际上不算一层,而是一个独立的节点,只是为了全局概览,用层的方式画在一起。
Dubbo和Spring Cloud区别
- 最大的区别就是通信方式不同,Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输,配合hession序列化进行RPC调用
- SpringCloud是基于Http协议+Rest接口调用远程过程的通信,相比之下,Http拥有更大的报文,rest比rpc更加灵活,不存在代码级别的强依赖
2、Dubbo自己的SPI实现
Dubbo内核包括四个:SPI(模仿JDK的SPI)、AOP(模仿Spring)、IOC(模仿Spring)、compiler(动态编译)
SPI的设计目标
- 面向对象的设计里,模块之间基于接口编程,模块之间不对实现类进行硬编码(硬编码:数据直接嵌入到程序)
- 一旦代码涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码
- 为了实现在模块中装配的时候,不在模块里写死代码,这就需要一种服务发现机制
- 为某个接口寻找服务实现的机制,有点类似IOC的思想,就是将装配的控制权转移到代码之外
SPI的具体约定
- 当服务的提供者(provide),提供了一个接口多种实现时,一般会在jar包的META_INF/services/目录下,创建该接口的同名文件,该文件里面的内容就是该服务接口的具体实现类的名称
- 当外部加载这个模块的时候,就能通过jar包的META_INF/services/目录的配置文件得到具体的实现类名,并加载实例化,完成模块的装配
dubbo为什么不直接使用JDK的SPI?
- JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
- dubbo的SPI增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其他扩展点,JDK的SPI是没有的
dubbo的SPI目的:获取一个实现类的对象
途径:ExtensionLoader.getExtension(String name)
实现路径:
- getExtensionLoader(Class< Type > type)就是为该接口new 一个ExtensionLoader,然后缓存起来。
- getAdaptiveExtension() 获取一个扩展类,如果@Adaptive注解在类上就是一个装饰类;如果注解在方法上就是一个动态代理类,例如Protocol$Adaptive对象。
- 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的一些细节
- objectFactory 就是ExtensionFactory ,也是通过ExtensionLoader.getExtensionLoader(ExtensionFactory.class)来实现,但objectFactory = null;
- objectFactory 的作用就是为dubbo的IOC提供所有对象
3、SPI 与 API的区别
API (Application Programming Interface)
- 大多数情况下,都是实现方来制定接口并完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现。
SPI (Service Provider Interface)
- 而如果是调用方来制定接口,实现方来针对接口来实现不同的实现。调用方来选择自己需要的实现方。
当我们选择在调用方和实现方中间引入接口。我们有三种选择:
- 接口位于实现方所在的包中
- 接口位于调用方所在的包中
- 接口位于独立的包中
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注解在类和方法上的区别
- 注解在类上:代表人工实现编码,即实现了一个装饰类(设计模式中的装饰模式),例如:ExtensionFactory
- 注解在方法上:代表自动生成和编译一个动态的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的内容存储在缓存变量里面,下面四种缓存变量:
- cachedAdaptiveClass //如果这个类有Adaptive注解就赋值,而Protocol在这个环节是没有的
- cachedWrapperClasses //只有当该class无Adaptive注解,并且构造函数包含目标接口(type),例如protocol里面的spi就只有ProtocolFilterWrapper、ProtocolListenerWrapper能命中
- cachedActivates //剩下的类包含adaptive注解
- 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][ a−zA−Z][_a-zA-Z0-9\.]*);”);
-
CLASS_PATTERN = Pattern.compile(“class\s+([ a − z A − Z ] [ _a-zA-Z][ a−zA−Z][_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个步骤:
- 设计配置属性和JavaBean
- 编写XSD文件 全称就是 XML Schema 它就是校验XML,定义了一些列的语法来规范XML
- 编写NamespaceHandler和BeanDefinitionParser完成解析工作
- 编写两个类spring.handlers和spring.schemas串联起所有部件
- 在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