开源软件:Dubbo 02 整体设计

Dubbo 源码解析系列

Posted by Shoukai Huang on June 2, 2019

整体设计

设计图示(dubbo给出)

  • 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
  • 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。
  • 图中绿色小块的为扩展接口,蓝色小块为实现类,图中只显示用于关联各层的实现类。
  • 图中蓝色虚线为初始化过程,即启动时组装链,红色实线为方法调用过程,即运行时调时链,紫色三角箭头为继承,可以把子类看作父类的同一个节点,线上的文字为调用的方法。

各层说明

  • service 业务层:业务代码的接口与实现。我们实际使用 Dubbo
  • 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

模块分包

模块说明:

  • dubbo-common 公共逻辑模块:包括 Util 类和通用模型。
  • dubbo-remoting 远程通讯模块:相当于 Dubbo 协议的实现,如果 RPC 用 RMI协议则不需要使用此包。
  • dubbo-rpc 远程调用模块:抽象各种协议,以及动态代理,只包含一对一的调用,不关心集群的管理。
  • dubbo-cluster 集群模块:将多个服务提供方伪装为一个提供方,包括:负载均衡, 容错,路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
  • dubbo-registry 注册中心模块:基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。
  • dubbo-monitor 监控模块:统计服务调用次数,调用时间的,调用链跟踪的服务。
  • dubbo-config 配置模块:是 Dubbo 对外的 API,用户通过 Config 使用Dubbo,隐藏 Dubbo 所有细节。
  • dubbo-container 容器模块:是一个 Standlone 的容器,以简单的 Main 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。

整体上按照分层结构进行分包,与分层的不同点在于:

  • container 为服务容器,用于部署运行服务,没有在层中画出。
  • protocol 层和 proxy 层都放在 rpc 模块中,这两层是 rpc 的核心,在不需要集群也就是只有一个提供者时,可以只使用这两层完成 rpc 调用。
  • transport 层和 exchange 层都放在 remoting 模块中,为 rpc 调用的通讯基础。
  • serialize 层放在 common 模块中,以便更大程度复用。

工程结构

tree 命令

├── dubbo-all
├── dubbo-bom(pom定义)
├── dubbo-cluster(集群模块)
├── dubbo-common(公共包)
├── dubbo-compatible(com.alibaba.dubbo 接口兼容包)
├── dubbo-config(单体配置)
│   ├── dubbo-config-api
│   └── dubbo-config-spring(ServiceBean等Spring容器初始化内容)
├── dubbo-configcenter(集群配置)
│   ├── dubbo-configcenter-api
│   ├── dubbo-configcenter-apollo
│   ├── dubbo-configcenter-consul
│   ├── dubbo-configcenter-etcd
│   ├── dubbo-configcenter-nacos
│   └── dubbo-configcenter-zookeeper
├── dubbo-container(容器:启动函数)
│   ├── dubbo-container-api
│   ├── dubbo-container-log4j
│   ├── dubbo-container-logback
│   └── dubbo-container-spring
├── dubbo-demo
│   ├── dubbo-demo-annotation
│   ├── dubbo-demo-api
│   ├── dubbo-demo-interface
│   ├── dubbo-demo-xml
├── dubbo-dependencies(pom依赖)
│   └── dubbo-dependencies-zookeeper
├── dubbo-dependencies-bom(pom依赖)
├── dubbo-distribution(demo)
├── dubbo-filter(过滤器)
│   ├── dubbo-filter-cache
│   └── dubbo-filter-validation
├── dubbo-metadata-report(元数据:监控)
│   ├── dubbo-metadata-definition
│   ├── dubbo-metadata-report-api
│   ├── dubbo-metadata-report-consul
│   ├── dubbo-metadata-report-etcd
│   ├── dubbo-metadata-report-nacos
│   ├── dubbo-metadata-report-redis
│   └── dubbo-metadata-report-zookeeper
├── dubbo-monitor(监控)
│   ├── dubbo-monitor-api
│   └── dubbo-monitor-default
├── dubbo-plugin(插件)
│   └── dubbo-qos
├── dubbo-registry(服务注册)
│   ├── dubbo-registry-api
│   ├── dubbo-registry-consul
│   ├── dubbo-registry-default
│   ├── dubbo-registry-etcd3
│   ├── dubbo-registry-multicast
│   ├── dubbo-registry-multiple
│   ├── dubbo-registry-nacos
│   ├── dubbo-registry-redis
│   ├── dubbo-registry-sofa
│   ├── dubbo-registry-zookeeper
│   └── dubbo-registry.iml
├── dubbo-remoting(传输协议)
│   ├── dubbo-remoting-api
│   ├── dubbo-remoting-etcd3
│   ├── dubbo-remoting-grizzly
│   ├── dubbo-remoting-http
│   ├── dubbo-remoting-mina
│   ├── dubbo-remoting-netty
│   ├── dubbo-remoting-netty4
│   ├── dubbo-remoting-p2p
│   └── dubbo-remoting-zookeeper
├── dubbo-rpc(交互协议,调用remoting)
│   ├── dubbo-rpc-api
│   ├── dubbo-rpc-dubbo
│   ├── dubbo-rpc-hessian
│   ├── dubbo-rpc-http
│   ├── dubbo-rpc-injvm
│   ├── dubbo-rpc-jsonrpc
│   ├── dubbo-rpc-memcached
│   ├── dubbo-rpc-native-thrift
│   ├── dubbo-rpc-redis
│   ├── dubbo-rpc-rest
│   ├── dubbo-rpc-rmi
│   ├── dubbo-rpc-thrift
│   ├── dubbo-rpc-webservice
│   └── dubbo-rpc-xml
├── dubbo-serialization(序列化)
│   ├── dubbo-serialization-api
│   ├── dubbo-serialization-avro
│   ├── dubbo-serialization-fastjson
│   ├── dubbo-serialization-fst
│   ├── dubbo-serialization-gson
│   ├── dubbo-serialization-hessian2
│   ├── dubbo-serialization-jdk
│   ├── dubbo-serialization-kryo
│   ├── dubbo-serialization-native-hession
│   ├── dubbo-serialization-protobuf-json
│   ├── dubbo-serialization-protostuff
│   └── dubbo-serialization-test
└── pom.xml

