以下是工作中学习到的一些技巧和原理性的介绍,主要来自于各个同事写的文章知识总结,对同事们表示感谢
另外微信公众号上也有一篇相关的文章:文章地址
Linux load的准确含义
日常运维中我们经常会碰到Linux系统load过高的问题,但是什么是load,load是怎么得到的,以及在load高的时候应该是怎样的排查思路,都没有一个很好的说明文档,在这里,尝试全面阐述一下Linux load的计算原理和相关的排查方法。
日常我们在排查问题的时候经常会使用到的就是top uptime等命令,实际上这些命令都是通过/proc/loadavg获取的,我们可以使用如下的命令进行验证:
|
|
实际上都是来自procps-ng这个rpm包中。
/proc/目录中mount的是Linux的伪文件系统,主要是被用作和内核数据结构的接口,我们查看其中loadavg的含义:
|
|
注意centos7之后的系统(未验证过)已经取消了这个的man信息
从这段话中我们可以总结以下信息:
- /proc/loadavg前三个值表示的是load1 load5 load15的值
- load值代表的是对应时间内jobs的平均数量,比如load1表示过去1min内jobs数量的平均值,这里的jobs其实应该是内核中的tasks或者用户态的threads概念
- load计算的值只包括状态为R和D的两种jobs,其他不包含在内,其中R表示运行队列,D表示在等待磁盘(具体的进程的状态转移请参考另一篇文章)
load分析工具
在这里我们先抛开其他因素,先看一下日常会用到的脚本分析工具:
|
|
简单介绍一下几个参数:
- -e表示显示当前系统中的所有进程
- -L表示对每一个进程都展示所包含的所有线程,每个线程一行
- h表示隐藏ps命令第一行的header标题信息
- o state,ucmd,这里他们是组合在一起生效的,只输出state和ucmd这两列信息,state表示线程状态,ucmd表示线程的名称
找一台比较繁忙的机器运行下脚本,我这边起了一个打cpu的进程,代码就是微信公众号文章的那个代码,输出结果:
|
|
这里发现脚本输出的第一列相加在104,和uptime load1输出的差不多。此时如果想要将load1值拆解到具体的线程级别上,就可以看到影响这个load输出结果的最重要因素是这个cpuburn的,状态是R,数量上贡献了101个。
内核里load是怎么计算的
注:这一段是看其他同学做的分析的结果,我自己本身并未做相关的分析实验,此处只是拿其他同学的分析结果。
首先来看下/proc/loadavg伪文件对应的内核代码:
|
|
预编译之后如下:
|
|
LOAD_INT(avnrun[0]), LOAD_FRAC(avnrun[0])预编译之后是((avnrun[0]) >> 11), ((((avnrun[0]) & ((1<<11)-1)) *="" 100)="">> 11)。由于内核没有小数计算,这段代码本质上是实现了avnrun[0]除以2048且取2位小数的浮点运算
get_avenrun函数定义在kernel/sched/core.c中:11)-1))>
|
|
预编译后:
|
|
我们可以看到get_avenrun函数通过avenrun全局数组变量,返回上面的avnrun数组变量。avenrun全局数组变量在calc_global_load函数中每隔5001毫秒,由calc_load函数将active值添加到原有的load值中,进而产生新的load值。其中load1、load5和load15的区别只是第二个exp参数传入给calc_load函数不同的值,依次为1884、2014和2037。这里active值从calc_load_tasks全局结构体变量获取。calc_load_tasks全局结构体变量在calc_load_account_active函数中设置,而整个值的最初来源是calc_load_fold_active函数。
在函数calc_load_fold_active中,我们可以看到最终获取的是rq(run queue)队列中的this_rq->nr_running和this_rq->nr_uninterruptible两种状态的task数。熟悉cpu调度算法的同学知道,这里实际上是把每个cpu队列里的nr_running和nr_uninterruptible值都汇总到一起。而nr_running和nr_uninterruptible正好对应于用户空间中的R和D两种状态的线程的数量。
回头再看前面man page说明中的jobs概念显然是不对的,正确的应该是tasks概念(用户空间对应threads概念)。
load2process输出结果中第一列之和、load5s和load1值之间的关系也一目了然。load2process第一列之和是运行这个脚本的瞬时nr_running和nr_uninterruptible状态线程数之和。load5s值是每5001毫秒对nr_running和nr_uninterruptible状态线程数之和的一个采样。load1是对之前历史所有load5s采样值按某种算法的平均值。
如何分析load高的问题
上面我们已经基本搞清楚了load的原理,也有了load分析的工具,接下来我们利用上述的工具来具体看看怎么分析load的问题。
前面我们说到,load的计算过程中,取得是R和D两种状态的数据进行计算,其中R状态代表的是running状态的线程,D状态代表的是uninterruptible状态的线程,具体的进程状态请参考Linux进程状态
而事实上,R和D状态对系统的影响度是完全不同的,一般来说:
- R状态导致的load高,主要和CPU核数有关,大于CPU核数2倍以上,就会出现严重问题,出现CPU争抢问题
- D状态的数据,则在load高的时候,系统有可能还是能正常服务,只是在读写操作上需要等待
因此我们需要分开监控R和D状态,kernel为我们提供了这样的数据:
|
|
在内核中,还提供了另外的几个参数:103代表此时正在运行的R状态的线程数。
另外,我们在日常运维的过程中,经常会在load过高的情况下去top看一下CPU占用,但其实只有在R状态的时候才耗费CPU,如果load高是因为D状态导致的,那么这时候去看top是无效的,即使是由R状态导致的,如果在运行top的时候R状态已经停止了,那么也是看不到相关信息的,默认情况下,top是3s刷新一次,显示3s内的CPU信息。
我们总结上述的几种规律:
- 单线程R状态过高导致load高,这种一般都是我们自己代码写的问题
- 多线程R状态高导致load高,这种不多出现
- 单线程D状态过高导致load高,可以查看当前这个程序对应进程的waiting channel信息:/proc/
/wchan,表示当前线程在这个函数位置,D状态导致load高的原因有很多种,需要具体分析 - 多个线程D状态高导致load高,需要具体分析,可能是系统或者磁盘损坏了。
在这里,也分享几个查看load高的时候排查的脚本:
|
|