面试官:要不我们聊一下“心跳”的设计? (下)

简介: 面试官:要不我们聊一下“心跳”的设计? (下)

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

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

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

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

org.apache.dubbo.remoting.transport.AbstractEndpoint


image.png


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

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

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

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

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

if transport == netty {
     don't start heartbeat timer
}

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

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

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


image.png


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

接着看看 carryxyh 老哥的观点:


image.png


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

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

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

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

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

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

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

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

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

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

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

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

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

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


image.png

然后,是这段话:

image.png

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

有点意思。


看看代码


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

首先是这里:

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


image.png

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

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

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

image.png

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

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

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

image.png

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

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

在使用 Netty4 的时候是 true。

image.png


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

那么它是怎么实现的呢?

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

关注一下它的 doOpen 方法:

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


image.png


在 pipeline 里面加入了我们前面说到的 IdleStateHandler 事件,这个事件就是如果 heartbeatInterval 毫秒内没有读写事件,那么就会触发一个方法,相当于是一个回调。

heartbeatInterval 默认是 6000,即 60s。

然后加入了 nettyClientHandler,它是干什么呢?

看一眼它的这个方法:

org.apache.dubbo.remoting.transport.netty4.NettyClientHandler#userEventTriggered



这个方法里面在发送心跳事件。

也就是说你这样写,含义是在 60s 内,客户端没有发生读写时间,那么 Netty 会帮我们触发 userEventTriggered 方法,在这个方法里面,我们可以发送一次心跳,去看看服务端是否正常。

从目前的代码来看, Dubbo 最终是采用的老徐的建议,但是默认实现还是没变,只是在 Netty4 里面采用了 IdleStateHandler 机制。

这样的话,其实我就觉得更奇怪了。

同样是 Netty,一个采用的是时间轮,一个采用的 IdleStateHandler。

同时我也很理解,步子不能迈的太大了,容易扯着蛋。


image.png


但是,在翻源码的过程中,我发现了一个代码上的小问题。

org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#decode(org.apache.dubbo.remoting.Channel, org.apache.dubbo.remoting.buffer.ChannelBuffer, int, byte[])

在上面这个方法中,有两行代码是这样的:


image.png


你先别管它们是干啥的,我就带你看看它们的逻辑是怎么样的:


image.png


可以看到两个方法都执行了这样的逻辑:

int payload = getPayload(channel);
boolean overPayload = isOverPayload(payload, size);

如果 finishRespWhenOverPayload 返回的不是 null,没啥说的,返回 return 了,不会执行 checkPayload 方法。

如果 finishRespWhenOverPayload 返回的是 null,则会执行 checkPayload 方法。

这个时候会再次做检查报文大小的操作,这不就重复了吗?

所以,我认为这一行的代码是多余的,可以直接删除。


image.png


你明白我意思吧?

又是一个给 Dubbo 贡献源码的机会,送给你,可以冲一波。

最后,再给大家送上几个参考资料。

第一个是可以去了解一下 SOFA-RPC 的心跳机制。 SOFA-PRC 也是阿里开源出来的框架。

在心跳这块的实现就是完完全全的基于 IdleStateHandler 来实现的。

可以去看一下官方提供的这两篇文章:

https://www.sofastack.tech/search/?page=1&query=%E5%BF%83%E8%B7%B3&type=all


image.png


第二个是极客时间《从0开始学微服务》,第 17 讲里面,老师在关于心跳这块的一点分享,提到的一个保护机制,这是我之前没有想到过的:


image.png


反正我是觉得,我文章中提到的这一些链接,你都去仔仔细细的看了,那么对于心跳这块的东西,也就掌握的七七八八了,够用了。

好了,就到这吧。

本文已收录至个人博客,欢迎大家来玩。

https://www.whywhy.vip/

目录
相关文章
|
3月前
|
Java 程序员
反问面试官:如何实现集群内选主
这个示例展示了多个节点通过投票选举一个新的主节点的过程。Netty 用于节点间的通信,而每个节点则负责发起和响应选举消息。
反问面试官:如何实现集群内选主
|
消息中间件 存储 网络协议
面试跳槽季惊艳面试官的回答:谈谈你对RabbitMQ工作原理的理解?
一个5年工作经验的小伙伴,在面试的时候被这样一个问题。谈谈你对RabbitMQ架构原理的理解。当时,这位小伙伴只解答说,我只会用,原理并没有关注过。那今天我给大家来分享一下我的理解。
98 1
|
7月前
|
Java 调度
金三银四面试必问:线程有几种状态
金三银四面试必问:线程有几种状态
37 0
|
算法 Java BI
Sentinel为什么这么强,我忍不住扒了扒背后的实现原理
大家好,我是三友~~ 最近我在整理代码仓库的时候突然发现了被尘封了接近两年之久的Sentinel源码库 两年前我出于好奇心扒了一下Sentinel的源码,但是由于Sentinel本身源码并不复杂,在简单扒了扒之后几乎就再没扒过了 那么既然现在又让我看到了,所以我准备再来好好地扒一扒,然后顺带写篇文章来总结一下。
Sentinel为什么这么强,我忍不住扒了扒背后的实现原理
|
消息中间件 存储 Kafka
这是面试官最想听到的回答:谈谈你对Kafka数据存储原理的理解?
一位5年工作经验的小伙伴面试的时候被问到这样一个问题,说”谈谈你对Kafka数据存储原理的理解“。然后,这位小伙伴突然愣住了,什么是零拷贝,零拷贝跟Kafka有关系吗? 那么今天,我给大家来聊一聊我对Kafka零拷贝原理的理解。
98 0
|
NoSQL 安全 Redis
分布式锁原理没搞懂,错失大厂offer
分布式锁原理没搞懂,错失大厂offer
62 0
|
消息中间件 算法 架构师
盘点那些IT技术面试官常用的10个挂人套路
最近几个朋友找我聊天,给我讲述了面试过程中遇到的一些不太理解的事情。作为一个技术面试官,今天来分享 10 个面试相关的套路。
|
存储 Prometheus 监控
腾讯二面:你们线程池是怎么做监控的?
大部分情况下,线程池的运行情况对于使用者来说是个黑盒 运行情况不可知,会导致 生产出现事故问题排查困难,以及线程池参数难以定义 文章围绕线程池监控展开,讨论 线程池如何监控、监控的指标以及监控数据的存储展示
|
消息中间件 算法 JavaScript
面试官:谈谈分布式一致性机制,我一脸懵逼。。
面试官:谈谈分布式一致性机制,我一脸懵逼。。
|
程序员
5分钟掌握如何做面试官
5分钟掌握如何做面试官
190 0
5分钟掌握如何做面试官