面试官:要不我们聊一下“心跳”的设计?,2024年最新java高级程序员面试笔试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正文

我只需要看看这两个 pr,就知道是怎么实现的“心跳绕过序列化”,这直接就让我少走了很多弯路。

首先看这个:

github.com/apache/dubb…

从这段描述中可以知道,我找到对的地方了。而从他的描述中知道“心跳跳过序列化”,就是用 null 来代替了序列化的这个过程。

同时这个 pr 里面还说明了自己的改造思路:

接着就带大家看一下这一次提交的代码。

怎么看呢?

可以在 git 上看到他对应这次提交的文件:

到源码里面找到对应地方即可,这也是一个去找源码的方法。

我比较熟悉 Dubbo 框架,不看这个 pr 我也大概知道去哪里找对应的代码。但是如果换成另外一个我不熟悉的框架呢?

从它的 git 入手其实是一个很好的角度。

一个翻阅源码的小技巧,送给你。

如果你不了解 Dubbo 框架也没有关系,我们只是聚焦于“心跳是如何跳过序列化”的这一个点。至于心跳是由谁如何在什么时间发起的,这一节暂时不讲。

接着,我们从这个类下手:

org.apache.dubbo.rpc.protocol.dubbo.DubboCodec

从提交记录可以看出主要有两处改动,且两处改动的代码是一模一样的,都位于 decodeBody 这个方法,只是一个在 if 分支,一个在 else 分支:

这个代码是干啥的?

你想一个 RPC 调用,肯定是涉及到报文的 encode(编码) 和 decode(解码) 的,所以这里主要就是对请求和响应报文进行 decode 。

一个心跳,一来一回,一个请求,一个响应,所以有两处改动。

所以我带着大家看请求包这一处的处理就行了:

可以看到代码改造之后,对心跳包进行了一个特殊的判断。

在心跳事件特殊处理里面涉及到两个方法,都是本次提交新增的方法。

第一个方法是这样的:

org.apache.dubbo.remoting.transport.CodecSupport#getPayload

就是把 InputStream 流转换成字节数组,然后把这个字节数组作为入参传递到第二个方法中。

第二个方法是这样的:

org.apache.dubbo.remoting.transport.CodecSupport#isHeartBeat

从方法名称也知道这是判断请求是不是心跳包。

怎么去判断它是心跳包呢?

首先得看一下发起心跳的地方:

org.apache.dubbo.remoting.exchange.support.header.HeartbeatTimerTask#doTask

从发起心跳的地方我们可以知道,它发出去的东西就是 null。

所以在接受包的地方,判断其内容是不是 null,如果是,就说明是心跳包。

通过这简单的两个方法,就完成了心跳跳过序列化这个操作,提升了性能。

而上面两个方法都是在这个类中,所以核心的改动还是在这个类,但是改动点其实也不算多:

org.apache.dubbo.remoting.transport.CodecSupport

在这个类里面有两个小细节,可以带大家再看看。

首先是这里:

这个 map 里面缓存的就是不同的序列化的方式对应的 null,代码干的也就是作者这里说的这件事儿:

另外一个细节是看这个类的提交记录:

还有一次优化性的提交,而这一次提交的内容是这样的。

首先定义了一个 ThreadLocal,并使其初始化的时候是 1024 字节:

那么这个 ThreadLocal 是用在哪儿的呢?

在读取 InputStream 的时候,需要开辟一个字节数组,为了避免频繁的创建和销毁这个字节数据,所以搞了一个 ThreadLocal:

有的同学看到这里就要问了:为什么这个 ThreadLocal 没有调用 remove 方法呢,不会内存泄漏嘛?

不会的,朋友们,在 Dubbo 里面执行这个玩意的是 NIO 线程,这个线程是可以复用的,且里面只是放了一个 1024 的字节数组,不会有脏数据,所以不需要移除,直接复用。

正是因为可以复用,所以才提升了性能。

这就是细节,魔鬼都在细节里面。

这一处细节,就是前面提到的另外一个 pr:

github.com/apache/dubb…

看到这里,我们也就知道了宇宙行到底是怎么让心跳跳过序列化操作了,其实也没啥复杂的代码,几十行代码就搞定了。

但是,朋友们,又要但是了。

写到这里的时候,我突然感觉到不太对劲。

因为我之前写过这篇文章,Dubbo 协议那点破事

在这篇文章里面有这样的一个图:

这是当时在官网上截下来的。

在协议里面,事件标识字段之前只有 0 和 1。

但是现在不一样了,从代码看,是把 1 的范围给扩大了,它不一定代表的是心跳,因为这里面有个 if-else

所以,我就去看了一下现在官网上关于协议的描述。

dubbo.apache.org/zh/docs/v3.…

果然,发生了变化:

并不是说 1 就是心跳包,而是改口为:1 可能是心跳包。

严谨,这就是严谨。

所以开源项目并不是代码改完就改完了,还要考虑到一些周边信息的维护。

心跳的多种设计方案


在研究 Dubbo 心跳的时候,我还找到了这样一个 pr。

github.com/apache/dubb…

标题是这样的:

翻译过来就是使用 IdleStateHandler 代替使用 Timer 发送心跳的建议。

我定睛一看,好机会,这不是 95 后老徐嘛,老熟人了。

看一下老徐是怎么说的,他建议具体是这样的:

几位 Dubbo 大佬,在这个 pr 里面交换了很多想法,我仔细的阅读之后都受益匪浅。

大家也可以点进去看看,我这里给大家汇报一下自己的收获。

首先是几位老哥在心跳实时性上的一顿 battle。

总之,大家知道 Dubbo 的心跳检测是有一定延时的,因为是基于时间轮做的,相当于是定时任务,触发的时效性是不能保证实时触发的。

这玩意就类似于你有一个 60 秒执行一次的定时任务,在第 0 秒的时候任务启动了,在第 1 秒的时候有一个数据准备好了,但是需要等待下一次任务触发的时候才会被处理。因此,你处理数据的最大延迟就应该是 60 秒。

这个大家应该能明白过来。

额外说一句,上面讨论的结果是“目前是 1/4 的 heartbeat 延时”,但是我去看了一下最新的 master 分支的源码,怎么感觉是 1/3 的延时呢:

从源码里可以看到,计算时间的时候 HEARTBEAT_CHECK_TICK 参数是 3。所以我理解是 1/3 的延时。

但是不重要,这不重要,反正你知道是有延时的就行了。

而 kexianjun 老哥认为如果基于 netty 的 IdleStateHandler 去做,每次检测超时都重新计算下一次检测的时间,因此相对来说就能比较及时的检查到超时了。

这是在实时性上的一个优化。

而老徐觉得,除了实时性这个考虑外,其实 IdleStateHandler 更是一个针对心跳的优雅的设计。但是呢,由于是基于 Netty 的,所以当通讯框架使用的不是 Netty 的时候,就回天无力了,所以可以保留 Timer 的设计来应对这种情况。

很快,carryxyh 老哥就给出了很有建设性的意见:

由于 Dubbo 是支持多个通讯框架的。

这里说的“多个”,其实不提我都忘记了,除了 Netty 之外,它还支持 Girzzly 和 Mina 这两种底层通讯框架,而且还支持自定义。

但是我寻思都 2021 年了,Girzzly 和 Mina 还有人用吗?

从源码中我们也能找到它们的影子:

org.apache.dubbo.remoting.transport.AbstractEndpoint

Girzzly、Mina 和 Netty 都各有自己的 Server 和 Client。

其中 Netty 有两个版本,是因为 Netty4 步子迈的有点大,难以在之前的版本中进行兼容,所以还不如直接多搞一个实现。

但是不管它怎么变,它都还是叫做 Netty。

好了,说回前面的建设性意见。

如果是采用 IdleStateHandler 的方式做心跳,而其他的通讯框架保持 Timer 的模式,那么势必会出现类似于这样的代码:

if transport == netty {

don’t start heartbeat timer

}

复制代码

这是一个开源框架中不应该出现的东西,因为会增加代码复杂度。

所以,他的建议是最好还是使用相同的方式来进行心跳检测,即都用 Timer 的模式。

正当我觉得这个哥们说的有道理的时候,我看了老徐的回答,我又瞬间觉得他说的也很有道理:

我觉得上面不需要我解释了,大家边读边思考就行了。

接着看看 carryxyh 老哥的观点:

这个时候对立面就出现了。

