先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
Map 里面的 key 是调用编号,也就是第 82 行代码中,从 request 里面获得的 id:
这个 id 是 AtomicLong 从 0 开始自增出来的。
代码里面还给了这样一行注释:
getAndIncrement() When it grows to MAX_VALUE, it will grow to MIN_VALUE, and the negative can be used as ID
说这个方法当增加到 MAX_VALUE 后再次调用会变成 MIN_VALUE。但是没有关系,负数也是可以当做 ID 来用的。
这个 DefaultFuture 对象构建完成后是返回回去了。
返回到哪里去呢?
就是 DubboInvoker 的 doInvoker 方法中下面框起来的代码:
在 103 行,包装之后的 DefaultFuture 会通过构造方法放到 AsyncRpcResult 对象中:
而 DubboInvoker 的 doInvoker 方法返回的这个 result,即 AsyncRpcResult 就是前面标号为 ① 这里的返回值:
接着说说标号为 ② 的地方。
首先是判断当前调用模式是否是同步调用。我们这里就是同步调用,于是进入到 if 判断里面的逻辑。在这里面一看,调用的 get 方法,还带有超时时间。
看一下这个 get 方法是怎么样的:
可以看到这个 get 方法不是一个简单的异步编程的 CompletableFuture.get 。里面还包含了一个 ThreadlessExecutor 的 waitAndDrain 方法的逻辑。
这个方法一进来就是 queue.take 方法,阻塞等待。
这个队列里面装的是什么东西?
全局查找往这个队列里面放东西的逻辑,只有下面这一处:
说明这个队列里面扔的是一个 runable 的任务。
这个任务是什么呢?
我们这里先买个关子,放到下一小节里面去讲。
你只要知道:如果队列里面没有任务,那么用户线程就会一直在 take 这里阻塞等待。
有的小伙伴就要问了:这里怎么能是阻塞式的无限等待呢?接口调用不是有超时时间吗?
注意了,这里并不是无限等待。Dubbo 会保证当接口不管是否超时,都会有一个 Runable 的任务被扔到队列里面。所以 take 这里最多也就是等待超时时间这么长时间。
先记着这里,下面会给大家讲到超时检测的逻辑。
看到这里,我们已经和官网上的回答产生一点联系了,我再给大家捋一捋我们现在有的东西:
第一点:用户线程在 AsyncToSyncInvoker 类里面调用了下面这个方法,在等结果。代码和官网上的描述的对应关系如下:
官网上说:会调用不同 DefaultFuture 对象的 get 方法进行等待,这应该是 2.6.x 版本的做法了。
在 2.7.5 版本中是在 AsyncRpcResult 对象的 get 方法中进行等待。而在该方法中,其实是调用了队列的 take 方法,阻塞等待。
在这两个不同对象上的等待是两种完全不同的实现方式。2.7.5 版本里面这样做也是为了做客户端的共享线程池。实现起来优雅了很多,大家可以拿着两个版本的代码自行比较一下,理解到他的设计思路之后觉得真的是妙啊。
但是不论哪个版本,万变不离其宗,请求发出去后,还是需要在用户线程等待。
第二点:发送 request 对象之前构建了一个 DefaultFuture 对象。在这个对象里面维护了一个静态 MAP:
有了调用编号和 DefaultFuture 对象的映射关系。等收到 Response 响应之后,我们从 Response 中取出这个调用编号,就知道这个调用编号对应的是哪个 DefaultFuture 了,妙啊。
但是,等等。“从 Response 中取出这个调用编号”,那不是意外着我们得把调用编号送到服务端去?在哪送的?
答案是在协议里面,还记得上一篇文章中讲协议的时候里面也有个调用编号吗?
呼应上了没有?
每个请求和响应的 header 里面都有一个请求编号,这个编号是一一对应的,这是协议规定好的。
在发送 request 之前,对其进行 encode 的时候写进去的:
org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#encodeRequest
然后 Dubbo 就拿着这个携带着 requestId 的请求这么轻轻的一发。
你猜怎么着?
就等着响应了。
接收响应,寻找请求
请求发出去是一件很简单的事情。
但是作为响应回来之后就懵逼。一个响应回来了,找不到是谁发起的它,你说它难受不难受?难受就算了,你就不怕它随便找一个请求就返回了,当场让你懵逼。
你说响应消息是在哪儿处理的?
上篇文章专门讲过哈,说不知道的都是假粉丝:
org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#decodeBody
你看上门代码截图的第 66 行:get request id(获取请求编号)。
从哪里获取?
从 header 中获取。
header 中的请求编号是哪里来的?
发起 request 请求的时候,从 request 对象中取出来写到协议里面的。
request 对象中的请求编号是哪里来的?
通过 AtomicLong 从 0 开始自增来的。
好了,知道这个 id 是怎么来的了,也获取到了。它是在哪里用的呢?
org.apache.dubbo.remoting.exchange.support.DefaultFuture#received(org.apache.dubbo.remoting.Channel, org.apache.dubbo.remoting.exchange.Response, boolean)
标号为 ① 的地方就是根据 response 里面的 id,即调用编号从 FUTURES 这个 MAP 中移除并获取出对应的请求。
如果获取到的请求是 null,说明超时了。
如果获取到的请求不为 null,则判断是否超时了。超时逻辑我们最后再讲。
标号为 ② 地方是要把响应返回给对应的用户线程了。
在 doReceived 里面使用了响应式编程:
这的 this 就是当前类,即 DefaultFuture。
那么这个 doReceived 方法是怎么调到这里的呢?
之前的文章说过 Dubbo 默认的派发策略是 ALL,所以所有的响应都会被派发到客户端线程池里面去,也就是这个地方:
当接收到服务端的响应后,响应事件也会被扔到线程池里面,从代码中可以看到,扔进去的就是一个 Runable 任务。
然后执行了 execute 方法,这个方法就和上一小节讲请求的地方呼应上了。
还记得我们的请求是调用了 queue.take 方法,进入阻塞等待吗?
而这里就是在往 queue 里面添加任务。
队列里面有任务啦!在阻塞等待的用户线程就活过来了!
接下来用户线程怎么执行?
看代码:
取到任务后执行了任务的 run 方法。注意是 run 方法哦,并不会起新的线程。
而这个任务是什么任务?
是 ChannelEventRunnable。看一下这个任务重写的 run 方法:
这不是巧了吗,这不是?
而 handler.received 方法最终就会调用到我们前说的 doReceived 方法:
闭环完成。
所以当用户线程执行完这个 Runable 任务后,继续往下执行:
这里返回的 Result 就是最终的服务端返回的数据了,或者是返回的异常。
现在你再回过头去看官网这张图,应该就能看明白了:
超时检查
前面说 newFuture 的时候不是说它还干了一件事就是检测是否超时嘛。其实原理也是很简单:
首先有一个 TimeoutCheckTask 类,这是一个待执行的任务。
触发后会根据调用编号去 FUTURES 里面取 DefaultFuture。
前面我刚刚说了:如果一个 future 正常完成之后,会从 FUTURES 里面移除掉。
那么如果到点了,根据编号没有取到 Future 或者取到的这个 Future 的状态是 done 了,则说明这个请求没有超时。
如果这个 Future 还在 FUTURES 里面,含义就是到点了你咋还在里面呢?那肯定是超时了,调用 notifyTimeout 方法,是否超时参数给 true:
这个 received 方法全局只有两个调用的地方,一个是前面讲的正常返回后的调用,一个就是这里超时之后的调用:
也就是不论怎样,最终都会调用这个 received 方法,最终都会通过这个方法把对应调用编号的 DefaultFuture 对象从 FUTURE 这个 MAP 中移除。
上面这个任务怎么触发呢?
Dubbo 自己搞了个 HashedWheelTimer ,这是什么东西?
时间轮调度算法呀:
你发起一个请求,指定时间内没有返回结果,于是就取消(future.cancel)这个请求。
这个需求不就类似于你下单买个东西,30 分钟还没有支付,于是平台自动给你取消了订单吗?
时间轮,可以解决你这个问题。
一个 2.7.5 版本关于检查 Dubbo 超时的小知识点,送给大家。
验证编号
前面一直在强调,这个调用编号很重要。
所以为了让大家有个更加直观的认识,我截个简单的图,给大家验证一下这个编号确实是贯穿请求和响应的。
首先,改造一下我们的服务端:
最后
文章中涉及到的知识点我都已经整理成了资料,录制了视频供大家下载学习,诚意满满,希望可以帮助在这个行业发展的朋友,在论坛博客等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我把这些资料,分享出来。相信对于已经工作和遇到技术瓶颈的朋友们,在这份资料中一定都有你需要的内容。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
已经工作和遇到技术瓶颈的朋友们,在这份资料中一定都有你需要的内容。**
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-2NOUbWaO-1713365780314)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!