服务调用过程
由于 Invoker 是 Dubbo 领域模型中非常重要的一个概念,很多设计思路都是向它靠拢。这就使得 Invoker 渗透在整个实现代码里,对于刚开始接触 Dubbo 的人,确实容易给搞混了。 下面我们用一个精简的图来说明最重要的两种 Invoker——服务提供 Invoker 和服务消费 Invoker:
贴一张服务调用的整体过程图
这张图可以看出大致的整理流程,然而一些集群,重试,负载均衡等步骤会专门抽出一节来讲。本章更注重服务端和消费端具体的通信细节,线程模型等。
客户端调用
客户端调用涉及的细节,比如集群等单独抽出一章来分析,可以先上一张图预览全貌
Dubbo 支持同步和异步两种调用方式,其中异步调用还可细分为“有返回值”的异步调用和“无返回值”的异步调用。所谓“无返回值”异步调用是指服务消费方只管调用,但不关心调用结果,此时 Dubbo 会直接返回一个空的 RpcResult。若要使用异步特性,需要服务消费方手动进行配置。默认情况下,Dubbo 使用同步调用方式。
// 异步无返回值
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
// 发送请求
currentClient.send(inv, isSent);
// 设置上下文中的 future 字段为 null
RpcContext.getContext().setFuture(null);
// 返回一个空的 RpcResult
return new RpcResult();
}
// 异步有返回值
else if (isAsync) {
// 发送请求,并得到一个 ResponseFuture 实例
ResponseFuture future = currentClient.request(inv, timeout);
// 设置 future 到上下文中
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
// 暂时返回一个空结果
return new RpcResult();
}
// 同步调用
else {
RpcContext.getContext().setFuture(null);
// 发送请求,得到一个 ResponseFuture 实例,并调用该实例的 get 方法进行等待
return (Result) currentClient.request(inv, timeout).get();
}
调用生成ResponseFuture后,会根据唯一请求id和future对象进入一个map池子中,然后等待回调的触发。回调可能是远程的通信,也可能是超时线程检查到超时的打断。
远程通信
从前面出现过的架构图中可以看到,远程通信模块共分为三层,从上到下分别是 Exchange
信息交换层,Transport
网络传输层以及 Serialize
序列化层,每一层都有其特定的作用。
从最底层的 Serialize
层说起,这一层的作用就是负责序列化/反序列化,它对多种序列化方式进行了抽象,如 JDK 序列化,Hessian 序列化,JSON 序列化等。
往上则是 Transport
层,这一层负责的单向的消息传输,强调的是一种 Message
的语义,不体现交互的概念。同时这一层也对各种 NIO 框架进行了抽象,例如 Netty
,Mina
等等。
再往上就是 Exhange
层,和 Transport
层不同,这一层负责的是请求/响应的交互,强调的一种 Request
和 Reponse
的语义,也正是由于请求响应的存在,才会有 Client
和 Server
的区分。
服务端处理
服务端都是通过IO线程接受请求,此时会决定是否在IO线程上进行逻辑处理。如果逻辑都在IO线程上很有可能会堵塞其他的请求,所以dubbo有五种不同的派发策略。
如上图,红框中的 Dispatcher 就是线程派发器。需要说明的是,Dispatcher 真实的职责创建具有线程派发能力的 ChannelHandler,比如 AllChannelHandler、MessageOnlyChannelHandler 和 ExecutionChannelHandler 等,其本身并不具备线程派发能力。Dubbo 支持 5 种不同的线程派发策略,下面通过一个表格列举一下。
策略 | 用途 |
---|---|
all | 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件等 |
direct | 所有消息都不派发到线程池,全部在 IO 线程上直接执行 |
message | 只有请求和响应消息派发到线程池,其它消息均在 IO 线程上执行 |
execution | 只有请求消息派发到线程池,不含响应。其它消息均在 IO 线程上执行 |
connection | 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池 |
服务端收到消息,解码后。会根据invocation解析出之前暴露的对应的Exporter,然后调用invoker执行实现类中的方法,并返回处理结果。
最后附上一张官网的图: