本站总访问量 字节面试 - Jerry的小站

Jerry Gao

上帝就是真理,真理就是上帝

TCP中TIME_WAIT状态

如何产生

首先调用close()发起主动关闭的一方,在发送最后一个ACK后才会进入time_wait状态,该发送方会保持2MSL事件之后回到初始状态,MSL是数据包在网络空间的最大生存时间。

2MSL = 去向ACK最大生存时间 + 来向FIN最大生存时间

产生的原因

实现TCP全双工连接的可靠释放

如果主动关闭的一方不维护这样一个TIME_WAIT状态,那么在FIN到达的时候会用RST包响应对方,被认为是有错误发生服务端不会断开连接。

使旧的数据包因为过期而消失

TCP协议栈无法区分前后两条TCP连接的不同,会把它视为同一个连接,如果不维护TIME_WAIT状态可能产生数据错乱而导致各种无法预知的现象。

如何避免产生time_wait状态

服务器设置SO_REUSEADDR套接字选项来通知内核,如果端口忙,但TCP链接处于time_wait状态,可以重用端口。

TCP三次握手

为什么需要三次握手

防止已经失效的连接请求报文段突然又传送到了服务端,因而产生错误。主要为了防止server端一直等待,浪费资源。

ping涉及到的协议

  • 通过DNS协议将域名转换为地址
  • 通过ARP协议将IP地址转换为MAC地址
  • 发送ICMP回显请求给目标主机并等待回显应答

TCP粘包问题

保护消息边界和流

保护消息边界:传输协议把数据当作一条独立的消息在网上传播,接收端一次只能接收发送端发出的一个数据包。

面向流则是指无消息保护边界的,如果发送端连续发送数据,接收端有可能在一次接受动作中会接受两个或更多的数据包。

当缓冲区足够大时,有可能会一次接收到两个甚至更多的数据包,这样就需要接收端额外拆包,增加了工作量。

TCP粘包是指发送方发送的若干包数据到接收方接受时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

什么时候需要考虑粘包问题

  • 如果利用tcp每次发送数据就与对方建立连接,然后发送完一段数据就关闭连接,这样就不会出现粘包问题
  • 如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接受存储就OK,也不用考虑粘包。如果双方建立连接,需要在连接后一段时间内发送不同结构的数据,需要分包,一般可能会在头加入一个数据长度之类的包,以确保接受。、

粘包出现的原因

  • 发送端需要等到缓冲区满才发送出去,造成粘包,也就是说为了提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据
  • 接收方不及时接收缓冲区的包,造成多个包接受。接收方引起的粘包问题是因为接收方进程不及时接收数据,从而导致粘包现象(一次取走多个数据包)。

如何应对粘包

接收方创建一预处理线程,对接收到的数据进行预处理,将粘连的包分开。

Linux杀死进程

1
2
3
4
5
kill -l pid // 以用户注销的方式结束进程,也试图杀死所留下的子进程,但不是总能够成功
kill -TERM PPID 给父进程发送一个TERM信号试图杀死它和它的子进程
killall httpd // 杀死同一进程组内所有进程,允许指定要终止的进程的名称,而非pid
kill -HUP PID // 让和缓的执行进程关闭,然后立即重启。
kill -SIGNAL PID // 向进程发送信号 -9 杀死 -1 挂起等

查看当前系统的负载,说一下平均负载的三个参数

uptime w ```均能够查看load average,分别表示过去1分钟、5分钟、15分钟内运行进程队列中的平均进程数量
1
2
3
4
5
6
7
8
9
10
11
12
13

系统平均负载被定义为特定时间间隔内运行队列中的平均进程数,如果一个进程满足下列条件就会位于运行队列中:

- 没有在等待I/O操作的结果
- 没有主动进入等待状态
- 没有被停止

一般来说只要每个CPU的当前活动进程数不大于3,那么系统的性能就是良好的,如果你每个CPU的任务数大于5,那么就表示这台机器的性能有严重问题。

# 如何查看监听端口

```shell
netstat -antlp

进程和线程的区别

  • 进程是操作系统资源分配的基本单位,线程是操作系统调度的基本单位。一个进程可以拥有多个线程。
  • 线程更加轻量级。创建一个线程比创建一个进程要快10~100倍。而且进程上下文切换比线程上下文切换消耗来得大。
  • 进程有独立的地址空间,启动一个进程,系统会分配地址空间,建立数据表来维护代码段、堆栈段和数据段,非常昂贵。而线程之间不需要系统分配独立的地址空间,比较轻量级,在CPU上下文切换时比较小,线程之间可以共享进程的地址空间、全局变量、打开文件、子进程、信号和信号处理程序等。
  • 多进程更加健壮,而多线程只要有一个线程死掉整个进程也会死掉。

阻塞IO和非阻塞IO

阻塞IO

进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程,操作成功则进程获取到数据。

