Dubbo 调用过程
Netty 方式调用是多线程事件驱动的方式,并将对应的业务线程进入等待唤醒状态,当响应线程收到对应的业务事件时,进而唤醒对应的业务线程,所以请求与响应是不同的线程完成的
在启动时候会创建 NettyServer
,NettyClient
对象。分别用于服务端,客户端的netty
操作。当需要发送请求使用NettyClient
,收到请求或响应时则通过 NettyServer
处理
调用概述图
- 业务线程 ->
- 业务线程 <-

- 可以看到每个流程都是有对应的线程来完成,当出现业务调用时会将对应的远程请求委托给请求线程完成,委托完成后,业务会进入等待唤醒的状态
- 当远程服务处理完成返回结果时,是由另外的线程来完成的,它会先进行解码(解析响应数据转为对应的返回类型),并委托给响应线程处理
- 响应线会发送信号唤醒等待的业务线程
这种线程模型可以简单理解为老板找工人方式。如当出现业务调用时,可以将业务线程看作老板,老板将远程调用的请求(可以理解为某项工作)交给某个员工去处理,老板交代完事情,就可以去做老板该做的事情,当员工完成工作就会主动通知老板
调用流程
- InvokerInvocationHandler#invoke
- MockClusterInvoker#invoke
- AbstractClusterInvoker#invoke
- FailoverClusterInvoker#doInvoke
- InvokerWrapper#invoke
- Filter#invoke 过滤器 (拦截器) 调用
- ConsumerContextFilter#invoke
- FutureFilter#invoke
- MonitorFilter#invoke
- ListenerInvokerWrapper#invoke
- DubboInvoker#doInvoke
- ReferenceCountExchangeClient#request
- DefaultFuture#get()
响应流程
- NettyClientHandler#channelRead
- AbstractPeer#received
- MultiMessageHandler#received
- HeartbeatHandler#received
- AllChannelHandler#received
- 通过线程池异步执行
- cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));
- ChannelEventRunnable#run
- DecodeHandler#received
- HeaderExchangeHandler#received
- HeaderExchangeHandler#handleResponse
- DefaultFuture#received
- DefaultFuture#doReceived
- 唤醒对应的业务线程 -> DefaultFuture#get()
- done.signal();
调用流程源码分析
Dubbo 的服务调用通常是通过一组 ClusterInvoker 对象来实现的,每个 ClusterInvoker 对象都代表了一种服务调用策略
org.apache.dubbo.rpc.proxy.InvokerInvocationHandler#invoke
负责将代理对象上的方法调用转换成远程调用,并处理远程调用的结果和异常情况
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
return invoker.invoke(createInvocation(method, args)).recreate();
}
拦截 Object
类中的方法,为什么要拦截呢?以下是个人理解
- 如果没有进行这样的拦截处理,当调用代理对象中
Object
方法时,将会被直接转发到远程服务端,这可能会造成运行错误 - 代理服务使用了
toString
,hashCode
,equals
方法,但是它走了远程调用,那么在比较2个相同的代理服务的返回数据时,它们都是不相同的 - 比如你想用
equals
比较2个相同代理服务对象是否相同时,它们是永远不相等的。因为远程调用了另外一个对象的equals
方法,除非在远程实现重写该方法 - 剩下的
Object
方法基本都是 native
标识的。当使用notify
想唤醒本地线程时,结果它远程调用了另外一个进程的notify
方法,那么本地的线程是不能被唤醒的
createInvocation(method, args)
创建一个 RpcInvocation
对象,用于封装方法的调用信息,包括调用的方法名、参数列表、参数类型等
private RpcInvocation createInvocation(Method method, Object[] args) {
RpcInvocation invocation = new RpcInvocation(method, args);
if (RpcUtils.hasFutureReturnType(method)) {
invocation.setAttachment(Constants.FUTURE_RETURNTYPE_KEY, "true");
invocation.setAttachment(Constants.ASYNC_KEY, "true");
}
return invocation;
}
- 设置
Constants.ASYNC_KEY
属性的作用是控制Dubbo在调用方法时使用同步还是异步的方式 - 通过设置
Constants.FUTURE_RETURNTYPE_KEY
属性,Dubbo
可以知道异步调用返回结果的类型,从而在异步调用完成后正确地将结果转换为对应的类型并返回给调用者
- 需要注意的是,
Constants.FUTURE_RETURNTYPE_KEY
属性只在异步调用时才会生效,对于同步调用,该属性的值不会被使用
RpcInvocation
构造时封装的数据
public RpcInvocation(Method method, Object[] arguments) {
this(method.getName(), method.getParameterTypes(), arguments, null, null);
}
public RpcInvocation(String methodName, Class<?>[] parameterTypes, Object[] arguments,
Map<String, String> attachments,Invoker<?> invoker) {
this.methodName = methodName;
this.parameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
this.arguments = arguments == null ? new Object[0] : arguments;
this.attachments = attachments == null ? new HashMap<String, String>() : attachments;
this.invoker = invoker;
}
委托调用并处理响应结果
RpcResult = invoker.invoke(RpcInvocation);
return Result.recreate();
- 将
RpcInvocation
对象传递给对应的 Invoker
实例,并调用其 invoke
方法进行远程调用 - 得到响应信息并封装到
Result
对象中返回给代理对象
处理返回结果 recreate()
,以下是 RpcResult
实现的源码
public Object recreate() throws Throwable {
if (exception != null) {
throw exception;
}
return result;
}
- 如果存在处理异常信息则直接抛出对应的异常
- 如果处理正常,则从对象中获取调用
result
结果,并将其返回给代理对象
org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#invoke
控制服务降级,根据服务状态和配置来决定是否需要使用 Mock
数据来替代实际服务调用
获取一个 Constants.MOCK_KEY
配置数据,该数据用于控制是否需要服务降级的作用。当 Constants.MOCK_KEY
没有数据时,则返回一个 false
的数据
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
result = doMockInvoke(invocation, null);
} else {
try {
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
}
if (logger.isWarnEnabled()) {
logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
}
result = doMockInvoke(invocation, e);
}
}
return result;
}
当无法获取对应的 Constants.MOCK_KEY
配置数据时,会默认返回 false
数据,那么它会调用下一个 ClusterInvoker 对象来执行实际的服务调用,并返回调用结果
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
result = this.invoker.invoke(invocation);
}
如果 Constants.MOCK_KEY
配置了 force
开头的数据,那么无论服务是否可用或者调用是否超时,都会强制使用 Mock
数据来替代实际服务调用
else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
result = doMockInvoke(invocation, null);
}
最后的分支则是混搭,只有在服务不可用或者超时才会使用 Mock
数据来替代实际服务返回
else {
try {
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
result = doMockInvoke(invocation, e);
}
}
directory.getUrl().getMethodParameter
用于获取指定方法的参数配置信息
public String getMethodParameter(String method, String key, String defaultValue) {
String value = getMethodParameter(method, key);
if (StringUtils.isEmpty(value)) {
return defaultValue;
}
return value;
}
public String getMethodParameter(String method, String key) {
String value = parameters.get(method + "." + key);
if (StringUtils.isEmpty(value)) {
return getParameter(key);
}
return value;
}
public String getParameter(String key) {
String value = parameters.get(key);
if (StringUtils.isEmpty(value)) {
value = parameters.get(Constants.DEFAULT_KEY_PREFIX + key);
}
return value;
}
mock 参数数据包括以下几个:
- mock:指定该方法使用的 Mock 实现类名。可以通过在 Dubbo 配置文件中使用 dubbo:method 标签或者在代码中使用 @Mock 注解来指定
- mock.delay:指定 Mock 调用时的延迟时间,单位为毫秒。可以用来模拟服务调用的响应时间较长的情况
- mock.value:指定 Mock 调用时返回的结果。可以用来模拟服务调用返回的结果,例如返回空结果、返回固定结果、抛出异常等
- mock.return:同 mock.value,用于指定 Mock 调用时返回的结果
- mock.expire:指定 Mock 调用时的过期时间,单位为毫秒。可以用来模拟服务调用超时的情况
- mock.force:指定是否强制使用 Mock 数据。如果设置为 true,那么无论服务是否可用或者调用是否超时,Dubbo 都会强制使用 Mock 数据来替代实际服务调用
org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke
- 判断当前调用是否被销毁, 销毁的情况下直接抛出异常