大家好,我是小林。
分享一篇字节后端开发校招一面经,同学反馈面试官人很 nice,虽然问的很细节,但是会引导问题方向,但是可惜自己没把握住,深问一点细节的,就不会了。
这一面主要是拷打基础方向,重点拷打了网络IO、Linux 操作系统、网络协议、mysql、算法。
项目相关
epoll 的工作原理?
先用 epoll_create 创建一个 epoll 对象 epfd,再通过 epoll_ctl 将需要监视的 socket 添加到epfd中,最后调用 epoll_wait 等待数据,当epoll_wait返回后,就可以遍历它返回的事件列表,然后根据事件类型做出相应的处理。
s socketAF_INET SOCK_STREAM binds listens epfd epoll_createepoll_ctlepfd //将所有需要监听的socket添加到epfd中 { n epoll_wait接收到数据的socket{}}
epoll、select、poll的区别?
select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。
所以,对于 select 这种方式,需要进行2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。
select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为1024,只能监听 0~1023 的文件描述符。
poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。
但是 poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。
epoll 通过两个方面,很好解决了 select/poll 的问题。
可以看到 epoll 相关的接口作用:
epoll 的方式即使监听的 Socket 数量越多的时候,效率不会大幅度降低,能够同时监听的 Socket 的数目也非常的多了,上限就为系统定义的进程打开的最大文件描述符个数。因而,epoll 被称为解决 C10K 问题的利器。
select线性表要从用户态复制到内核态,具体怎么复制的?
用户态准备一个文件描述符集合,通常是使用fd_set数据结构来表示,该集合包含要监视的文件描述符。调用select系统调用时,将该文件描述符集合作为参数传递给select函数。
内核态的select函数接收到用户态传递的文件描述符集合后,会在内核中创建一个与用户态相对应的数据结构 fdset,然后将用户空间的ufdset拷贝到内核空间fdset。
操作系统
进程、线程、协程的概念
系统创建进程的时候,会给进程分配哪些资源?
会分配虚拟内存空间、文件描述符、信号资源。
线程的资源怎么回收?
linux 线程退出有多种方式,如return,pthread_exit,pthread_cancel等;线程分为可结合的(joinable)和 分离的(detached)两种。
怎么看进程当中有哪些线程?
使用ps命令:通过在终端中运行ps -eLf命令,可以列出所有进程及其对应的线程信息。每个线程都会显示线程ID(TID)、进程ID(PID)、线程优先级(PRI)、CPU占用率(%CPU)、内存占用(%MEM)等信息。
怎么查看网络的状态?
可以通过 netstat 命令。
如果只想看close_wait状态的连接,怎么看?
netstat napt grep close_wait
计网
HTTP协议状态码 500 501 502 503 504分别代表什么?可以举出具体场景嘛?
状态码500:
状态码501:
状态码502:
状态码503 :
状态码504 :
说一说四次挥手的整个过程?
TCP 四次挥手的过程如下:
具体过程:
你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。
Time_wait 为什么2MSL ?
主要是两个原因:
原因一:防止历史连接中的数据,被后面相同四元组的连接错误的接收
假设 TIME-WAIT 没有等待时间或时间过短,被延迟的数据包抵达后会发生什么呢?
TIME-WAIT 时间过短,收到旧连接的数据报文
如上图:
为了防止历史连接中的数据,被后面相同四元组的连接错误的接收,因此 TCP 设计了 TIME_WAIT 状态,状态会持续2MSL时长,这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
原因二:保证「被动关闭连接」的一方,能被正确的关闭
在 RFC 793 指出 TIME-WAIT 另一个重要的作用是:
TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.
也就是说,TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
如果客户端(主动关闭方)最后一次 ACK 报文(第四次挥手)在网络中丢失了,那么按照 TCP 可靠性原则,服务端(被动关闭方)会重发 FIN 报文。
假设客户端没有 TIME_WAIT 状态,而是在发完最后一次回 ACK 报文就直接进入 CLOSE 状态,如果该 ACK 报文丢失了,服务端则重传的 FIN 报文,而这时客户端已经进入到关闭状态了,在收到服务端重传的 FIN 报文后,就会回 RST 报文。
TIME-WAIT 时间过短,没有确保连接正常关闭
服务端收到这个 RST 并将其解释为一个错误(Connection reset by peer),这对于一个可靠的协议来说不是一个优雅的终止方式。
为了防止这种情况出现,客户端必须等待足够长的时间,确保服务端能够收到 ACK,如果服务端没有收到 ACK,那么就会触发 TCP 重传机制,服务端会重新发送一个 FIN,这样一去一来刚好两个 MSL 的时间。
TIME-WAIT 时间正常,确保了连接正常关闭
客户端在收到服务端重传的 FIN 报文时,TIME_WAIT 状态的等待时间,会重置回 2MSL。
当存在大量close_wait的连接时怎么处理?
CLOSE_WAIT 状态是「被动关闭方」才会有的状态,而且如果「被动关闭方」没有调用 close 函数关闭连接,那么就无法发出 FIN 报文,从而无法使得 CLOSE_WAIT 状态的连接转变为 LAST_ACK 状态。
所以,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接。
那什么情况会导致服务端的程序没有调用 close 函数关闭连接?这时候通常需要排查代码。
我们先来分析一个普通的 TCP 服务端的流程:
可能导致服务端没有调用 close 函数的原因,如下。
第一个原因:第 2 步没有做,没有将服务端 socket 注册到 epoll,这样有新连接到来时,服务端没办法感知这个事件,也就无法获取到已连接的 socket,那服务端自然就没机会对 socket 调用 close 函数了。
不过这种原因发生的概率比较小,这种属于明显的代码逻辑 bug,在前期 read view 阶段就能发现的了。
第二个原因:第 3 步没有做,有新连接到来时没有调用 accpet 获取该连接的 socket,导致当有大量的客户端主动断开了连接,而服务端没机会对这些 socket 调用 close 函数,从而导致服务端出现大量 CLOSE_WAIT 状态的连接。
发生这种情况可能是因为服务端在执行 accpet 函数之前,代码卡在某一个逻辑或者提前抛出了异常。
第三个原因:第 4 步没有做,通过 accpet 获取已连接的 socket 后,没有将其注册到 epoll,导致后续收到 FIN 报文的时候,服务端没办法感知这个事件,那服务端就没机会调用 close 函数了。
第四个原因:第 6 步没有做,当发现客户端关闭连接后,服务端没有执行 close 函数,可能是因为代码漏处理,或者是在执行 close 函数之前,代码卡在某一个逻辑,比如发生死锁等等。
可以发现,当服务端出现大量 CLOSE_WAIT 状态的连接的时候,通常都是代码的问题,这时候我们需要针对具体的代码一步一步的进行排查和定位,主要分析的方向就是服务端为什么没有调用 close。
什么是聚簇索引和非聚簇索引?
InooDB 为什么要使用聚簇索引?
使用聚簇索引的一些好处:
什么是 InooDB里面的联合索引?
通过将多个字段组合成一个索引,该索引就被称为联合索引。
比如,将商品表中的 product_no 和 name 字段组合成联合索引(product_no, name),创建联合索引的方式如下:
index_product_no_name productproduct_no name
联合索引(product_no, name)的 B+Tree 示意图如下(图中叶子节点之间我画了单向链表,但是实际上是双向链表,原图我找不到了,修改不了,偷个懒我不重画了,大家脑补成双向链表就行)。
可以看到,联合索引的非叶子节点用两个字段的值作为 B+Tree 的 key 值。当在联合索引查询数据时,先按 product_no 字段比较,在 product_no 相同的情况下再按 name 字段比较。
也就是说,联合索引查询的 B+Tree 是先按 product_no 进行排序,然后再 product_no 相同的情况再按 name 字段排序。
因此,使用联合索引时,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。在使用联合索引进行查询的时候,如果不遵循「最左匹配原则」,联合索引会失效,这样就无法利用到索引快速查询的特性了。
比如,如果创建了一个(a, b, c)联合索引,如果查询条件是以下这几种,就可以匹配上联合索引:
需要注意的是,因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。
但是,如果查询条件是以下这几种,因为不符合最左匹配原则,所以就无法匹配上联合索引,联合索引就会失效:
上面这些查询条件之所以会失效,是因为(a, b, c)联合索引,是先按 a 排序,在 a 相同的情况再按 b 排序,在 b 相同的情况再按 c 排序。所以,b 和 c 是全局无序,局部相对有序的,这样在没有遵循最左匹配原则的情况下,是无法利用到索引的。
我这里举联合索引(a,b)的例子,该联合索引的 B+ Tree 如下(图中叶子节点之间我画了单向链表,但是实际上是双向链表,原图我找不到了,修改不了,偷个懒我不重画了,大家脑补成双向链表就行)。
可以看到,a 是全局有序的(1, 2, 2, 3, 4, 5, 6, 7 ,8),而 b 是全局是无序的(12,7,8,2,3,8,10,5,2)。因此,直接执行where b = 2这种查询条件没有办法利用联合索引的,利用索引的前提是索引里的 key 是有序的。
只有在 a 相同的情况才,b 才是有序的,比如 a 等于 2 的时候,b 的值为(7,8),这时就是有序的,这个有序状态是局部的,因此,执行where a = 2 and b = 7是 a 和 b 字段能用到联合索引的,也就是联合索引生效了。
查询符合最左匹配原则,可以a1 和 a2 都可以使用联合索引。
具体的查询过程,在二级索引 b+树找到符合条件 a2 和 a1 的记录,然后获取这些记录的 id 值,拿 id 值去主键索引查询 a5 列的值,这里涉及了回表的查询。
算法
滑动窗口
本网站的文章部分内容可能来源于网络和网友发布,仅供大家学习与参考,如有侵权,请联系站长进行删除处理,不代表本网站立场,转载者并注明出处:https://www.jmbhsh.com/shumazixun/35095.html