Dubbo 架构演进:从 RMI 到 Dubbo
谈谈你对 Dubbo 的认知?对于这个问题,你是怎么回答的呢?
我们知道 Dubbo 是从 RPC 起家,到现在则已经发展成为一个微服务的治理框架。所以,要回好上面的问题,关键是二点:Dubbo 是如何进行 RPC 通信,二是 Dubbo 是如何进行服务治理。
本文试图从 Dubbo 的架构演进来回答上述问题,包括:一是最原始的 RPC 通信如何实现;二是分析传统的 JDK RMI 架构模型,并指出它的不足;三是 Dubbo 又在 RMI 的基础上做了那些改进?
1. 自定义 RPC
如果我们要实现自己的 Java RPC,即直接通过 socket 将参数发送给服务端,我们需要那些发送那些参数呢?首先是类名(serviceName)、方法名(methodName)、方法参数类型(parameterTypes)、以及具体的参数(args)。其中前三个是定位信息,可以定位到具体实现类的具体方法,最后通过 method.invoke(args) 反射执行方法。代码实现参考文章《实现自己的 Java RPC》。
在我们自己实现的 Java RPC 中,分为两层:第一层是应用层,第二层是网络层。这样网络层代码就会侵入应用层,怎么解决这个问题呢?其实我们可以给 service 生成一个代理类 proxy,由代理类负责所有的网络通信,这就是 Java RMI 在改进措施,同时 RMI 还提供了服务注册与发现的功能。
2. RMI 架构
2.1 整体架构
RMI 的具体源码分析见《RMI 源码分析》,这里只是着重分析其架构设计。在 RMI 中有三个角色:注册中心(Registry)、客户端(Client)、服务端(Server)。
说明:这张图大家都很熟悉了,我就不再多说。在整个 RMI 中,提出了一个非常核心的概念:Stub(存根) 和 Skeleton(骨架)。这翻译其实有点古怪,后面还是叫 Stub 和 Skeleton。之后的 RPC 框架基本上都沿用了这个概念,客户端 Stub 和服务端 Skeleton 是 Service 的代理对象,专门用于屏蔽网络通信。
2.2 RPC 调用
有了 Stub 和 Skeleton 那服务之间的调用就变成了下面这样:
说明:在上图中我们可以很清楚的看到,所有和网络通信相关的都封装在了 Stub 和 Skeleton 中。相对来说,服务端 Skeleton 的概念其实有点虚,在 RMI 中并没能一个明确的对象,而客户端 Stub 是接口 Service 的代理对象。这样整个服务注册、发现及调用过程如下:
-
服务注册(1 ~ 2)
- 第一步:服务端创建远程对象。一是创建 ServiceImpl 远程对象;二是注册 ServiceImpl 服务。ServiceImpl 继承自 UnicastRemoteObject,在创建时默认会随机绑定一个端口,启动 Socket 来监听客户端的请求。
- 第二步:向注册中心注册该服务。注意:和其它的注册中心不同,Registry 只能注册本地服务。
-
服务发现(3 ~ 4)
- 客户端访问注册中心,会将服务端生成的 Stub 序列化后传给客户端,客户端反序列化后生成一个对应的实例。之后,就可以使用这个 Stub 和服务端进行通信了。**注意:Stub 是服务端生成的,使用的时候通过网络传给客户端。
-
服务调用(5 ~ 9)
- 客户端存根和服务器骨架通信,返回结果。有一个很关键的问题,Stub 调用时是如果找到要调用的方法的?Java RPC 调用远程方法需要四元组信息:接口名、方法名称、参数类型、参数。服务端注册服务时每个 Service 都会生成一个 ObjID,并封装在 Stub 中。同时 Java 类中每一个方法都会对应唯一的编号 opum,每次通信时 Stub 都会将 ObjId 和 opum 发送给服务方。
这样 RMI 其实就可以分为三层:
说明:后续的 RPC 框架基本沿用了 RMI 的架构,Dubbo 的整体架构和 RMI 基本上差不多,主要是增加了服务治理的功能。RMI 中代理层包括 stub 和 skeleton 两个代理对象,它们既是代理对象(Proxy)也是执行对象(Invoker)。服务治理则是围绕服务实例(ip/port)展开,也就是在执行对象 Invoker 上做文章,而 RMI 中将代理对象和执行对象耦合在一起,导致扩展非常困难。下面,我们就看一下 Dubbo 在 RMI 上的改进。
3. Dubbo 架构
因此,我认为 Dubbo 相对于 RMI 在架构上最大的改进是:将执行对象 Invoker 和代理对象 proxy 分开。事实上,Invoker 是 Dubbo 中最核心的领域模型,其服务治理都是围绕 Invoker 做文章,在读 Dubbo 源码之前,我们脑海中一定要有一个这样的深刻印像。Dubbo 的架构可以参考官网的文章《Dubbo 框架设计》。
在 Dubbo 的核心领域模型中,可以看到 Protocol 和 Invocation 实际上也是围绕 Invoker 展开的:
Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等。
3.1 整体架构
下图是 Dubbo 的整体架构图,可以看到相比于 RMI 而言,变化不大。
说明:服务治理是 Dubbo 最核心的功能,所有的功能都是围绕 Invoker 展开。下面会逐一分析 Dubbo 服务注册、服务发现、服务调用基本功能,以及其服务治理功能。
3.2 RPC 调用
Dubbo 整个服务注册、发现及调用过程如下:
-
服务注册:Dubbo 在注册服务分为两步,需要先将服务实例 service 包装成 Invoker 后,再注册服务。
- 一是调用 ProxyFactory#getInvoker 将 serviceImpl 包装成 Invoker。
- 二是调用 Protocol#export 注册服务,这个注册服务的过程和 RMI 几乎完全一样,启动 socket 监听,并将 Invoker 包装成 Exporter 保存到一个 Map 集合中,最后注册。
-
服务发现:和服务注册相反,需要生成 Invoker,再生成 service 接口的代理类。
- 一是调用 Protocol#refer 生成 Invoker,这个 Invoker 封装了网络通信。
- 二是调用 ProxyFactory#getProxy 生成 service 接口的代理类。
-
服务调用:客户端将 Invocation 参数发送给服务端,包含接口名称,方法名称,方法参数类型,实际参数。通过 Invocation 前三个参数(接口名、方法名、方法参数)就可以定位到具体的方法,再通过反射调用。
说明:可以看到的是,在整个 RPC 调用过程中都是围绕 Protocol、Invoker、Invocation 这三个核心的领域模型展开。
3.3 服务治理
RMI 只是一个纯粹的 RPC 框架,而 Dubbo 现在已经围绕服务治理形成了一个生态。现在我们就看一下 Dubbo 是如何进行服务治理的。Dubbo 的服务治理其实都是对 Invoker 进行处理,服务治理包含以下四个方面。
- 服务发现:核心接口 Directory。调用 Directory.list() 方法获取注册中心的 Invoker,实现类有 StaticDirectory 和 RegistryDirectory。
- 服务路由:核心接口 Router。调用 Router.route(invokers, url, invocation) 来过滤 Invoker,实现类有 ConditionRouter(条件路由)、ScriptRouter(脚本路由)等。
- 负载均衡:核心接口 LoadBalance。调用 LoadBalance.select(invokers, url, invocation) 过滤 Invoker,实现类有 RandomLoadBalance(随机)、RoundRobinLoadBalance(轮询)、ConsistentHashLoadBalance(一致性Hash)等。
- 集群容错:核心接口 Cluster。调用 Cluster.join(directory) 将多个 invokers 合并成一个 Invoker,实现类有 FailbackClusterInvoker(失败恢复)、FailoverClusterInvoker(失败转移)、FailsafeClusterInvoker(安全失败 )等。
这些接口又是如何配合起来工作的呢?
4. 总结时刻
- 最简单的 RPC 通信。我们需要发送类名(serviceName)、方法名(methodName)、方法参数类型(parameterTypes)、以及具体的参数(args)这个参数,这些信息在网络通信中一个统一的名称 Invocation。此时,只有应用层和网络层。
- Java RMI。主要做了两点改进:一是增加代理类 proxy,用于屏蔽网络通信;二是增加服务注册与发现的功能。此时架构分为三层:应用层、代理层、网络层。
- Dubbo。为了支持服务的注册与发现,Dubbo 将代理层 proxy 抽取出执行体 Invoker,后续的整个服务治理与发现都是围绕 invoker 展开。此时架构分为五层:应用层、代理层、服务治理层(Invoker)、协议层、网络层。
推荐阅读
- 《RMI 源码分析》:对 RMI 的整体架构从服务注册、服务发现和服务调用三个部分进行分析。
- 《Dubbo 框架设计》:Dubbo 官网。