Dubbo 调用过程源码分析(更新中)

文章详细阐述了Dubbo调用过程中涉及的线程模型,包括Netty的事件驱动机制,如何通过NettyServer和NettyClient进行服务端和客户端交互。调用流程包括从业务线程到请求线程,再到响应解析线程,最后唤醒业务线程的过程。此外,还介绍了拦截器的角色以及服务降级策略MockClusterInvoker的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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();
    //用于拦截 `Object` 类中的方法, 如果是 `Object` 类中的方法, 直接本地调用 
    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]);
    }
  
    //将 `method` 和 `args` 封装到 `RpcInvocation` 中,并执行后续的调用
    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")) {
            //no mock
            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());
            }
            //force:direct mock
            result = doMockInvoke(invocation, null);
        } else {
            //fail-mock
            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

  • 判断当前调用是否被销毁, 销毁的情况下直接抛出异常
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值