依赖关系

图例说明:

  • 图中小方块 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 携带配置信息。

总体调用

服务暴露过程

服务器端(服务提供者)在框架启动时,会初始化服务实例,通过 Proxy 组件调用具体协议( Protocol),把服务端要暴露的接口封装成 Invoker(真实类型是 AbstractProxylnvoker),然后转换成 Exporter,这个时候框架会打开服务端口等并记录服务实例到内存中,最后通过Registry把服务元数据注册到注册中心。这就是服务端(服务提供者)整个接口暴露的过程。读者可能对里面的各种组件还不清楚,下面就讲解组件的含义:

  • Proxy组件:我们知道, Dubbo中只需要引用一个接口就可以调用远程的服务,并且只需要像调用本地方法一样调用即可。其实是 Dubbo架为我们生成了代理类,调用的方法其实是 Proxy 组件生成的代理方法,会自动发起远程本地调用,并返回结果, 整个过程对用户完全透明。
  • Protocol:顾名思义,协议就是对数据格式的一种约定。它可以把我们对接口的配置 根据不同的协议转换成不同的 Invoker对象。例如:用 DubboProtocol可以把XML文件中一个远程接口的配置转换成一个 Dubbolnvoker
  • Exporter:用于暴露到注册中心的对象,它的内部属性持有了 Invoker对象,我们可以认为它在 Invoker上包了一层。
  • Registry:把 Exporter注册到注册中心。

以上就是整个服务暴露的过程,消费方在启动时会通过 Registry在注册中心订阅服务端的元数据(包括IP和端口)。这样就可以得到刚才暴露的服务了。

远程调用过程

调用过程也是从一个 Proxy 开始的, Proxy 持有了一个 Invoker 对象。然后触发invoke调用。在 invoke 调用过程中,需要使用 Cluster, Cluster 负责容错,如调用失败的重试 Cluster 在调用之前会通过 Directory 获取所有可以调用的远程服务 Invoker列表(一个接口可能有多个节点提供服务)。由于可以调用的远程服务有很多,此时如果用户配置了路由规则(如指定某些方法只能调用某个节点),那么还会根据路由规则将 Invoker 列表过滤一遍。

存活下来的 Invoker可能还会有很多,此时要调用哪一个呢?于是会继续通过 LoadBalance方法做负载均衡,最终选出一个可以调用的 Invoker。这个 Invoker在调用之前又会经过一个过滤器链,这个过滤器链通常是处理上下文、限流、计数等。

接着会使用 Client做数据传输,如我们常见的 Netty Client等传输之前肯定要做一些私有协议的构造,此时就会用到 Codec接口。构造完成后,就对数据包做序列化(Serialization)然后传输到服务提供者端。服务提供者收到数据包,也会使用 Codec处理协议头及一些半包、粘包等。处理完成后再对完整的数据报文做反序列化处理。

随后这个Request会被分配到线程池(ThreadPool)中进行处理 Server会处理这些 Request 根据请求查找对应的 Exporter(它内部持有了 Invoker) Invoker是被用装饰器模式一层一层有了非常多 Filter的,因此在调用最终的实现类之前,又会经过一个服务提供者端的过滤器链。

最终,我们得到了具体接口的真实实现并调用,再原路把结果返回。至此,一个完整的远程调用过程就结束了。

参考