你好,欢迎进入江苏优软数字科技有限公司官网!

诚信、勤奋、创新、卓越

友好定价、专业客服支持、正版软件一站式服务提供

13262879759

工作日:9:00-22:00

codejock 162 Dubbo源码解析:打开黑盒,揭秘服务调用流程与消费端奥秘

发布时间:2025-11-06

浏览次数:0

一、引言

对于从事Java开发的人员来说,就dubbo而言,我们通常是将其身视为一个不被探知内部结构的黑箱来予以运用的,并不需要去把这个黑箱打开啊。

然而,跟着当下程序员领域的进展codejock 162,我们存有必要开启这个黑盒子,进而探寻当中的奇妙之处 。

这期有关 dubbo 源码解析的系列文章,会引领你去领会 dubbo 源码里面的奥秘之处 。

这期源码类文章,汲取了先前,以及Kakfa、JUC源码文章的经验,不会再逐行带着大伙剖析源码,会把一些不太重要的部分当作黑盒来处理,从而让我们能更迅速、更高效地研读源码。

虽然现在是互联网寒冬,但乾坤未定,你我皆是黑马!

废话不多说,发车!

二、服务调用流程1、消费端

之前的那篇文章,阐述了咱消费端是以怎样的方式去订阅我们服务端中注册到的服务接口,对 dubbo 服务订阅的前后经过从源码进行了全方位的剖析 。

鉴于消费端已然知晓了我们的服务信息codejock 162,于是接下来便要着手正式展开调用了。

我们先从消费端聊聊服务调用的流程

1.1 动态代理的回调

我们聊到消费端订阅服务时,最终创建的代码如下:

java复制代码public  T getProxy(Invoker invoker, Class[] interfaces) {
返回,强制转换为T类型,通过代理获取代理对象以接口为参数,然后调用newInstance方法,使用一个新的调用者调用处理程序实例化,传入调用者参数。
}

怀有看过动态代理的小伙伴理应晓得,当我们去调用代理的接口之际,实际上所走的是dler该类的方法,。

在Java里,用于复制代码的部分是,有这样一段代码为public Object invoke(Object proxy, Method method, Object[] args), 。
    // 获取方法名=getUserById
String这个词所代表的方法名,是通过method对象调用其名为getName的方法而获取到的 。
    // 获取参数
    Class参数类型数组等于方法的带类型参数,这些带类型参数是该方法获取得到的。
    
    // 组装成 RpcInvocation 进行调用
创造一个RpcInvocation实例,这个实例名为rpcInvocation,它要基于serviceModel,服务模式之名会被用于其构建,方法的名字method.getName也会是其中所需,调用者的接口名字invoker.getInterface().getName也是构造要素之一,协议服务键protocolServiceKey同样不可或缺,方法的参数类型method.getParameterTypes以及参数args,都要被用来创建这个RpcInvocation实例。
    
    // 执行调用方法
}

这里我们重点介绍下 的几个参数:

我们继续往下看 . 做了什么

关于你提的只是局部代码片段,不是完整的可运行代码,你可以补充完整后再让我进行更准确的处理,当前这样不太能按要求改写。请补充完整的内容,比如完整的类定义、方法。祈求者,远程过程调用调用请求抛出可抛出的事物 ,) 引发可抛出的事物  {。
    URL url = invoker.getUrl();
有一个名为String的东西,它被称作serviceKey,其取值是通过url调用getServiceKey这个法子来获取的 。
rpcInvocation进行设置,所设置的目标的服务拥有独一无二的名称,此名称即为serviceKey 。
    
}
// 判断当前的是应用注册还是接口注册
函数为公开的“返回结果(Result)”类型,其名为“调用(invoke)”,参数是“调用(Invocation)”,形式上是括号括起来的“调用(invocation)”,且会引发“远程过程调用异常(RpcException)” 。
要是,步骤,等于,应用程序首次出现时的这个状态,那么。
            if (promotion < 100 && ThreadLocalRandom.current().nextDouble(100) > promotion) {
            }
返回,决定调用者的特定方法所执行的操作,该操作针对调用请求发出调用请求指令,以执行调用请求指令中的具体操作,从而返回执行结果 。
        }
当前可使用的调用器进行调用,返回调用结果,针对的是此次调用操作 。然后返回该调用器对此次调用的执行结果 。
    }
}

