java学习

Java面试系列之记一次小红书之旅

2020-10-22  本文已影响0人  程序员麦冬
大纲

一面


一面面试官看着二十七八岁,文质彬彬,这哪里是写代码的,头发都飘起来了好么。上来就干项目,由于大家的项目都不太一样,所以对于项目部分我就说说我面试的时候经常遇到的问题

一口是吃不了胖子的,描述之前先憋着气掂量掂量自己所说的东西能不能唬住自己,然后唬住面试官。

对于大多数的我们而言,就是开发的角色,同样的道理,角色对应相应的职务,阐述自己做的内容能引面试官上钩,拉钩上吊一百年不许变。

这三个问题,是不是可以拎着脚趾拇都可以想出来,除非不是你做的,哈哈哈哈哈。不慌,不是我们做的也不怕,我们必须知道有个网站叫做Github,大牛这么多,自己不是大牛,难道不会学学人家麦。Clone下来,搭建环境跑起来,开始调试修改,通过将模块拆分,进一步修改,这不就是你的项目吗,当然我不怎么建议大家这么操作啦。

项目被问的差不多了,开始怼基础知识,基础知识老四套,计算机网络,数据库,操作系统,数据结构

我看你简历中写着网络流量的还原,你应该对计算机网络比较熟悉?(注意哈,简历上写上去的东西,自己心里一定要有点B数),那我们说说计算机网络

首先 Client 给 Server 发送一个SYN包,Server 接收到 SYN 回复 SYN+ACK,然后客户端回复ACK 表示收到。

你这样回答肯定是不会让面试官满意的,那就加点配料,不放佐料的菜怎么香?那就详细的安排一下

首先客户端的协议栈向服务端发送SYN包,同时告诉服务端当前发送的序列号是X,此时客户端进入 SYNC_SENT状态

服务端的协议栈收到这个包以后,使用 ACK 应答,此时应答的值为 X+1,表示对 SYN 包 J 的确认,同时服务端也发送一个SYN包,告诉客户端当前我的发送序列号是Y,此时服务端进入SYNC_RCVD状态

客户端协议栈收到 ACK 以后,应用程序通过connect调用表示服务端的单向连接成功,此时状态为ESTABLISHED,同时客户端协议栈对服务器端的 SYN 进行应答,此时数据为Y+1

服务端收到客户端的应答包,通过accept阻塞调用返回,此时服务端到客户单的单向连接也建立成功,服务器将进入ESTABLISHED状态

这样是不是稍微有B格一点呢,而且还比较形象,当然为了加深大家对这个过程的印象,我再举个例子

第一次握手:小蓝给某女娃告白,说我喜欢你,然后我傻乎乎的等着回应

第二次握手:女生看我这颜值,秒回,自然就答应我啊,并回复我也喜欢你拉

第三次握手:我收到女生的回应说:“那晚上去吃火锅,看电影,理疗”

就这样在一起啦,那么后续是啥样呢?是不是得往下看看什么是四次挥手了(渣男石锤),非也,还在热恋期呢,专一的好吗。面试官会继续问你三次握手

面试官说:“那我问你,如果客户端发送的SYN丢失了或者其他原因导致Server无法处理,是什么原因?

这个场景非常常见,没有万无一失。在TCP的可靠传输中,如果SYN包在传输的过程中丢失,此时Client段会触发重传机制,但是也不是无脑的一直重传过去,重传的次数是受限制的,可以通过 tcp_syn_retries 这个配置项来决定。如果此时 tcp_syn_retries 的配置为3,那么其过程如下

TCP重传

当 Client 发送 SYN 后,如果过了1s还没有收到 Server 的回应,那么进行第一次的重传。如果经过了2s没有收到Sever的响应进行第二次的重传,一直重传tcp_syn_retries次。这里的重传三次,意味着当第一次发送SYN后,需要等待(1 +2 +4 +8)秒,如果还是没有响应,connect就会通过ETIMEOUT的错误返回。

说说四次挥手吧,哎,卑微的蓝蓝

第一次挥手:女生觉得和这个男生不太合适,但是是个好人,决定提出分手,等待男生回应

第二次挥手:这男生吧,也是会玩儿,直接说:”分就分“

第三次挥手:过了一段时间,男生觉得好没得面子:"我一个大老爷们,应该是我提出分手啊",于是给女生说:我们分手吧

第四次挥手:女生看到这个消息,你是「憨批」还是「神经病」?

TIMEWAIT了解哈,过多的TIMEWAIT怎么办,什么原因造成的?

回答问题的方法无外乎即是什么,为什么会出现以及可以解决的方案

