从今天起,豆芽有空也尽己所能,帮助一下大家。

面经来源:https://www.nowcoder.com/discuss/692614?source_id=discuss_experience_nctrack&channel=-1


1. select和epoll的区别

  1. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大;而epoll保证了每个fd在整个过程中只会拷贝一次。

  2. 每次调用select都需要在内核遍历传递进来的所有fd;而epoll只需要轮询一次fd集合,同时查看就绪链表中有没有就绪的fd就可以了。

  3. select支持的文件描述符数量太小了,默认是1024;而epoll没有这个限制,它所支持的fd上限是最大可以打开文件的数目,这个数字一般远大于2048。


2. 水平出发边缘触发?边缘触发请求会处理不及时吗?

epoll水平触发与边缘触发的区别

  1. LT模式(水平触发)下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作;

  2. 而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。

如果用户一次没有读完数据,再次请求时,不会立即返回,需要等待下一次的新的数据到来时才会返回,这次返回的内容包括上次未取完的数据,这就造成了处理不及时。


3. 内存池写过吗?STL中的内存池?

内存池也是一种对象池,我们在使用内存对象之前,先申请分配一定数量的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。当不需要此内存时,重新将此内存放入预分配的内存块中,以待下次利用。这样合理的分配回收内存使得内存分配效率得到提升。

没有写过。(不给面试官任何机会)

STL内存管理

  1. 第一级配置器
    第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重新配置等操作,并且能在内存需求不被满足的时候,调用一个指定的函数。

  2. 第二级配置器
    在STL的第二级配置器中多了一些机制,避免太多小区块造成的内存碎片,小额区块带来的不仅是内存碎片,配置时还有额外的负担。区块越小,额外负担所占比例就越大。

    如果要分配的区块大于128bytes,则移交给第一级配置器处理。

    如果要分配的区块小于128bytes,则以 内存池管理(memory pool),又称之次层配置(sub-allocation):每次配置一大块内存,并维护对应的16个空闲链表(free-list)。下次若有相同大小的内存需求,则直接从free-list中取。如果有小额区块被释放,则由配置器回收到free-list中。


4. 介绍redis,持久化?数据结构?

介绍redis

Redis 是一个基于内存的高性能key-value数据库。

  1. 整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。

  2. 因为是纯内存操作,Redis的速度快。支持丰富数据类型,如string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。支持事务。

Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

持久化

Redis是一种高级key-value数据库。它跟memcached类似,不过数据可以持久化。Redis的所有数据都是保存在内存中,然后不定期的通过异步方式保存到磁盘上。redis提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)。

RDB是定期将数据写入磁盘,当redis出现故障时,有一部分内存数据肯定会丢失。而AOF是以日志的方式追加,数据丢失会少很多。对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大,数据恢复慢一些。

  1. 不要仅仅使用 RDB,因为那样会导致你丢失很多数据

  2. 也不要仅仅使用 AOF,因为那样有两个问题,第一,你通过 AOF 做冷备,没有 RDB 做冷备,来的恢复速度更快; 第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug。

  3. redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。

数据类型

如string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。


5. tcp/udp、http协议?

TCP与UDP的区别

(1)TCP需要建立一对一稳定连接;UDP无连接

(2)TCP一对一;UDP可以一对一、一对多、多对多

(3)TCP可靠传输,序列号、确认应答、超时重传;UDP不保证可靠传输,尽最大努力交付

(4)TCP头部字节20字节;UDP8个字节

(5)TCP开销大;UDP灵活开销小

(6)TCP提供可靠的服务,适用于通讯质量要求高的场景;UDP传输效率高,适用于高速传输和实时性要求的场景。

http

HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。


6. 三次握手四次挥手状态

三次握手

  1. Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,等待Server确认。
  2. Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置1,ack=J+1,随机产生一个值seq=K,并将该数据包发给Client以确认连接请求。
  3. Client收到确认后,检测ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server。完成三次握手,随后Client与Server之间可以开始传输数据了。

四次挥手

  1. 数据传输结束后,Client的应用进程发出连接释放报文段FIN,并停止发送数据,此时Client依然可以接收Server发送来的数据。
  2. Server接收到FIN后,发送一个ACK给Client,确认序号为收到的序号+1。
  3. 当Server没有数据要发送时,Server发送一个FIN报文,等待Client的确认。
  4. Client收到Server的FIN报文后,给Server发送一个ACK报文,确认序列号为收到的序号+1。此时Client进入TIME_WAIT状态,等待2MSL(MSL:报文段最大生存时间),然后关闭连接。

7. Time_wait,为什么是2MSL

这是为了保证客户端发送的最后一个ACK报文段能够到达服务器。如果客户端不等待2MSL,这个ACK报文段可能丢失,因而使得处在LAST-ACK状态的服务器收不到ACK报文段的确认,导致服务器无法正常关闭。而如果客户端等待2MSL,服务器就会超时重传FIN报文段,而客户端就能在2MSL时间内收到这个重传的FIN-ACK报文段。接着客户端重传一个确认,重新启动2MSL计时器。当服务器收到最后一个ACK后就可以正常关闭了。

