一、前言
本系列为个人Dubbo学习笔记,内容基于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章,仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。
在之前的文章中,我们知道了消费者端获取到的RefProxy 结构如下
也就是说消费者在发起一次调用的时候时序图如下(图源《深度剖析Apache Dubbo 核心技术内幕》):
需要注意是,图中的 InvokerDelegete 为 Directory 中的 Invoker列表,是在RegistryDirectory#toInvokers 中方法中 Invoker的委托类。如下:
下面我们按照时序图中的调用顺序来具体分析:
二、MockClusterInvoker
MockClusterInvoker 实现了本地 Mock。服务消费端本地服务mock主要用来做本地测试用,当服务提供端服务不可用时,使用本地mock服务可以模拟服务提供端来让服务消费方测试自己的功能,而不需要发起远程调用。
要实现Mock功能,首先需要消费端先实现服务端的 mock 实现类,需要注意的是Mock实现类必须要符合 接口包名.类名Mock格式。需要注意的是,在执行Mock服务实现类 mock() 方法前,会先发起远程调用,当远程服务调用失败时,才会降级执行mock功能。
三、FailoverClusterInvoker
虽然时序图上画的是 FailoverClusterInvoker 类,但这个类中实际上完成了整个Dubbo集群容错功能,Dubbo容错包括下面几个组件:
- Cluster & Cluster Invoker :Dubbo集群容错中存在两个概念,分别是集群接口 Cluster 和 Cluster Invoker,这两者是不同的。Cluster 是接口,而 Cluster Invoker 是一种 Invoker。服务提供者的选择逻辑,以及远程调用失败后的的处理逻辑均是封装在 Cluster Invoker 中。而 Cluster 接口和相关实现类的用途比较简单,仅用于生成 Cluster Invoker。简单来说,Cluster 就是用来创建 Cluster Invoker 的,并且一一对应。而Cluster 和 Cluster Invoker 的作用就是,在消费者进行服务调用时选择何种容错策略,如:服务调用失败后是重试、还是抛出异常亦或者返回一个空的结果集等。
- Directory :Directory 即服务目录, 服务目录中存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,比如 ip、端口、服务协议等。通过这些信息,服务消费者就可通过 Netty 等客户端进行远程调用。而实际上服务目录在获取注册中心的服务配置信息后,会为每条配置信息生成一个 Invoker 对象,并把这个 Invoker 对象存储起来,这个 Invoker 才是服务目录最终持有的对象。简单来说,Directory 中保存了当前可以提供服务的服务提供者列表集合。当消费者进行服务调用时,会从 Directory 中按照某些规则挑选出一个服务提供者来提供服务。
- Router :服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者。简单来说, Router制定了一些服务规则,Directory 中的服务提供者列表必须要满足 Router 规则才能作为候选服务提供者列表。
- LoadBalance :当服务提供方是集群时,为了避免大量请求一直集中在一个或者几个服务提供方机器上,从而使这些机器负载很高,甚至导致服务不可用,需要做一定的负载均衡策略。Dubbo提供了多种均衡策略,默认为random,也就是每次随机调用一台服务提供者的服务。简单来说,LoadBalance 制定了某种策略,让请求可以按照某种规则(随机、hash 等)分发到服务提供者的机器上。
在 这个环节中完成了 Dubbo的集群容错功能。整个集群容错可以简述为 :
- 请求经过 ClusterInvoker#invoke,在这里会通过 Directory 组件来从注册中心获取支持本次调用的服务提供者列表。
- Directory 内部获取到所有服务提供者会通过 Router#route 来剔除不符合路由规则的服务提供者,随后将剩余的服务提供者返回给 ClusterInvoker。
- ClusterInvoker拿到 Directory 返回的服务提供者列表后 根据用户参数初始化负载均衡策略,使用选择的负载均衡策略从上一步返回的 服务提供者列表 中选择一个提供者为本次调用提供服务。
具体组件的详细实现,如有需要详参:Dubbo笔记 ⑫ :Dubbo 集群组件概述
四、ProtocolFilterWrapper
ProtocolFilterWrapper 是对 Dubbo org.apache.dubbo.rpc.Filter 的处理。对于 Servlet Filter 可以完成 Servlet 的请求过滤,但是无法对 RPC 调用的请求进行过滤,此时需要Dubbo 的 org.apache.dubbo.rpc.Filter 来进行处理。
如有需要:
- 在 Dubbo笔记衍生篇③:ProtocolWrapper 中有对 ProtocolFilterWrapper 的分析。
- 在 Dubbo笔记 ⑰ :Dubbo Filter 详解 中有对各个 Filter 的分析。
五、DubboInvoker
DubboInvoker#doInvoke 是 正式发起调用的地方。具体实现如下:
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
// ... 其他处理
try {
// 是否异步,判断你条件是 RpcInvocation 中的 async 属性
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
// 是否异步 Future,判断条件是 RpcInvocation 中的 future_returntype 属性
boolean isAsyncFuture = RpcUtils.isReturnTypeFuture(inv);
// 是否不需要返回响应
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 同步 && 不需要返回值
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
// 发起同步请求
currentClient.send(inv, isSent);
// 设置 上下文 Future为空
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
/******* 异步调用 *********/
// 1. 进行异步调用
ResponseFuture future = currentClient.request(inv, timeout);
// For compatibility
// 2. 使用 FutureAdapter 包装 future
FutureAdapter<Object> futureAdapter = new FutureAdapter<>(future);
// 将适配器设置到上下文中。
RpcContext.getContext().setFuture(futureAdapter);
Result result;
// 3. 返回结果的处理。如果方法返回类型是CompletableFuture,将结果包装成 AsyncRpcResult 返回。否则返回SimpleAsyncRpcResult
if (isAsyncFuture) {
// register resultCallback, sometimes we need the async result being processed by the filter chain.
// 3.1 如果异步请求需要返回类型是 CompletableFuture ,则将结果包装成 AsyncRpcResult
result = new AsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);
} else {
// 3.2 如果异步请求返回类型不是 CompletableFuture ,则将结果包装成 SimpleAsyncRpcResult
result = new SimpleAsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);
}
return result;
/**********************/
} else {
// 其他情况,同步需要返回值
RpcContext.getContext().setFuture(null);
// 直接阻塞线程异步调用结果,这里调用的是 DefaultFuture#get
return (Result) currentClient.request(inv, timeout).get();
}
}
....
}
上面的代码注释的很清楚,其中关于消费者异步调用的流程,如有需要详参 Dubbo笔记 ⑳ :消费者的异步调用。