在TCP的四次挥手过程中,发起连接断开的一方会进入TIME_WAIT的状态。通常一个TCP连接通过对外开发端口的方式提供服务,在高并发的情况下,每个连接占用一个端口,但是端口是有限的以致于可能导致端口耗尽,所以就会出现'"服务时而好时而坏的情况"。

如下图所示的TCP四次挥手,TCP连接准备终止的时候会发送FIN报文,主机2进入CLOSE_WAIT状态并发送ACK应答。主机1会在TIMEWAIT停留2MSL的时间。

为什么不直接进入CLOSE转态,而是需要先等待2MSL,这段时间在干啥?

第一个原因是为了确保最后的ACK能够正常接收,从而有效的正常关闭。怎么理解,科学家们在设计TCP的时候,假设TCP报文会出错从而开始重传,如果主机1的报文没有传输成功,那么主机2就会重发FIN报文,此时主机1没有维护TIME_WAIT状态,就会失去上下文从而恢复RST,导致服被动关闭一方出现错误。

四次挥手

第二个原因是让旧链接的重复分节在网络中自然消失。

一次网络通信可能经过无数个路由器,交换机,不知道到底会是哪个环节出问题。我们为了标识一个连接,通过四元组的方式[源IP,源端口,目的IP,目的端口]。假设此时两个连接A,B。A连接在中途中断了,此时重新创建B连接,这个B连接的四元组和A连接一样,如果A连接经过一段时间到达了目的地,那么B连接很有可能被认为是A连接的一部分,这样就会造成混乱。所以TCP设置了这样一个机制,让两个方向的分组都被丢弃。

那么TIME_WAIT有哪些危害?

过多的连接势必造成内存资源的浪费

对端口的占用。可开启的端口也就32768~61000

有没有对TCP进行优化过

开玩笑,这东西复习过,尽管问,锤子不怕。优化的点很多,随便提一点,让后比较深的描述下这个过程就行比如调整哪些参数在某些特定的条件下会最优

我们应该都知道半连接,即收到SYN以后没有回复SYN+ACK的连接,那么Server每收到新的SYN包,都会创建一个半连接,然后将这个半连接加入到半连接的队列(syn queue)中,syn queue的长度又是有限的,可以通过tcp_max_syn_backlog进行配置,当队列中积压的半连接数超过了配置的值,新的SYN包就会被抛弃。对于服务器而言,可能瞬间多了很多新的连接,所以通过调大该值,以防止SYN包被丢弃而导致Client收不到SYN+ACK。

就这样是不是就可以让面试官感觉,这小伙子有点东西。那怎么配置呢

配置syn queue

你以为面试官是傻子?当然不是,万一面试官问你:半连接积压较多,还有其他的原因?

哈哈哈,这说明面试官上钩了哇,来,我们看看还有啥原因

还有可能是因为恶意的Client在进行SYN Flood攻击。

SYN Flood攻击是个啥过程?

首先Client以较高频率发送SYN包,且这个SYN包的源IP不停的更换,对于Server来说,这是新的链接,就会给它分配一个半连接

Server的SYN+ACK会根据之前的SYN包找IP,发现不是原来的IP,所以无法收到Client的ACK包,从而导致无法正确的建立连接,自然就让Server的半连接队列耗尽,无法响应正常的SYN包

那有没有什么方案解决这个问题?

那必须,毕竟面试嘛,需要让面试官问我们知道的内容。在Linux内核中引入了SYN Cookies机制,那看看这个机制是啥意思

首先Server收到SYN包,不分配资源保存Client的信息,而是根据SYN计算出Cookie值,然后将Cookie记录到SYN ACK并发送出去

如果是正常的情况,这个Cookies值会伴随着Client的ACK报文带回来

Server会根据这个Cookies检查ACK包的合法性,合法则创建连接

那么开启SYN Cookies的方法?

SYN Cookies

网络问到这就差不多了,挺好的,完全按照我的套路出牌。开始怼我操作系统

我擦,我差点没反应过来,"大爷内存",不过确实牛逼,大页内存,记住了,是大页内存。

我们知道操作系统堆内存的管理采用多级页表和分页进行管理,操作系统给每个页的默认大小是4KB。假设当前进程使用的内存比较大为1GB,那么此时在页表中会占用1GB/4KB=26211个页表项,但是系统的TLB可容乃的页表项远远小于这个数量。所以当多个内存密集型应用访问内存的时候,就会导致过多的TLB没有命中,因此在特定的情况下会需要减少未命中次数,一个可行的办法即是增大每个页的尺寸。

操作系统默认支持的大页为2MB,当使用1GB内存的时候,页表将占用512页表项,大大的提高TLB命中率从而提高性能。另外需要注意的是,大页内存分配的是物理内存,所以不会有换出磁盘的操作,所以没有缺页中断,也就不会引入访问磁盘的时延。

