框架设计
整体设计
图例说明:
- 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
- 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
- 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
- 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。
各层说明
- config 配置层:对外配置接口,以
ServiceConfig
,ReferenceConfig
为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类 - proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以
ServiceProxy
为中心,扩展接口为ProxyFactory
- registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为
RegistryFactory
,Registry
,RegistryService
- cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以
Invoker
为中心,扩展接口为Cluster
,Directory
,Router
,LoadBalance
- monitor 监控层:RPC 调用次数和调用时间监控,以
Statistics
为中心,扩展接口为MonitorFactory
,Monitor
,MonitorService
- protocol 远程调用层:封装 RPC 调用,以
Invocation
,Result
为中心,扩展接口为Protocol
,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-common 公共逻辑模块:包括 Util 类和通用模型。
- dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。
- dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。
- dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
- dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
- dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。
- dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用D ubbo,隐藏 Dubbo 所有细节。
- dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。
整体上按照分层结构进行分包,与分层的不同点在于:
- container 为服务容器,用于部署运行服务,没有在层中画出。
- protocol 层和 proxy 层都放在 rpc 模块中,这两层是 rpc 的核心,在不需要集群也就是只有一个提供者时,可以只使用这两层完成 rpc 调用。
- transport 层和 exchange 层都放在 remoting 模块中,为 rpc 调用的通讯基础。
- serialize 层放在 common 模块中,以便更大程度复用。
依赖关系
图例说明:
- 图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monitor 代表层或模块,蓝色的表示与业务有交互,绿色的表示只对 Dubbo 内部交互。
- 图中背景方块 Consumer, Provider, Registry, Monitor 代表部署逻辑拓扑节点。
- 图中蓝色虚线为初始化时调用,红色虚线为运行时异步调用,红色实线为运行时同步调用。
- 图中只包含 RPC 的层,不包含 Remoting 的层,Remoting 整体都隐含在 Protocol 中。
调用链
展开总设计图的红色调用链,如下:
暴露服务时序
展开总设计图左边服务提供方暴露服务的蓝色初始化链,时序图如下:
引用服务时序
展开总设计图右边服务消费方引用服务的蓝色初始化链,时序图如下:
领域模型
在 Dubbo 的核心领域模型中:
- Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
- Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
- Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。
基本设计原则
- 采用 Microkernel + Plugin 模式,Microkernel 只负责组装 Plugin,Dubbo 自身的功能也是通过扩展点实现的,也就是 Dubbo 的所有功能点都可被用户自定义扩展所替换。
- 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。
转载自:dubbo开发官方手册
=====================================
实现细节
初始化过程细节
解析服务
基于 dubbo.jar 内的 META-INF/spring.handlers
配置,Spring 在遇到 dubbo 名称空间时,会回调 DubboNamespaceHandler
。
所有 dubbo 的标签,都统一用 DubboBeanDefinitionParser
进行解析,基于一对一属性映射,将 XML 标签解析为 Bean 对象。
在 ServiceConfig.export()
或 ReferenceConfig.get()
初始化时,将 Bean 对象转换 URL 格式,所有 Bean 属性转成 URL 的参数。
然后将 URL 传给 协议扩展点,基于扩展点的 扩展点自适应机制,根据 URL 的协议头,进行不同协议的服务暴露或引用。
暴露服务
1. 只暴露服务端口:
在没有注册中心,直接暴露提供者的情况下 1,ServiceConfig
解析出的 URL 的格式为:dubbo://service-host/com.foo.FooService?version=1.0.0
。
基于扩展点自适应机制,通过 URL 的 dubbo://
协议头识别,直接调用 DubboProtocol
的 export()
方法,打开服务端口。
2. 向注册中心暴露服务:
在有注册中心,需要注册提供者地址的情况下 2,ServiceConfig
解析出的 URL 的格式为: registry://registry-host/com.alibaba.dubbo.registry.RegistryService?export=URL.encode("dubbo://service-host/com.foo.FooService?version=1.0.0")
,
基于扩展点自适应机制,通过 URL 的 registry://
协议头识别,就会调用 RegistryProtocol
的 export()
方法,将 export
参数中的提供者 URL,先注册到注册中心。
再重新传给 Protocol
扩展点进行暴露: dubbo://service-host/com.foo.FooService?version=1.0.0
,然后基于扩展点自适应机制,通过提供者 URL 的 dubbo://
协议头识别,就会调用 DubboProtocol
的 export()
方法,打开服务端口。
引用服务
1. 直连引用服务:
在没有注册中心,直连提供者的情况下 3,ReferenceConfig
解析出的 URL 的格式为:dubbo://service-host/com.foo.FooService?version=1.0.0
。
基于扩展点自适应机制,通过 URL 的 dubbo://
协议头识别,直接调用 DubboProtocol
的 refer()
方法,返回提供者引用。
2. 从注册中心发现引用服务:
在有注册中心,通过注册中心发现提供者地址的情况下 4,ReferenceConfig
解析出的 URL 的格式为:registry://registry-host/com.alibaba.dubbo.registry.RegistryService?refer=URL.encode("consumer://consumer-host/com.foo.FooService?version=1.0.0")
。
基于扩展点自适应机制,通过 URL 的 registry://
协议头识别,就会调用 RegistryProtocol
的 refer()
方法,基于 refer
参数中的条件,查询提供者 URL,如: dubbo://service-host/com.foo.FooService?version=1.0.0
。
基于扩展点自适应机制,通过提供者 URL 的 dubbo://
协议头识别,就会调用 DubboProtocol
的 refer()
方法,得到提供者引用。
然后 RegistryProtocol
将多个提供者引用,通过 Cluster
扩展点,伪装成单个提供者引用返回。
拦截服务
基于扩展点自适应机制,所有的 Protocol
扩展点都会自动套上 Wrapper
类。
基于 ProtocolFilterWrapper
类,将所有 Filter
组装成链,在链的最后一节调用真实的引用。
基于 ProtocolListenerWrapper
类,将所有 InvokerListener
和 ExporterListener
组装集合,在暴露和引用前后,进行回调。
包括监控在内,所有附加功能,全部通过 Filter
拦截实现。
远程调用细节
服务提供者暴露一个服务的详细过程
上图是服务提供者暴露服务的主过程:
首先 ServiceConfig
类拿到对外提供服务的实际类 ref(如:HelloWorldImpl),然后通过 ProxyFactory
类的 getInvoker
方法使用 ref 生成一个 AbstractProxyInvoker
实例,到这一步就完成具体服务到 Invoker
的转化。接下来就是 Invoker
转换到 Exporter
的过程。
Dubbo 处理服务暴露的关键就在 Invoker
转换到 Exporter
的过程,上图中的红色部分。下面我们以 Dubbo 和 RMI 这两种典型协议的实现来进行说明:
Dubbo 的实现
Dubbo 协议的 Invoker
转为 Exporter
发生在 DubboProtocol
类的 export
方法,它主要是打开 socket 侦听服务,并接收客户端发来的各种请求,通讯细节由 Dubbo 自己实现。
RMI 的实现
RMI 协议的 Invoker
转为 Exporter
发生在 RmiProtocol
类的 export
方法,它通过 Spring 或 Dubbo 或 JDK 来实现 RMI 服务,通讯细节这一块由 JDK 底层来实现,这就省了不少工作量。
服务消费者消费一个服务的详细过程
上图是服务消费的主过程:
首先 ReferenceConfig
类的 init
方法调用 Protocol
的 refer
方法生成 Invoker
实例(如上图中的红色部分),这是服务消费的关键。接下来把 Invoker
转换为客户端需要的接口(如:HelloWorld)。
关于每种协议如 RMI/Dubbo/Web service 等它们在调用 refer
方法生成 Invoker
实例的细节和上一章节所描述的类似。
满眼都是 Invoker
由于 Invoker
是 Dubbo 领域模型中非常重要的一个概念,很多设计思路都是向它靠拢。这就使得 Invoker
渗透在整个实现代码里,对于刚开始接触 Dubbo 的人,确实容易给搞混了。 下面我们用一个精简的图来说明最重要的两种 Invoker
:服务提供 Invoker
和服务消费 Invoker
:
为了更好的解释上面这张图,我们结合服务消费和提供者的代码示例来进行说明:
服务消费者代码:
public class DemoClientAction {
private DemoService demoService;
public void setDemoService(DemoService demoService) {
this.demoService = demoService;
}
public void start() {
String hello = demoService.sayHello("world" + i);
}
}
上面代码中的 DemoService
就是上图中服务消费端的 proxy,用户代码通过这个 proxy 调用其对应的 Invoker
5,而该 Invoker
实现了真正的远程服务调用。
服务提供者代码:
public class DemoServiceImpl implements DemoService {
public String sayHello(String name) throws RemoteException {
return "Hello " + name;
}
}
上面这个类会被封装成为一个 AbstractProxyInvoker
实例,并新生成一个 Exporter
实例。这样当网络通讯层收到一个请求后,会找到对应的 Exporter
实例,并调用它所对应的 AbstractProxyInvoker
实例,从而真正调用了服务提供者的代码。Dubbo 里还有一些其他的 Invoker
类,但上面两种是最重要的。
远程通讯细节
协议头约定
线程派发模型
- Dispather:
all
,direct
,message
,execution
,connection
- ThreadPool:
fixed
,cached
1. 即:<dubbo:service regisrty="N/A" />
或者<dubbo:registry address="N/A" />
↩
2. 即:
<dubbo:registry address="zookeeper://10.20.153.10:2181" />
↩
3. 即:
<dubbo:reference url="dubbo://service-host/com.foo.FooService?version=1.0.0" />
↩
4. 即:
<dubbo:registry address="zookeeper://10.20.153.10:2181" />
↩
5 .DubboInvoker
、HessianRpcInvoker
、InjvmInvoker
、RmiInvoker
、WebServiceInvoker
中的任何一个 ↩
转载自:dubbo开发官方手册