老徐的角度是,心跳肯定是要有的,只是他觉得不同通讯框架的实现方式可以不必保持一致(现在都是基于 Timer 时间轮的方式),他并不认为 Timer 抽象成一个统一的概念去实现连接保活是一个优雅的设计。

在 Dubbo 里面我们主要用的就是 Netty,而 Netty 的 IdleStateHandler 机制,天生就是拿来做心跳的。

所以,我个人认为,是他首先觉得使用 IdleStateHandler 是一种比较优雅的实现方式,其次才是时效性的提升。

但是 carryxyh 老哥是觉得 Timer 抽象的这个定时器,是非常好的设计,因为它的存在,我们才可以不关心底层是netty还是mina,而只需要关心具体实现。

而对于 IdleStateHandler 的方案,他还是认为在时效性上有优势。但是我个人认为,他的想法是如果真的有优势的话,我们可以参考其实现方式,给其他通讯框架也赋能一个 “Idle” 的功能,这样就能实现大统一。

看到这里,我觉得这两个老哥 battle 的点是这样的。

首先前提是都围绕着“心跳”这个功能。

一个认为当使用 Netty 的时候“心跳”有更好的实现方案,且 Netty 是 Dubbo 主要的通讯框架,所以应该可以只改一下 Netty 的实现。

一个认为“心跳”的实现方案应该统一,如果 Netty 的 IdleStateHandler 方案是个好方案,我们应该把这个方案拿过来。

我觉得都有道理,一时间竟然不知道给谁投票。

但是最终让我选择投老徐一票的,是看了他写的这篇文章:《一种心跳,两种设计》

这篇文章里面他详细的写了 Dubbo 心跳的演变过程,其中也涉及到部分的源码。

最终他给出了这样的一个图, 心跳设计方案对比:

然后,是这段话:

老徐是在阿里搞中间件的,原来搞中间件的人每天想的是这些事情。

有点意思。

看看代码


带大家看一下代码,但是不会做详细分析,相当于是指个路,如果想要深入了解的话,自己翻源码去。

首先是这里:

org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeClient

可以看到在 HeaderExchangeClient 的构造方法里面调用了 startHeartBeatTask 方法来开启心跳。

同时这里面有个 HashedWheelTimer,这玩意我熟啊,时间轮嘛,之前分析过的。

然后我们把目光放在这个方法 startHeartBeatTask:

这里面就是构建心跳任务,然后扔到时间轮里面去跑,没啥复杂的逻辑。

这一个实现,就是 Dubbo 对于心跳的默认处理。

但是需要注意的是,整个方法被 if 判断包裹了起来,这个判断可是大有来头,看名字叫做 canHandleIdle,即是否可以处理 idle 操作,默认是 false:

所以,前面的 if 判断的结果是 true。

那么什么情况下 canHandleIdle 是 true 呢?

在使用 Netty4 的时候是 true。

也就是 Netty4 不走默认的这套心跳实现。

那么它是怎么实现的呢?

由于服务端和客户端的思路是一样的,所以我们看一下客户端的代码就行。

关注一下它的 doOpen 方法:

org.apache.dubbo.remoting.transport.netty4.NettyClient#doOpen

总结

虽然我个人也经常自嘲,十年之后要去成为外卖专员,但实际上依靠自身的努力,是能够减少三十五岁之后的焦虑的,毕竟好的架构师并不多。

架构师,是我们大部分技术人的职业目标,一名好的架构师来源于机遇(公司)、个人努力(吃得苦、肯钻研)、天分(真的热爱)的三者协作的结果,实践+机遇+努力才能助你成为优秀的架构师。

如果你也想成为一名好的架构师,那或许这份Java成长笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

org.apache.dubbo.remoting.transport.netty4.NettyClient#doOpen

总结

虽然我个人也经常自嘲,十年之后要去成为外卖专员,但实际上依靠自身的努力,是能够减少三十五岁之后的焦虑的,毕竟好的架构师并不多。

架构师,是我们大部分技术人的职业目标,一名好的架构师来源于机遇(公司)、个人努力(吃得苦、肯钻研)、天分(真的热爱)的三者协作的结果,实践+机遇+努力才能助你成为优秀的架构师。

如果你也想成为一名好的架构师,那或许这份Java成长笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。

[外链图片转存中…(img-xMknRZH4-1713184638241)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-20i4xBhI-1713184638241)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值