目录

一、计算密集型和 I/O 密集型

二、衡量 CPU 的工作情况的指标

2.1 负载指标

2.2 通信量(Traffic)

2.3 监控平台

三、决定进程/线程数量

四、 问题

4.1 我的服务应该开多少个进程、多少个线程?

4.2 如果磁盘坏了,通常会是怎样的情况?


资源分配多了,CPU、内存等资源会产生资源闲置浪费。资源给少了,则服务不能正常工作,甚至雪崩。因此这里就产生了一个性价比问题:

我的服务应该开多少个进程、多少个线程

一、计算密集型和 I/O 密集型

通常我们会遇到两种任务,一种是计算、一种是 I/O。

计算,就是利用 CPU 处理算数运算。比如深度神经网络(Deep Neural Networks),需要大量的计算来计算神经元的激活和传播。再比如,根据营销规则计算订单价格,虽然每一个订单只需要少量的计算,但是在并发高的时候,所有订单累计加起来就需要大量计算。如果一个应用的主要开销在计算上,我们称为计算密集型

再看看 I/O 密集型,I/O 本质是对设备的读写。读取键盘的输入是 I/O,读取磁盘(SSD)的数据是 I/O。通常 CPU 在设备 I/O 的过程中会去做其他的事情,当 I/O 完成,设备会给 CPU 一个中断,告诉 CPU 响应 I/O 的结果。比如说从硬盘读取数据完成了,那么硬盘给 CPU 一个中断。如果操作对 I/O 的依赖强,比如频繁的文件操作(写日志、读写数据库等),可以看作I/O 密集型

你可能会有一个疑问,读取硬盘数据到内存中这个过程,CPU 需不需要一个个字节处理

通常是不用的,因为在今天的计算机中有一个叫作 Direct Memory Access(DMA)的模块,这个模块允许硬件设备直接通过 DMA 写内存,而不需要通过 CPU(占用 CPU 资源)。通过中断来实现

java 一个服务器最多有多少个线程 服务器最多有几个进程_数据

很多情况下我们没法使用 DMA,比如说你想把一个数组拷贝到另一个数组内,执行的 memcpy 函数内部实现就是一个个 byte 拷贝,这种情况也是一种CPU 密集的操作

可见,区分是计算密集型还是 I/O 密集型这件事比较复杂。按说查询数据库是一件 I/O 密集型的事情但是如果存储设备足够好,比如用了最好的固态硬盘阵列,I/O 速度很快,反而瓶颈会在计算上对缓存的搜索耗时成为主要部分)。因此,需要一些可衡量指标,来帮助我们确认应用的特性。

二、衡量 CPU 的工作情况的指标

如下图所示:CPU 有 2 种状态,忙碌和空闲。此外,CPU 的时间还有一种被偷走的情况。

java 一个服务器最多有多少个线程 服务器最多有几个进程_数据_02

  1. 忙碌就是 CPU 在执行有意义的程序
  2. 空闲就是 CPU 在执行让 CPU 空闲(空转)的指令。通常让 CPU 空转的指令能耗更低,因此让 CPU 闲置时,我们会使用特别的指令,最终效果和让 CPU 计算是一样的,都可以把 CPU 执行时间填满,只不过这类型指令能耗低一些而已。
  3. 除了忙碌和空闲,CPU 的时间有可能被宿主偷走,比如一台宿主机器上有 10 个虚拟机,宿主可以偷走给任何一台虚拟机的时间。

如上图所示,CPU 忙碌有 3 种情况:

  1. 执行用户空间程序;
  2. 执行内核空间程序;
  3. 执行中断程序。

CPU 空闲有 2 种情况。

  1. CPU 无事可做,执行空闲指令(注意,不能让 CPU 停止工作,而是执行能耗更低的空闲指令)。
  2. CPU 因为需要等待 I/O 而空闲,比如在等待磁盘回传数据的中断,这种我们称为 I/O Wait

2.1 负载指标

load average——平均负载。 负载可以理解成某个时刻正在排队执行的进程数除以 CPU 核数。平均负载需要多次采样求平均值。 如果这个值大于1,说明 CPU 相当忙碌。因此如果你想发现问题,可以先检查这个指标。

具体来说,如果平均负载很高,CPU 的 I/O Wait 也很高, 那么就说明 CPU 因为需要大量等待 I/O 无法处理完成工作。产生这个现象的原因可能是:线上服务器打日志太频繁,读写数据库、网络太频繁。你可以考虑进行批量读写优化。

到这里,你可能会有一个疑问:为什么批量更快呢?我们知道一次写入 1M 的数据,就比写一百万次一个 byte 快。因为前者可以充分利用 CPU 的缓存、复用发起写操作程序的连接和缓冲区等。

如果想看更多load average,你可以看/proc/loadavg文件。

2.2 通信量(Traffic)

如果怀疑瓶颈发生在网络层面,或者想知道当前网络状况。可以查看/proc/net/dev,下图是在我的虚拟机上的查询结果:

java 一个服务器最多有多少个线程 服务器最多有几个进程_数据_03

表头分成了 3 段:

  • Interface(网络接口),可以理解成网卡
  • Receive:接收的数据
  • Transmit:发送的数据

然后再来看具体的一些参数:

  • byte 是字节数
  • package 是封包数
  • erros 是错误数
  • drop 是主动丢弃的封包,比如说时间窗口超时了
  • fifo: FIFO 缓冲区错误
  • frame: 底层网络发生了帧错误,代表数据出错了