行,差不多时间了,写个简单代码吧,实现一个无重复字符的最长子串

思路:使用滑动窗口保证每个窗口的字母都是唯一的

无重复字符的最长子串

二面

一面感觉还不错,果不其然二面来了,HR小姐姐打电话通知周三二面,行,对于从来不迟到的暖蓝,肯定守时。拿着茶,等到2:30,至于为什么拿着茶,这是我的习惯,面试前喝杯茶等待面试官的捧击(面试官其实大部分很温柔的啦)。

可耐,面试官到点了居然还没来,等不及的我打电话给HR,HR说不好意思,得等几分钟,行,对这甜美的声音我忍了,可是等了十分钟都没音信,我下午还有个笔试,无奈给HR说,我下午还有事儿,要不改天面?

不知道什么情况,直接说,我马上给你换个面试官,我的天呐,还有这种事儿,我这乡卡卡的娃儿有这种的待遇?是我一面表现的太太突出?不会吧,反正小红书我爱了。

“staty with me”响起,这正是我的手机铃声。。

"您好”

“你好,请问是XX?”

"嗯嗯,你好面试官"

"我是你的二面面试官,先自我介绍吧"

我叫XX,来自XX大学,本科XX,硕士XXX,期间做了XX,谢谢面试官。自我介绍不用那么花里胡哨,挑重点说,不会在意你本科谈了几次恋爱,也不会在意你XXXX,简单明了完事,开始二面

至于这个题,我还是比较惊讶的,怎么突然问到了C,想了想可能还是考虑对于面向对象中多态,继承等的理解。

多态无外乎就是编译时多态和运行时多态,编译时多态理解为重载,运行时多态理解为重写。那么要实现重载,需要用到c中的宏,V_ARGS。

c实现重载

理解上面的方法,实现多态就更轻松了

c实现多态

感觉没啥问的,先写个代码,二路归并

这是我之前说过的常考算法之一,中心思想即分治,可通过递归一直拆分,递归的结束条件即不可再分,即分为1个的时候就停止。从第一个开始时将每一个模块当作一个已经排序好的数组,有如双指针,在两个数组头设立指针,进行值的比较,然后插入到新数组中,上代码咯

归并排序

倒排索引了解不?

假设我这里有几十本文档,每个文档题目不一样,如果我给你文档的题目,你可能很快就可以找到相应的文档。但是如果我让你找论文中包含"暖"和“蓝”这两个字,你可能直接给我"两儿巴“。因为多半很难很快就找出来。从稍微专业的角度来分,前一种是正排索引,后一个是倒排索引。

我们先看简单的正排索引。此时给每个文档一个唯一ID,然后使用哈希表将文档的ID作为键,将文档内容作为键对应的值。这样我们就可以在O(1)的时间代价完成key的检索。这也正是正排索引

正排索引

这里遍历哈希表的时间代价为O(n)。每遍历一个文档都需要遍历每个字符判断是否包含两字。假设每个文档的平均长度为k,那么遍历一个文档的时间代价为O(K)。

有没有什么优化的方法?

其实以上就是两种方案,一种是根据题目找到内容,另一种是根据关键字查找题目。这完全相反的方案,那我们反着试试

我们将关键字当做key,将包含这个关键字的文档的列表当做存储的内容。同样建立一个哈希表,在O(1)的时间我就可以找到包含该关键字的文档列表。这种根据内容或者字段反过来的索引结构即倒排索引。

如何创建倒排索引?

创建倒排索引

如果要查询同时包含"暖"“蓝”两个key怎么办?

顺藤摸瓜啦,分别用两个key去倒排索引中检索,这样使用的两个不同list:A和B。A中的文档都包含了"暖"字,B中的文档都包含了"蓝"字。如果文档即出现"暖"也出现"蓝",是不是就正好包含了两个字?所以只需要找到AB公共元素即可

如何找到AB两个链表的公共元素?希望小伙伴们思考下,经常在手撕算法中被问到

链表公共元素

你说使用过kafka,那么使用消息队列的时候如何保证只消费一次?

首先引入kafka等消息队列是为了对峰值写流量做削峰填谷,对不同系统做解耦合。

举个例子,我们开发了一个电商系统,其中一个功能是当用户购买了A产品5份就送一个红包从而鼓励用户消费。但是如果在消息传递的过程中丢失了,用户很可能会因为没有收到红包而不开心,甚至取消订单,在这里如何保证消息被消费到且一次?

