ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【架构师从入门到进阶】第六章:服务集群优化——第八节:线程数公式

【架构师从入门到进阶】第六章:服务集群优化——第八节:线程数公式

【架构师从入门到进阶】第六章:服务集群优化——第八节:线程数公式一

  • CPU核数
  • CPU利用率
    • load average
  • w 和 c
  • 实际中的并发数
    • IO密集型
    • 计算密集型

本篇文章我们来学习多线程中线程数的选择。

这个也是困扰大家的一个问题,有的时候甚至说拍脑袋决定一个,其实这里面的答案,严格来说,没有准确的数字。但是大概也是有据可循,就是通过一些公式,也可以拿到一些数据来作为一个参考。

好了,我们来看一下这个线程的公式:

有一本书叫做JAVA并发编程实战,这个书中给出一个公式是这样的。

CPU核数

CPU核数很简单,大家都知道。

CPU利用率

CPU的利用率是什么呢?比如说,CPU运行了两秒的时间,然后发现CPU在跑你的程序的过程中,CPU计算的时间只有零点五秒,那么这个时候的CPU利用率就是50%。

也就是说,CPU的计算时间比上CPU的总时间,就叫做CPU的利用率。

它反映了CPU的繁忙程度,也就是CPU忙的时间占总时间的比例是多少。

还有就是我们要考虑到核数,比如说单核的CPU在1秒内计算的时间是0.8秒。那么这个CPU利用率就是80%。双核的CPU1秒内,一个是运行0.4秒,另外一个核运行了0.6秒,那么总体来说,CPU的使用率是(0.4+0.6)÷2,这样一除是50%。

我们在实际用的时候呢,我们假设CPU的利用率是100%,假设这样的话,这个参数相当于就没有了,因为是一嘛,一乘以任何数,都等于任何数嘛。

但是根据一些经验法则的建议,在生产系统中,我们CPU的总使用率不超过70%,也就是说我们把程序部署到项目中的机器上,那么这个机器的CPU,控制在70%以下,算是比较健康的。我们也可以用70%去假设。

好了,有人可能会说,那你刚才说了一个100%,又说了一个70%,那到底以哪个为准?

首先,第一步,先拿100%去计算,大家以为我的CPU能跑满,但是实际上放心,CPU不会跑满,就是CPU大概率的情况下,不会说你按它使用率100%去计算,它就把CPU跑死了,不会的,大概率CPU会有一点余量的,因为它处理不完会排队嘛。所以呢,大家拿100%去算,或者拿70%去算,估一个大概的范围都是没有问题的。

load average

在CPU利用率这一块,我们做一个扩展,就是我们在linux的系统下,通过top命令去查。会查到一个load average,里面有几个数,比如说“1.0, 1.1, 1.2”。

这三个数字分别代表一分钟、五分钟、十五分钟,系统的平均负载。

这个值越小,表示系统的工作量越少,就是负载越低,反之就是负载越高。

理想的情况下,每个CPU应该满负荷去工作,并没有等待进程,就说CPU就是不停的计算,不停的计算。此时平均负载等于CPU的逻辑核数,比如说CPU是二核,那我们的平均负载就是二。

但是在实际生产中,不建议系统满负荷去运行。根据法则,CPU的负载率不要超过70%。所以这块一般要低于CPU的逻辑核数乘以0.7这么一个值。当平均负载持续大于0.7×CPU逻辑核数的时候,就需要开始调查原因了,防止系统恶化。当平均负载持续大于逻辑核数时,必须想办法降低平均负载,停止一些无用的服务,把什么杀毒软件或者在服务器上做的装的一些没用的进程给它停掉。当服务器持续大于五倍的CPU核数,那么表示系统已经开始有严重的问题,长时间未响应,或者接近死机了。