我们继续往下追源码

1.2 过滤器

java复制代码// 过滤器责任链模式
// 依次遍历,执行顺序:
公共的接口,过滤器链构建器 在编程领域中,对于公共的接口而言,存在一处名为过滤器链构建器的部分 。
对于公开的结果而言,通过调用(发出调用指令)的方式,(该调用指令针对)相关的引用调用(行为),(此行为会)以引发远程过程调用异常(的可能性)来抛出(异常情况) 。
        Result asyncResult;
InvocationProfilerUtils进入详细分析器,通过传入调用,执行一个函数,函数体为返回一个拼接后的字符串,内容是"Filter "加上过滤器类的全限定名再加上" invoke." 。
先是有一个异步结果,这个异步结果呢,是通过过滤器调用下一个节点,并且是基于那个调用的行为获得的 。
    }
}

这里会依次遍历所有的 :

究竟每个过滤器是怎样达成实现的,在此处便不会去展开进行讲述了,往后若存在机会将单独去划出一章。

1.3 路由逻辑

当我们的责任链完成之后,下一步会经过我们的 路由 逻辑

用于复制代码时的`java`代码,其中有`public Result invoke〔这个`invoke`是用于把最终的`Invocation`进行调用的〕,并且会在出现`RpcException`异常时抛出异常 .。
    // 
    List召集者们,等于依照调用情况列出的清单 。
Invocation 进行相关操作,InvocationProfilerUtils 释放详细的探查器,针对该 Invocation 来进行 。
用于负载均衡的对象,称其为负载均衡变量,将其赋值为,通过调用初始化方法,传入调用者集合与调用请求所得到的那次负载均衡初始化操作的结果 。
如果是异步的情况,RpcUtils.attachInvocationIdIfAsync附带上调用标识到获取的URL那里,这个URL是getUrl()获取得到的,而调用标识是invocation 。
调用返回,执行调用操作,传入调用信息、调用者集合以及负载均衡器,返回执行结果 。
}

其一,List<> 等于 list() ,此处便是咱们的路由逻辑,。

java复制代码List> invokers = list(invocation);
public List对,把Invocation的实例作为参数传入得到的异常方法,这里是以罗列形式展开的,会抛出RpcException异常。
    List被路由的结果等于执行列表操作,该列表包含可用的调用者,针对调用请求进行此操作。
}
public List> doList(BitList这句话似乎不完整且带有特定编程语言相关内容,不太明确准确的改写要求,仅从这部分机械改写如下:调用者们,调用、调用行为调用(。
    // 这里就是我们的路由策略!!!
    List路由链通过所获取的消费者url,以及多种调用者,还有相关调用操作,得出了结果 。  。  其具体操作为,路由链对获取消费者url这一行为,结合多种调用者,以及相关调用操作,进行了路由 ,  并得出了结果 。
返回,是结果为null的时候,就返回BitList的空列表,否则,就返回结果 。
}

这里的路由策略比较多,我举两个比较经典的:

而对于整体路由的流程:

抵达此处后,我们这里的路由会将满足相应要求的服务端挑选出来,随后便进入到咱们负载均衡的阶段了。

1.4 重试次数

这里我们设置 为 5

@DubboReference,其协议为“dubbo”,超时时间设定为100,重试次数为5,进行Java代码复制 。
私有,一个名为IUserService的,iUserService变量被声明。,。

依据源码去看,有几次咱们要查看的调用呢,从源码方面来看,咱们会存在5加1次调用 。

用Java代码来复制,其中,定义一个整型变量len,它的值是通过调用calculateInvokeTimes这个方法,传入methodName参数后计算得到的结果 。
for (int i = 0; i < len; i++) {}
private int calculateInvokeTimes(String methodName) {
    // 获取当前的重试次数+1
    int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
    RpcContext rpcContext = RpcContext.getClientAttachment();
    Object retry = rpcContext.getObjectAttachment(RETRIES_KEY);
    if (retry instanceof Number) {
        len = ((Number) retry).intValue() + 1;
        rpcContext.removeAttachment(RETRIES_KEY);
    }
    if (len <= 0) {
        len = 1;
    }
    return len;
}

我们直接 Debug 一下看看:

1.5 负载均衡

这般一行得以经由特定方式化作(, )部分从而获致我们所拥有的负载均衡策略状况,在默认情形之下呈现为如下这般模样:

我们可以看到,默认情况下是 随机负载。

我们继续往下追源码:

Java,要复制代码,public的Result去做doInvoke,针对Invocation这个调用,还有final的List 。调用者们,负载均衡,负载均衡器) { (注:原内容错误、表达缺失逻辑,较难进行符合句式连贯表达但按要求改写了部分)。
    
    List> invoked = new ArrayList计算传递进来的用于调用的对象集合里含有的对象数量,这里指已实际启动调用操作的那些调用对象 。
    Set providers = new HashSet(len);
    for (int i = 0; i < len; i++) {
        // 如果是重新调用的,要去更新下Invoker,防止服务端发生了变化
        if (i > 0) {
            checkWhetherDestroyed();
将调用相关内容复制,使其成为一个列表存储,而这个列表是基于调用本身所生成的 。
            // 再次校验
对复制调用者进行检查调用者操作,针对这一检查调用者操作,将其应用于调用情况 。
        }
        // 负载均衡逻辑!!!
        Invoker
        invoked.add(invoker);
RpcContext所获取的服务上下文,被设置为调用者列表,此列表正是调用过的对象形成的集合,表示为(List) invoked 。
        boolean success = false;
        try {
结果,一个名为result的返回值,是通过在特定上下文环境下调用invoker,并应用invocation来获取到的。,。
            success = true;
            return result;
        } 
    }
}

这里我简单将下负载均衡的逻辑:

java复制代码Invoker调用者等于,进行选择操作,该操作基于负载均衡,针对调用请求,从调用者列表中,选取已选定的部分,来完成此次操作 。
private Invoker进行选择,负载均衡器为负载均衡,调用为调用,列表为列表, (你给出的原内容不完整,请补充完整以便更准确改写) 。> invokers, List> selected){
    // 如果只有一个服务端,那还负载均衡个屁
    // 直接校验下OK不OK直接返回就好
    if (invokers.size() == 1) {
        Invoker tInvoker = invokers.get(0);
对那个被称为 tInvoker 的进行检查,看是否应当使其无效来调用它   。

dubbo源码解析 服务调用流程_codejock 162_dubbo动态代理回调 过滤器 路由逻辑

return tInvoker; } // 如果多个服务端,需要执行负载均衡算法 Invoker负载均衡执行选择操作,从一系列调用者当中,提取出与获取的URL地址以及调用相关信息相匹配的调用者,最终将其选出并赋值给调用者变量 。 且, 调用者变量 等于 负载均衡所选出的调用者。 。 return invoker; }

Dubbo 里面的负载均衡算法如下:

这里也就不一介绍了,正常情况下,我们采用的都是 负载均衡

当然这里博主介绍另外一个写法,也是我们业务中使用的

1.4.1 自定义负载均衡

上面我们所见到的情形是,借助某种等式关系,也就是 = (, ) ,我们能够获取到一个达成负载均衡功能的实现类别 。

在咱们的生产情形里,不一样的集群中有不一样的合作方,我们得依据合作方来实行不同集群的调用分发 。

于这个特定时候,咱能够去重新撰写我们的那个,于其所置之处重新梳理我们的逻辑,而此处所提及的集群A,实际上也就是我们所指的group 。

1.6 调用服务

在我们将如下流程予以完成的时候:过滤器,而后是路由,接着是重试,再之后是负载均衡,便抵达了如下这一行:

实现Java代码的复制,名为Result对象的result,其结果是通过在特定上下文环境中调用名为invoker的调用器结合Invocation的情形来获取的,这一获取操作最终导出了result 。

我们继续往下追:

用于Java语言中的复制代码,公开的`Result`类型对象,通过调用`invoke`方法,该方法接收参数为`Invocation`类型的`invocation`,并且会抛出`RpcException`异常 。
    try {
        // 加读写锁
        lock.readLock().lock();
    } finally {
        lock.readLock().unlock();
    }
}

我们直接追到 的 方法

用Java复制这些代码,其中“public Result invoke(Invocation inv) throws RpcException {” ,这是一段代码,有特定功能,是用来进行某种操作,对传入的Invocation对象进行调用的,会抛出Rpc异常 。
RpcInvocation 这个调用实例,它被进行了类型转换,转换后被设置为 (RpcInvocation) inv中的 RpcInvocation所包含的内容。
    // 配置RPCinvocation
将引起调用的事物准备好,这个引起调用的事物引发程序运行变化,按照特定的顺序和路径、执行相应的操作和任务,进而实现。
    // 调用RPC同时同步返回结果
用doInvokeAndReturn这个操作,针对invocation进行调用并返回的操作,所得到的结果,被赋值给AsyncRpcResult类型的asyncResult 。
    // 等待返回结果
等待如果同步的异步结果,其中异步结果为asyncResult,所应用的调用为invocation 。
    return asyncResult;
}

我们可以看到,对于调用服务来说,一共分为一下三步:

1.6.1 配置

这里主要将 转变成

Java复制代码,私有 void 去准备调用,RpcInvocation是inv 。
将RpcInvocation的Invoker属性进行设置,以此来表明该调用是经由哪个Invoker发起的,。
    inv.setInvoker(this);
    
	// 当前线程的一些状态信息
对inv执行添加调用附件的操作 ,对inv施行添加调用附件的行为 ,对inv进行添加调用附件的动作 。
    // 同步调用、异步调用
引入变量inv,设置其调用模式,该模式通过根据给定的url以及inv,调用RpcUtils中的某个方法来获取得到 。
    // 异步调用生成一个唯一的调用 ID
RpcUtils,若为异步情况,将调用标识符附加到获取的URL上,针对inv进行此操作。调用获取URL的方法,把调用标识符附加到inv上,在异步状况下由RpcUtils来做。 其中,RpcUtils进行。
    // 选择序列化的类型
Byte serializationId ,通过执行 CodecSupport.getIDByName ,利用 getUrl().getParameter  ,以 SERIALIZATION_KEY 为参数名 ,从 DefaultSerializationSelector.getDefaultRemotingSerialization() 获取默认远程序列化值 ,最终得到该值 。 , 。
倘若序列化标识符并非为空,那么{此处为嵌套结构提示,实际无需翻译}。
inv进行放置操作,将序列化ID键、serializationId放入其中 。
    }
}

1.6.2 调用 RPC 同步返回结果

私有的,异步远程过程调用结果,通过执行且返回,远程过程调用请求,来达成,这样一个操作,其代码用Java写成 。
这个异步结果,是通过对调用进行操作后,以异步远程过程调用结果的形式得到的,具体而言它等于把调用操作的结果转换为异步远程过程调用结果 。
}
被保护的结果,执行调用,针对最终的调用,(此句中)调用为那个调用,进行这样操作: 这个操作返回此类结果 。
    // 获取超时时间
定义整型变量timeout,其值为RpcUtils通过传入getUrl、invocation、methodName以及DEFAULT_TIMEOUT来计算得到的超时时间值。
   
    // 设置超时时间
把TIMEOUT_KEY这个值,设置为timeout转换为的字符串,以此来对invocation进行附件设置。
    
    // 从dubbo线程池中拿出一个线程
得到回调执行器,此回调执行器,是基于获取的网址、调用的操作,来获取的,将其赋值给一个执行器服务对象。
    // request:进行调用
	CompletableFuture当前客户端,发出请求,针对调用,输入超时限制,采用执行器,产生响应未来对象,而后将对象应用转换,变成应用响应,在此过程中,赋值给应用响应未来对象。
把与未来相关的上下文获取出来,接着,将具有兼容性的未来对象设置到这个获取出来的未来相关的上下文之中,此具有兼容性的未来对象是应用响应的未来对象 。
存在一个名为AsyncRpcResult的结果,它是通过一个名为new的操作创建的,其参数是一个名为AsyncRpcResult的结果,该结果源自一个名为appResponseFuture的应用响应未来对象,以及一个名为inv的INV对象,由此诞生了该存在。
    result.setExecutor(executor);
    return result;
}

这里的 . 进行请求的发送:

使请求对象,设置超时时间,借助执行器服务,来进行操作 ,这些操作被包含在一个方法里 ,该方法接收请求对象 ,接收超时时间 ,接收执行器服务 。
返回,客户端,请求,该请求,超时设置,执行器,通过执行器来处理请求并返回 。
}
public CompletableFuture request(Object request, int timeout, ExecutorService executor){
    Request req = new Request();
req进行设定版本的操作,此版本由获得协议版本的Version来提供,。
    req.setTwoWay(true);
    req.setData(request);
DefaultFuture future,它是DefaultFuture用newFuture方法,基于channel、req、timeout以及executor创建出来的 。
    channel.send(req);
    return future;
}

在此处的这个 .send(req) 呀,乃是 dubbo 自行进行包装的呢,那我们就去瞧一瞧其内在的实现状况吧 。

当然,要是我们这儿有人看过博主 Netty 源码文章,实际上是能够猜到的,必定是对 Netty 进行了封装的。

使系统依据特定条件,对涉及到的各种不同类型且具有独特性质的相关对象,进行全面细致的、多种多样且复杂程度各异的、符合特定规则与算法要求的详细处理,以达成预期的精确目标,其中包含依据特定格式来处理名为message的对象,同时依据特定逻辑判断名为sent的布尔值,在处理过程中若出现特定异常情况,则按照对应的规则与流程来。
        // 校验当前的Channel是否关闭
        super.send(message, sent);
        boolean success = true;
        int timeout = 0;
        try {
            // channel 写入并刷新
横线斜杠,通道冒号,输入输出网络工具包,通道接口冒号,通道句号。
有一个ChannelFuture,它被命名为future,其来源是通过一个channel进行写入并且刷新一条消息而得到的 。
            if (sent) {
                // 等待超时的时间
                // 超过时间会报错
首先获取 URL,接着从获取的 URL 中获取正参数,该正参数的键为 TIMEOUT_KEY,若获取不到则使用默认超时时间 DEFAULT_TIMEOUT,最终将此结果赋值给 timeout 。
在未来,成功等于等待超时的结果,这是一种特定的情况,它涉及到等待一个时限。 ,结果为成功 。 ,该结果是通过等待未来的某个状态得到的 。 ,。
            }
            // 这里如果报错了,就会走重试的逻辑
Throwable类型的cause,等于future得到的cause 。
    }
}

1.6.3 等待返回结果

要是同步的话,针对异步结果,利用调用,等待结果成功,此操作用Java复制代码来实现。
私有的 void 类型的方法,用于在进行同步操作时等待结果,如果 AsyncRpcResult 类型的异步 RPC 结果存在,以及 RpcInvocation 类型的 RPC 调用存在,那么执行该方法 。
    // 判断当前的调用是不是同步调用
    // 异步调用直接返回即可
倘若,调用模式为同步的情形下,与调用所获取到的调用模式并不相同 ,(在此条件下执行后续操作)。
        return;
    }
    
    // 获取超时时间 
对象超时键,这个东西,它等于调用获取对象附件且不进行转换,具体拿没拿超时键,就是这么个状态,就像这样,以超时键为参数,这么个操作符,它去获取对象附件且不进行转换,然后得到的那个结果,就是对象超时键 。
长的超时时间等于,远程过程调用工具(RpcUtils),转变转换为数字,通过超时键(timeoutKey),取得整数的最大值(Integer.MAX_VALUE) 。
    // 等待timeout时间
    // 获取失败-直接抛出异常
对 `asyncResult` 进行调用,然后获取其结果,设置超时时间为 `timeout`,时间单位为 `TimeUnit.MILLISECONDS` 。
}
可获取的公开资源所对应的结果,通过指定长时间的超时设置并以特定时间单位进行衡量,以此操作来得到此结果 ,对应的具体方法名称为get ,其中超时时间以长整型数值表示。
    // 获取响应返回的数据-等待timeout时间
返回,响应未来对象,获取,超时时间,时间单位,以此方式操作 。
}

如果没有异常,如下图所示:

到这里我们的消费端调用服务的整个流程源码剖析就完毕了~

三、流程

高清图片可私聊博主

四、总结

鲁迅先曾讲,独自前行困难颇大,众人一同前行则较为容易,同志向与兴趣相投的人一道取得进步,将彼此毫无掩饰毫无保留地分享经验,才是应对互联网寒冬时节那种艰难状况的最好选择。

有的时候,并非是我们自身不够努力,极有可能是努力的层面出现偏差,要是有个人能够略微给予你一些指引,你确实很有可能会减少几年的曲折路程 。

如有侵权请联系删除!

13262879759

微信二维码