如果你怀疑自己系统的网络有故障,可以查一下通信量部分的参数,相信会有一定的收获。

2.3 监控平台

Linux 中有很多指令可以查看服务器当前的状态,有 CPU、I/O、通信、Nginx 等维度。如果去记忆每个指令自己搭建监控平台,会非常复杂。这里你可以用市面上别人写好的开源系统帮助你收集这些资料。 比如 Taobao System Activity Report(tsar)就是一款非常好用的工具。它集成了大量诸如上面我们使用的工具,并且帮助你定时收集服务器情况,还能记录成日志。你可以用 logstash 等工具,及时将日志收集到监控、分析服务中,比如用 ELK 技术栈。

三、决定进程/线程数量

上面观察指标是我们必须做的一件事情,通过观察上面的指标,可以对我们开发的应用有一个基本的认识。

下面请你思考一个问题:如果线程或进程数量 = CPU 核数,是不是一个好的选择

有的应用不提供线程,比如 PHP 和 Node.js。

Node.js 内部有一个事件循环模型,这个模型可以理解成协程(Coroutine),相当于大量的协程复用一个进程,可以达到比线程池更高的效率(减少了线程切换)。PHP 模型相对则差得多。Java 是一个多线程的模型,线程和内核线程对应比 1:1;Go 有轻量级线程,多个轻量级线程复用一个内核级线程。

以 Node.js 为例,如果现在是 8 个核心,那么开 8 个 Node 进程,是不是就是最有效利用 CPU 的方案呢? 乍一看——8 个核、8 个进程,每个进程都可以使用 1 个核,CPU 利用率很高——其实不然。 你不要忘记,CPU 中会有一部分闲置时间是 I/O Wait,这个时候 CPU 什么也不做,主要时间用于等待 I/O。

假设我们应用执行的期间只用 50% CPU 的执行时间,其他 50% 是 I/O Wait。那么 1 个 CPU 同时就可以执行两个进程/线程。

我们考虑一个更一般的模型,如果你的应用平均 I/O 时间占比是 P,假设现在内存中有 n 个这样的线程,那么 CPU 的利用率是多少呢?

假设我们观察到一个应用 (进程),I/O 时间占比是 P,那么可以认为这个进程等待 I/O 的概率是 P。那么如果有 n 个这样的线程,n 个线程都在等待 I/O 的概率是Pn。而满负荷下,CPU 的利用率就是 CPU 不能空转——也就是不能所有进程都在等待 I/O。因此 CPU 利用率 = 1 -Pn。

理论上,如果 P = 50%,两个这样的进程可以达到满负荷。 但是从实际出发,何时运行线程是一个分时的调度行为,实际的 CPU 利用率还要看开了多少个这样的线程,如果是 2 个,那么还是会有一部分闲置资源。

因此在实际工作中,开的线程、进程数往往是超过 CPU 核数的。你可能会问,具体是多少最好呢?——这里没有具体的算法,要以实际情况为准。比如:你先以 CPU 核数 3 倍的线程数开始,然后进行模拟真实线上压力的测试,分析压测的结果。

  • 如果发现整个过程中,瓶颈在 CPU,比如load average很高,那么可以考虑优化 I/O Wait,让 CPU 有更多时间计算。
  • 当然,如果 I/O Wait 优化不动了,算法都最优了,就是磁盘读写速度很高达到瓶颈,可以考虑延迟写、延迟读等等技术,或者优化减少读写。
  • 如果发现 idle 很高,CPU 大面积闲置,就可以考虑增加线程。

四、 问题

4.1 我的服务应该开多少个进程、多少个线程?

计算密集型一般接近核数,如果负载很高,建议留一个内核专门给操作系统。I/O 密集型一般都会开大于核数的线程和进程。 但是无论哪种模型,都需要实地压测,以压测结果分析为准;另一方面,还需要做好监控,观察服务在不同并发场景的情况,避免资源耗尽。

然后具体语言的特性也要考虑,Node.js 每个进程内部实现了大量类似协程的执行单元,因此 Node.js 即便在 I/O 密集型场景下也可以考虑长期使用核数 -1 的进程模型。而 Java 是多线程模型,线程池通常要大于核数才能充分利用 CPU 资源。

所以核心就一句,眼见为实,上线前要进行压力测试。

4.2 如果磁盘坏了,通常会是怎样的情况?

 磁盘如果彻底坏了,服务器可能执行程序报错,无法写入,甚至死机。这些情况非常容易发现。而比较不容易观察的是坏道,坏道是磁盘上某个小区域数据无法读写了。有可能是硬损坏,就是物理损坏了,相当于永久损坏。也有可能是软损坏,比如数据写乱了。导致磁盘坏道的原因很多,比如电压不稳、灰尘、磁盘质量等问题。

磁盘损坏之前,往往还伴随性能整体的下降;坏道也会导致读写错误。所以在出现问题前,通常是可以在监控系统中观察到服务器性能指标变化的。比如 CPU 使用量上升,I/O Wait 增多,相同并发量下响应速度变慢等。

如果在工作中你怀疑磁盘坏了,可以用下面这个命令检查坏道:

  1. sudo badblocks -v /dev/sda5

我的机器上是 /dev/sda5,你可以用df命令查看自己的文件系统。