2MSL的意义是,经过2MSL后,所有的报文都会消失,不会影响下一次连接。最后客户端和服务器端都能正常进入到CLOSED状态。


8. tcp定时器

TCP使用四种定时器(Timer,也称为“计时器”):

  1. 重传计时器:Retransmission Timer:为了控制丢失的报文段或丢弃的报文段,也就是对报文段确认的等待时间。当TCP发送报文段时,就创建这个特定报文段的重传计时器,可能发生两种情况:若在计时器超时之前收到对报文段的确认,则撤销计时器;若在收到对特定报文段的确认之前计时器超时,则重传该报文,并把计时器复位;

  2. 坚持计时器:Persistent Timer:专门为对付零窗口通知而设立的。当发送端收到零窗口的确认时,就启动坚持计时器,当坚持计时器截止期到时,发送端TCP就发送一个特殊的报文段,叫探测报文段,这个报文段只有一个字节的数据。探测报文段有序号,但序号永远不需要确认,甚至在计算对其他部分数据的确认时这个序号也被忽略。探测报文段提醒接收端TCP,确认已丢失,必须重传。

  3. 保活计时器:Keeplive Timer:每当服务器收到客户的信息,就将keeplive timer复位,超时通常设置2小时,若服务器超过2小时还没有收到来自客户的信息,就发送探测报文段,若发送了10个探测报文段(没75秒发送一个)还没收到响应,则终止连接。

  4. 时间等待计时器:Time_Wait Timer:在连接终止期使用,当TCP关闭连接时,并不认为这个连接就真正关闭了,在时间等待期间,连接还处于一种中间过度状态。这样就可以时重复的fin报文段在到达终点后被丢弃,这个计时器的值通常设置为一格报文段寿命期望值的两倍。


9. 零窗死锁?恢复过程?(个人理解,仅作参考)

零窗死锁:当接收端向发送端发送零窗口报文段后不久,接收端的接收缓存又有了一些存储空间,于是接收端向发送端发送了Windows size = 2的报文段,然而这个报文段在传输过程中丢失了。发送端一直等待收到接收端发送的非零窗口的通知,而接收端一直等待发送端发送数据,这样就死锁了。

解决办法:TCP为每个连接设有一个持续计时器。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器,若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。


10. get和post区别

  1. get将数据放在url后面,post将数据放在报文体

  2. url长度会受到特定的浏览器及服务器的限制,如IE对URL长度的限制是2083字节(2K+35)。而报文体长度没有限制

  3. get将数据放在url后面,信息并不安全;post方法将数据放在报文体中,更安全。


11. 进程线程调度方法

  1. 先来先服务调度算法

  2. 短作业(进程)优先调度算法

  3. 高优先级优先调度算法

  4. 时间片轮转法

  5. 多级反馈队列调度算法


12. 多线程多进程区别?分别用在什么场景?

线程与进程的区别

(1)一个线程从属于一个进程;一个进程可以包含多个线程。

(2)一个线程挂掉,对应的进程挂掉;一个进程挂掉,不会影响其他进程。

(3)进程是系统资源调度的最小单位;线程CPU调度的最小单位。

(4)进程系统开销显著大于线程开销;线程需要的系统资源更少。

(5)进程在执行时拥有独立的内存单元,多个线程共享进程的内存,如代码段、数据段、扩展段;但每个线程拥有自己的栈段和寄存器组。

(6)进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈,线程切换时只需要切换硬件上下文和内核栈。

(7)通信方式不一样。

分别用在什么场景:进程适应于多核、多机分布;线程适用于多核


13. fork的过程?写时复制?

fork()函数,其原型如下:

   #include <unistd.h>  
   pid_t fork(void);  

fork()函数不需要参数,返回值是一个进程标识符PID。返回值有以下三种情况:

(1) 对于父进程,fork()函数返回新创建的子进程的PID。
(2) 对于子进程,fork()函数调用成功会返回0。
(3) 如果创建出错,fork()函数返回-1。

fork()函数创建一个新进程后,会为这个新进程分配进程空间,将父进程的进程空间中的内容复制到子进程的进程空间中,包括父进程的数据段和堆栈段,并且和父进程共享代码段。这时候,子进程和父进程一模一样,都接受系统的调度。因为两个进程都停留在fork()函数中,最后fork()函数会返回两次,一次在父进程中返回,一次在子进程中返回,两次返回的值不一样,如上面的三种情况。

写时复制

当创建新进程时,连数据段和堆栈段都不再立马复制了,而是等到需要修改数据段或堆栈段的数据时再复制,这就是写时复制

这样更加节省了进程空间,效率更高。


14. fork发生复制的时候子进程会复制什么?子进程和父进程共享什么?

将父进程的进程空间中的内容复制到子进程的进程空间中,包括父进程的数据段和堆栈段

父进程共享代码段。



以上所有题的答案其实都来源于我的博客面经,欢迎大家围观:https://blog.nowcoder.net/jiangwenbo