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

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

image.png


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


image.png


这个代码是干啥的?

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

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

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


image.png


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

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

第一个方法是这样的:

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


image.png


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

第二个方法是这样的:

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


image.png


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

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

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

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



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

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

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

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

org.apache.dubbo.remoting.transport.CodecSupport

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

首先是这里:



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


image.png


image.png


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

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

image.png


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

image.png


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



image.png


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

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

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

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

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

https://github.com/apache/dubbo/pull/7168


image.png

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

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

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

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

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

image.png

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

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

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

image.png

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

https://dubbo.apache.org/zh/docs/v3.0/concepts/rpc-protocol/#dubbo2

果然,发生了变化:


image.png

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

严谨,这就是严谨。

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


心跳的多种设计方案


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

https://github.com/apache/dubbo/issues/3151

标题是这样的:

image.png

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

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

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

image.png

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

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

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



image.png

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

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

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

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

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

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

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

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

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

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

image.png

目录
相关文章
|
5月前
|
算法 前端开发 JavaScript
【面试题】 面试官:你都工作3年了,这个算法题都不会?
【面试题】 面试官:你都工作3年了,这个算法题都不会?
|
1月前
|
Java 程序员
反问面试官:如何实现集群内选主
这个示例展示了多个节点通过投票选举一个新的主节点的过程。Netty 用于节点间的通信,而每个节点则负责发起和响应选举消息。
反问面试官:如何实现集群内选主
|
消息中间件 存储 网络协议
面试跳槽季惊艳面试官的回答:谈谈你对RabbitMQ工作原理的理解?
一个5年工作经验的小伙伴,在面试的时候被这样一个问题。谈谈你对RabbitMQ架构原理的理解。当时,这位小伙伴只解答说,我只会用,原理并没有关注过。那今天我给大家来分享一下我的理解。
93 1
|
消息中间件 存储 Kafka
这是面试官最想听到的回答:谈谈你对Kafka数据存储原理的理解?
一位5年工作经验的小伙伴面试的时候被问到这样一个问题,说”谈谈你对Kafka数据存储原理的理解“。然后,这位小伙伴突然愣住了,什么是零拷贝,零拷贝跟Kafka有关系吗? 那么今天,我给大家来聊一聊我对Kafka零拷贝原理的理解。
93 0
|
NoSQL 安全 Redis
分布式锁原理没搞懂,错失大厂offer
分布式锁原理没搞懂,错失大厂offer
60 0
|
Java API 调度
多线程基础知识,常被面试官挂在嘴边的问题
本篇文章主要围绕为什么使用多线程,进程、线程、管程 (monitor 监视器),,多线程并行和并发的区别,synchronized 和 lock 的区别,线程实现方式,线程的生命周期,线程同步的这部分内容进行讲解, 感兴趣的大佬戳进来
86 0
多线程基础知识,常被面试官挂在嘴边的问题
|
存储 网络协议 Java
面试官:小伙子我们先来详细的好好聊一聊NIO的三大组件
NIO是Java从JDK1.4开始引入的一系列改进版输入输出处理手段,也就是New IO,简称NIO,也有说法叫NonBlocking IO,是同步非阻塞式的IO模型,准确地说它支持阻塞非阻塞两种模式。
|
消息中间件 算法 架构师
盘点那些IT技术面试官常用的10个挂人套路
最近几个朋友找我聊天,给我讲述了面试过程中遇到的一些不太理解的事情。作为一个技术面试官,今天来分享 10 个面试相关的套路。
|
消息中间件 算法 JavaScript
面试官:谈谈分布式一致性机制,我一脸懵逼。。
面试官:谈谈分布式一致性机制,我一脸懵逼。。
|
程序员
5分钟掌握如何做面试官
5分钟掌握如何做面试官
184 0
5分钟掌握如何做面试官