提升利用CPU的效率
1 )CPU的调度机制
-
现在来看下 linux中 CPU的一个调度机制
-
假设现在系统上有只有一颗CPU,而linux系统是一个多任务的一个操作系统
-
它允许我们各个不同的用户允许在同一个操作系统上执行很多个进程
-
单核CPU肯定不可能同时去执行这样一些程序
-
CPU在同一时刻只能够调度一个进程来执行,没办法并发执行多个程序
-
从宏观上来看,多任务系统,都是能够一起执行的
-
比如说在windows中,打开一个聊天软件,边聊天,边看电影,都可以一起来做
-
们从这种宏观上来说,多个进程,它是一个并行执行的,但从CPU的角度,同一时刻只能干一件事
-
比如应用程序进程:一个CPU在处理 Kafka, MQ, Nginx, ElasticSearch 进程
-
对CPU调度的时候,它在某一个时刻,它只能有一个会被调度到CPU上
-
比如,Kafka 进程被调度到CPU上执行,执行完了以后,会拿下来,到底它去执行多久
-
其实这里有一个叫时间片的概念,也就是说每一个进程都会被首先分配一个时间片
-
那这个时间片它就是我们在CPU上去执行的时候的一个时间长度
-
这个时间片它可能是不一样的,是由我们CPU调度算法来决定的
-
在不同的操作系统中,可能CPU调度算法是不一致的
-
有一个大的原则,比如,某一个进程的优先级是最高的
-
如果说,这个进程的优先级是比较高的话,它相应的得到的CPU的执行的时间片就会多一些
-
还有一个前提,就是说这个进程在执行的过程中不会陷入阻塞状态
-
也就是说它自身程序在执行的过程中,如果遇到阻塞比如读取磁盘(读取效率远远小于cpu执行效率)
-
如果陷入这样一个阻塞状态的话,CPU会把当前进程从时间片上拿下来,
-
即使当前程序被分配了 100ms, 只执行了20ms, 也会被扯下来,CPU会执行下一个进程
-
整个CPU就是在循环往复的去执行每一个进程,在操作系统中,所有进程都会被分配一些时间片
2 ) 原则 和 优化策略
- 整个优化进程使用CPU遵循的一些原则,如下
- 尽可能占有全部的这个CPU资源
- 尽可能占用更大的CPU时间片、减少进程间切换
- 有以下几项优化策略
- 指定 worker 子进程个数
- 如果不知道个数,则 woker_processes auto; 设定为 auto
- 将worker子进程与每个CPU进行绑定
- worker_cpu_affinity 01 10 01 10 通过掩码来绑定
- 这样可以有效利用cpu缓存
- 提高worker子进程的进程优先级
- worker_priority -20; 优先级越高越优先
- 默认值 120, -20 ~ 19
- 数字越小,优先级越高
- 延迟处理新链接
- listen 80 deferred 延迟Nginx处理并发连接
- 建立连接完成之后,用户没有发送Http请求,Nginx自身不会被激活唤醒
- 从而提升Nginx处理能力
- 指定 worker 子进程个数
TCP的三次握手和四次挥手
- 不参考网络七层模型,按照我们实际使用的四层模型来说
1 )四层模型封包拆包过程
![](https://i-blog.csdnimg.cn/direct/49526cae640d4d5cb0dc9b8a0e9d7d02.png)
- 最高层的一个应用层, 在下面是传输层
- 之后是网络层,最下面是一个数据链路层和物理层
- 来看一下整个数据报文通信的过程中,在每一层都做了哪些事情
- 首先,在应用中,发送的就是 HTTP 的报文,比如说,请求某一个URL的时候
- 这个时候其实对我的应用层来说,中间的数据都是HTTP的一些具体数据
- 我不能够把应用层的数据直接发送给接收端的服务器上
- 必须要把这个数据报文进行层层的封装
- 比如,HTTP数据报文发送到这个传输层的时候,它需要封装一个TCP的报文
- 其实到传输层,这个报文其实就变成了TCP报文
- 传输层继续往下层,经过网络层的时候,它又会封装了一个IP首部的一个报文
- 包括到最下面的一链链路层的时候,又会去封装一个以太网首部的一个报文
- 其实我们真正的数据就HTTP这么一小块, 可能是只有几十个字节
- 为了发送的几十个字节的数据,需要给它封上一层一层的报文
- 其实这就是性能上的一些开销,但是这也是不可避免的
- 那其实对某一些应用进行深度优化的时候,在某些程度上甚至可以去修改整个TCP协议
- 依据RFC的规范,现在整个TCP/IP协议发展已经越来越越好了,这种场景是比较少的
- 但是在一些极致场景下,比如 Google 他可能认为这个TCP/IP协议不太好
- 他就会去写一些第三方的协议来优化这样一个协议,当然它不是整个推翻再重写
- 它只是根据它自己的一些应用对某一个一些细节进行优化
- 到最底层的链路层的时候,我的报文其实已经被封装了好几层了
- 在我真正的HTTP数据上封装了一层TCP报文又抹上了一层这个IP报文
- 最后最外层抹了一个以太网这样一个报文
- 对这样一个整体报文,就会通过网线,通过数据链路在网络上进行寻址
- 比如说,要过运营商的路由器,层层过了很多网络设备之后,到达我的服务器
- 这个时候到达服务器的网卡上之后,网卡自身会逐层的拆包
- 网卡首先会拆去这个以太网首部,拆完会去看一下这个以太网首部中可能有一些地址
- 比如说有我原服务器的一个MAC地址(对应物理网卡的一个地址)
- 假如说, 他发现这个目的网卡的地址就是他自己这台机器上的对应这块网卡的地址
- 它这个时候就知道这个数据报文原来是发给自己的
- 这个时候它就会把这个以太网首部报文给拆掉
- 拆掉之后,会把里面这样一个报文交给上层的一个网络层,就由网络层再去处理
- 这个时候,网络层会拆去这个IP首部报文
- 它会在IP首部报文中对比,着重核实一下这个目的IP是不是自己这台服务器的一个IP地址
- 如果不是本机配置的IP地址,它会直接扔掉这个包了,如果它发现这个目的IP地址是本机配置的IP地址
- 这个时候它会继续把这个报文往上一层交,继续交给传输层
- 当然在网络层会把这个报文拆掉,把里面TCP报文包裹的这个数据给整体发到传输层
- 传输层其实会看一下TCP首部,里面都知道对应的是端口,着重看下目的端口是多少
- 比如说,这儿有一台web服务器,上面部署了HTTPS的这样一个web服务
- 这个时候,会发现目的端口是 443, 他就知道需要发到上层的应用层里进行处理
- 这个时候传输层的任务就做完了,继续把这个包中的就整个HTTP的数据发给
- 应用层的HTTPS服务进程去处理,它发现请求了某个URL
- 这时候,进程会去构建 HTTP 响应包,比如把整个网页编码之后封装成HTTP报文
- 这时候,要准备响应给我们的发送端了,之后,它同样会逐层封装报文,怎么拆的,怎么封回去
-
- 最后通过网络发送给客户端,客户端拿到之后也是自下而上,逐层拆包
- 最后拆到我们这个响应的HTTP报文, 最后在浏览器中在展示
- 其实,在整个TCP连接连接建立和数据传输的过程中,其实我们知道最多的只有TCP的三次握手和四次挥手
- 因为我们能对这个网络层,包括这个链路层能优化的事情是其实是比较少的
- 因为这个跟我们这个硬件设备是极其相关的,尤其是我们这个数据链路层
- 其实,比如说对内核层的一个TCP连接的建立和断开这样一个过程,我们可以做一些优化
2 ) TCP三次握手和四次挥手的过程
2.1 三次握手
![](https://i-blog.csdnimg.cn/direct/869256755b6e4237aa7d25c3b7fcbcff.png)
- 三次握手,其实通常是由客户端发起的
- 比如说, 现在有一个客户端浏览器,输入了某一个URL,回车之后
- 那其实这个时候我们的浏览器会向服务器端发送一个TCP连接建立的请求
- 我们的客户端会首先发送一个 SYN 这样一个标志位
- 它发送SYN这样一个包的时候,服务器端接收到这个包之后,就知道客户端要建立一个新的TCP连接
- 假定,客户端发送的 SYN的包的序号是 x
- 服务器在收到以后,服务器首先需要对收到客户端的 x序号 的包进行一个响应
- 它会有一个ACK的这样一个标志位,ACK的序号是 x + 1, 将这个包发送给客户端
- 客户端接收到以后,查看发现 ACK的序号是 x + 1,它就知道上次发送的 x 包已经被服务器接收了
- TCP是需要确认的,是一个面向可靠连接的协议
那这个时候我们其实在生测过程的过程中, - 服务端在第一次响应的时候 除了会发送 ACK=x+1,它也会发送一个SYS包, 序号是 y
- 客户端在收到这个包后,也需要对序号为y包进行确认,发出 ACK=y+1 进行确认
- 服务端收到后,至此,TCP的整个连接就建立了,这时,客户端和服务器之间就可以发送数据
- 比如说,客户端请求某一个URL,服务端构建响应请求把数据返回给客户端
- 客户端进行页面展示,中间再不停的发数据,接数据,发数据,接数据 …
- 在整个过程中,假如说, 客户端想要断开连接,或者服务器处理性能已经达到了
- 不能够再满足客户端的新请求了, 这个时候都要去断开已有的连接
2.2 四次挥手
![](https://i-blog.csdnimg.cn/direct/d897a974871849b68edc9efeacdbefb8.png)
- 在四次挥手过程中,客户端和服务器端两个都可以主动的去断开连接
- 三次握手,通常是由客户端发起的,四次挥手(断开连接)的过程,两端都可以发起
- 假定客户端断开这样一个连接,我们的客户端中间发送了很多数据,当不需要再请求数据就要断开了
- 客户端会发送一个 FIN 标志位的包给服务器,同时也会有一个ACK的包,建立时客户端接收到的是y
- 客户端发送了一个FIN标志位的包,也就意味着我的客户端要断开连接
- 注意,客户端在建立连接时,最后收到服务端响应的的 是 x + 1 和 y,
- 所以,客户端在封包的时候会自动加上1,即:FIN seq=x+2 ACK=y+1
- 之后,服务器端在收到这样一个断开链接的请求之后,他会首先发送一个ACK,即:x+2+1=x+3
- 这个意思是,服务端已经了解客户端要断开连接的需求了
- 客户端再次接收到以后,其连接请求可能并不能立刻断开
- TCP是一个全双工的这么一个协议,客户端和服务器利用这样的通信通道的过程中
- 客户端可以给我的服务器端发送数据,同时服务器端也可以给客户端返送数据
- 也就是说,他们两个可以同时发送的数据,对于此场景,客户端发起了断开请求
- 客户端肯定没有数据发送给我的服务端,但是服务端可能还在发送数据,客户端还没有接受完的状态
- 为了应对TCP连接过程这个全双工的场景, 必须要两次断开
- 服务端确认可以断开
- 服务端还在发送数据,客户端不会立即断开,等待服务器发送完成
- 服务端会发送 FIN seq = y + 1 的包给客户端
- 客户端进行确认 ACK = y+2
- 至此,TCP才可以彻底断开
- 这里还有一个问题,服务器端发送给客户端 FIN seq=y+1 之后,客户端进入TIME_WAIT的状态
- 在这个 TIME_WAIT 的状态中,后面需要去等待两个MSL的时间
- MSL是在RFC中规定的一个时间, 全称是 Max Segment Lifetime
- 可以理解为断开前的最大生存时间
- 其实也就是意味着,如果一个MSL 在整个网络中传输中
- 如果超过了两个MSL的时间,这个报文会失效的
- 这种场景是,TCP协议在设计之处考虑到整个网络传输是不可靠的
- 因为网络传输可能需要经过很多的中间节点,因为中间的可能有路由器有交换机等等
- 中间任何一个节点出现故障不可靠的话,那TCP的报文都是需要在超时重传的
- 因此, 为了保证客户端回送的这个ACK的报文能够正确的到达服务器端
- 必须等待两个MSL这样一段时间,即使没有传输到服务器端,连接也已经断开了
- 如果超过了两个MSL时间又发送给了服务端,那时,因为失效会被丢弃