除了关注平均负载本身,我们也应该关注平均负载的变化趋势。如果说这三个值,就是说。一分钟、五分钟和十五分钟,这三个值非常接近的时候,表明短期内系统负载比较平稳,此时应该将其与昨天或上周或者上个月同时段的历史负载进行比对,观察是否有显著的上升。

当一分钟这一块,小于后面的五分钟或者十五分钟的时候,这表明最近一分钟负载在降低,就是系统慢慢的负载在降低,说明高峰过去了。

那么当一分钟远大于五分钟或十五分钟的时候,说明系统的负载在急剧的升高,如果不是临时性的抖动,而是持续的升高,特别是已经超过0.7×CPU逻辑合数的时候,应该调查原因。

w 和 c

w是wait的意思,wait就是等待时间;那么c呢,就是计算时间。也就是说程序的等待时间和计算时间的一个比就是w比c。

举个例子。我们的CPU的核数是两个,CPU使用率是100%,就是1,然后等待时间是2毫秒,计算时间呢1毫秒。这样算下来,就等于六个线程。

怎么去验证6个线的这个结果是对的呢?

假设这是一个三秒钟的时间段。

我们等待时间是2,然后计算时间是1秒。那么我们大家可以看一下,比如说有一个线程来了,一个线程来之后,他计算时间花去了一秒钟的时间,然后他需要去其他地方等待IO,去等待的时候那么CPU是不是空出来剩下的两秒?

然后又另外一个线程来。另外一个线程就利用CPU去计算,他把这段时间给计算了,然后他再去另外一个地方去等待。

然后,又来一个线程3号,来的时候又利用CPU的这段时间去计算,然后他又去等待了。

那么,这个CPU核的时间是不是被三个线程的计算利用了?那么如果两个核呢,它乘以2,这是6个线程。

跟我们的公式算出来6是一模一样的。

如果CPU的利用率是50%的话,那么就会变成3。

如果等待时间远大于计算时间,比如说等待时间变成6,那么我们的结果就会变成14个。

就是说等待的时间比上计算的时间比例越大,需要的线程数越多。其实这就说明另外一个问题,就是说CPU的计算时间越少,那么这种程序在CPU能计算的程序数就会越来越多。

所以等待的时间越久,可以设置的线程数越高。可能大家会有这个疑问,等待时间和计算时间怎么来做呢?其实我们可以通过程序来统计,比如说代码从上到下执行,中间这块是计算,然后后面这块是IO输入,我们可以在前后都打上时间戳,把这段时间记下来。

实际中的并发数

实际中的并发数要怎么设计呢?

我们先假设我们的我们的CPU的利用率是100%,那么我们就把相当于把它去掉了。

这里面会分为两种情况,我们的程序可以分为两种程序,一种是IO密集型,另外一种呢是计算密集型。

IO密集型

那么我们先来看IO密集型。

一般的情况下,IO密集型肯定就是IO等待时间比计算时间要大很多很多,也就是说阻塞耗时一般都是计算耗时的很多倍。但是需要考虑系统内存有限,就是每开一个线程都需要一个内存空间,并不是说线程越多越好。这里需要具体的去服务器上进行测试,开多少个线程比较合适。也就是说,我们开一定数量的线程之后,我们跑一下,然后去关注服务器的一些指标,CPU占比、线程数、总耗时、内存消耗等等指标。如果说不大家在最开始还没上线程序的时候,不想去测,那我们可以保守点,就取1。

因为实际上来说,等待时长肯定比计算时间要长很多,那么我们这里保守一点,就w这个值给它设置成1的话,就相当于等待时间和计算时间是一样的。这样的话,我们设置来生成的线程数肯定会偏少一些。最后就会变成CPU的核数乘以2。CPU是八核的,你就开16个线程。

计算密集型

那么计算密集型呢?计算密集型我们也是用这个公式去做。

计算密集型就是c特别大,w比较小,那么就把w当成无限趋近于零。

也就是说计算密集型,就是CPU是几核,你就给它设置上几个线程。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进