我们先看看这个消息写入消息队列会有几个阶段,首先有消息从生产者写入消息到队列,消息存储在消息队列,消息被消费者消费这个阶段,任何一个阶段都有可能丢失,我们分别查看这几个阶段

丢失的三种可能

第一个阶段:消息生产

消息的生产通常会是业务服务器,业务服务器和独立部署的消息队列服务器通过内网通信,很可能因为网络抖动导致消息的丢失,这样可以采用消息重传的机制保证消息的送达。但是容易出现重复消费的情况,意思收到两个红包,用户开心了,但是。。。

第二个阶段:在队列中丢失

kafka为了减少消息存储对磁盘的随机IO,采用的异步刷盘的方式将消息存储在磁盘中。

我看你简历上打过acm,说说你的策略或者经历吧

写个验证邮箱的正则

当时没有写出来,确实记不住,每次都是用的时候才去查,谁知道面试的时候遇见谁呢,手撕KMP?这里给大家个答案,后续我详细安排一篇正则的套路

实现验证邮箱的正则

了解内存映射?说说,尽量说

既然是尽量说,就不客气了。从什么是内存到如何查看服务器内存,最后怎么能够更好地用好内存来答就完事

首先内存作为存储系统和应用程序的指令,数据等。在Linux中,管理内存使用到了内存映射。平时我们经常说的内存容量,主要指的是物理内存,也叫做主存。只有内核才能直接访问,那么问题来了,进城如果要访问内存怎么办呢?

Linux内核为每个进程提供了一个虚拟地址空间且空间地址连续,这样的话,进程访问虚拟内存将非常的方便。

虚拟地址又分为内核空间和用户空间,不同字长的处理器地址范围也不同。我们下面分别看看32位和64位的虚拟地址空间

内核空间与用户空间

从这个图很明显的看出32位系统中内核空间1G,而64位的内核空间与用户空间都是128T。

内存映射即虚拟内存地址映射到物理内存地址,完了顺利完成映射,需要给每个进程维护一张页表记录两者的关系。

Java面试系列之记一次小红书之旅

虚拟地址到物理地址的转化

这样,如果进程访问的虚拟地址不在则通过缺页异常进入内核空间分配物理内存,更新进程页表,最终返回用户空间。

说到虚拟内存又不得不说说用户空间的各个段

用户空间各个段

忍不住悄悄咪咪问了下HR,二面面试官对我的评价,基础和code的能力不错,项目讲述的不清楚

三面

三面面试官,真的不能用"秃"来描述了,就感觉我的眼睛被闪了一分钟,怎么说,面嘛

线程的锁有哪些,我说到了读写锁打断我了,问我读写锁会有什么些问题,无非就是写锁饥饿问题,我说没看过内核源码,然后如果让我来实现,我怎么来避免

分布式Hash表,当进行扩容的时候(会花费很长的时间),我说P肯定一定要保证的,CA只能选其一,但是我们可以使用弱一致性来保证其可用性

多个随机Request请求,然后不同的请求有不同的权重,进行随机抽样,要求权重大更可能被抽到

有了解过RPC?

RPC翻译过来为远程过程调用。帮助我们屏蔽网络细节,实现调用远程方法就跟调用本地一样的体验。举个例子,如果没有桥,我们要过河只好划船,绕道等方式,如果有桥,我们就像在路面行走一样自如到目的地。

RPC的通信流程是怎样的?

刚才说RPC屏蔽了网络细节,也就是意味着它处理好了网络部分,它为了保证可靠性,默认采用TCP传输,网络传输的数据是二进制,但是调用所请求的参数是对象,所以需要将对象转换为二进制,这就需要用到序列化技术

服务提供方接收到数据以后,并不知道哪里是结尾,所以需要一些边界条件来标识请求的数据哪里是开始,哪里是结束,就像高速路上各种指路牌引领我们前进的方向。这种格式的约定叫做“协议”

根据协议规定的格式,就可以正确的提取出相应的请求,根据请求的类型和序列化的类型,将二进制消息体逆向还原为请求对象,这叫做反序列化

服务提供方通过反序列化的对象找到对应的实现类完成整正的调用,这样就是一次rcp的调用。画个图加深下印象

RPC过程

其他问的一些问题感觉在前面的面试问过了就没有写在这部分内容了,还问了几个数据库的问题,很常规的了,之前的文章写过,篇幅太长,看着累,要不先三连,我们下期再见?么么哒

总结

请记下以下几点:

最后

感谢大家看到这里,如果本文有什么不足之处,欢迎多多指教;如果你觉得对你有帮助,请给我点个赞。

也欢迎大家关注我的公众号:程序员麦冬,每天更新行业资讯!

上一篇下一篇

猜你喜欢

热点阅读