特点

  • 进程阻塞挂起不消耗CPU资源,及时相应每个操作
  • 实现难度低、开发应用较容易
  • 适用于并发量小的网络应用开发
  • 不适用于并发量大的应用,需要为每个请求分配一个处理进程以及时响应,系统开销大

非阻塞IO

进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞,进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回给进程。

异步IO

当进程发起一个IO操作,进程返回,但也不能结果,内核把整个IO处理完毕之后,会通知进程结果。如果IO操作成功则进程直接获取到数据。

IO复用模型

多个进程的IO可以注册到一个复用器上,然后用一个进程调用该select,select会监听所有注册进来的IO;

如果select没有监听的IO在内的内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任意IO在内核缓冲区中有可读数据时,select调用就会被返回;

而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。

多个进程注册后,只有一个select调用进程被阻塞

典型应用

  • select、poll、epoll三种方案,nginx都可以选择使用这三种方案

特点

  • 专一进程解决多个进程IO’的问题,性能好;
  • 实现、开发难度较大
  • 适用于高并发服务应用开发:一个进程(线程)响应多个请求;

信号驱动IO模型

当进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时就会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。

服务器常见的并发模型

单线程循环

多线程/多进程

主要特点是每个网络请求由一个进程/线程处理,线程内部使用阻塞式系统调用,在实际场景中适用预先分配的进程/线程/池,以减少频繁创建销毁线程的开销,往往获得更好的性能。

往往由一个单独的线程处理accept连接,其余线程处理具体的网络请求(收包、处理、发包);还可以多个进程单独listen,accept网络连接

优点

  • 实现相对简单
  • 利用多核CPU资源

缺点

  • 线程内部还是阻塞的,如果一个线程在handle的业务逻辑中sleep了,这个线程也就挂了

单线程IO复用

Linux高并发服务器中常用epoll作为IO复用机制,线程将需要处理的socket读写事件都注册到epoll中,当有网络IO发生时,epoll_wait返回,线程检查并处理到来的是socket上的请求。

优点

  • 实现简单
  • 减少锁开销
  • 减少线程切换开销

缺点

  • 只能使用单核cpu,handle时间过长就会导致整个服务挂死

适用场景:高IO、低计算、handle处理时间短

典型应用:redis

多线程/多进程IO复用

每个子进程都监听服务,并且都使用epoll机制来处理进程的网络请求,子进程accept()后将创建已连接描述符,然后通过已连接描述符来与客户端通信。

优点:支撑较高并发

缺点:异步编程不直观、容易出错

适用场景:支撑高并发

典型应用:Nginx

协程

在应用层用户态模拟线程,在用户态管理协程的调度与切换

优点

  • 减少上下文切换开销
  • 编程友好、同步的方式写出异步代码

缺点

  • 多个协程运行在一个线程上,一个协程阻塞将导致整个线程阻塞

HTTP协议由哪些部分组成

HTTP协议包含:通用头域、请求消息、响应消息和主体消息

HTTP协议包含:http协议的请求和http协议的响应

http协议的请求又包含以下内容

  • 请求方法-URL-协议/版本号
  • 请求头
  • 请求正文

http协议的相应包含以下内容

  • 状态行
  • 响应头
  • 响应正文

通用头域

Cache-Control头域

指定请求和响应遵循的缓存机制,在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。

Date头域

表示消息发送的时间,时间的描述格式由rfc822定义。描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的地区。

Pragma头域

用来包含实现特定的指令

请求消息

响应消息

实体消息

JSON和Protobuf

什么是Protobuf

Google开发的一种协议,允许对结构化数据进行序列化和反序列化。

与JSON有什么不同

可以互相交换使用。JSON是一种消息格式,源自于JavaScript子集,以文本格式交换,几乎支持所有的编程语言。

Protobuf不仅仅是一种消息格式,还是一组用于定于和交换这些消息的规则和工具。拥有比JSON更多的数据类型。

根据实验,比JSON更快

为什么选择JSON,JSON的优势是什么

Protobuf缺少详细的文档,社区不够健壮。

Python 元类

元类就是类的类,就是type

如何创建一个元类

1
2
3
4
5
6
>>> Person = type("Person", (), {"age": 10})
>>> Person.age
10
>>> Man = type("Man", (Person,), {"get_age": lambda self, self.age})
>>> Man().get_age()
10

类的创建过程

  • 当Python见到class关键字时,首先会解析class…中的内容。例如解析基类信息,最重要的是找到元类信息
  • 元类找到后,Python需要准备namespace(也可以认为是type中的dict参数),如果元类实现了__prepare__函数,则会调用它来得到默认的namespace
  • 之后是调用exec来执行类的body,包括属性和方法的定义,最后这些定义会保存在namespace
  • 上述步骤结束后,就得到了创建类所需要的所有信息,这时Python会调用元类的构造函数来创建真正的类

评论