最近在兼职部分运维工作,之前没怎么做过,对Linux不是那么的熟悉,这导致的主要问题是,当出现性能问题,不知道看那些指标,那些指标有什么含义。有不足的当然要恶补一下,所以就有了这篇文章。这篇文章只是抛砖引玉,改天有时间写篇,linxu性能监控,大体上就是服务器性能应该看那些指标,去定位问题所在。
内存
存储媒介
速度:L1/L2/ cache > 内存 > 固态 > 硬盘。一般的缓存路径是:硬盘or固态—>内存 —> L1/L2缓存。 将数据存入L1/L2缓存一般是那种比较底层的应用会采取的方式,例如linux的TLB(将虚拟地址到物理地址的映射缓存到 L1 cache中),加快地址转换的速度。
虚拟内存
虚拟内存是Linux管理内存的一种技术。它使得每个应用程序都认为自己拥有独立且连续的可用的内存空间。大体关系如下。
进程X 进程Y
+-------+ +-------+
| VPFN7 |--+ | VPFN7 |
+-------+ | 进程X的 进程Y的 +-------+
| VPFN6 | | Page Table Page Table +-| VPFN6 |
+-------+ | +------+ +------+ | +-------+
| VPFN5 | +----->| .... |---+ +-------| .... |<---+ | | VPFN5 |
+-------+ +------+ | +------+ | +------+ | | +-------+
| VPFN4 | +--->| .... |---+-+ | PFN4 | | | .... | | | | VPFN4 |
+-------+ | +------+ | | +------+ | +------+ | | +-------+
| VPFN3 |--+ | | .... | | | +--->| PFN3 |<---+ +----| .... |<---+--+ | VPFN3 |
+-------+ | | +------+ | | | +------+ | +------+ | +-------+
| VPFN2 | +-+--->| .... |---+-+-+ | PFN2 |<------+ | .... | | | VPFN2 |
+-------+ | +------+ | | +------+ +------+ | +-------+
| VPFN1 | | | +----->| FPN1 | +----| VPFN1 |
+-------+ | | +------+ +-------+
| VPFN0 |----+ +------->| PFN0 | | VPFN0 |
+-------+ +------+ +-------+
虚拟内存 物理内存 虚拟内存
这里需要了解的:
- 虚拟内存的描述的结构叫memory mapping,主要描述了虚拟地址的起始位置,长度,权限,以及类型
- 虚拟内存关联的不一定是物理内存,还可以是磁盘,swap区。因为是映射,可以映射到各种存储媒介中去,这是虚拟内存强大的地方之一
- 虚拟内存由于是映射,所以可以先分配虚拟内存,到了使用时才分配真正的物理内存,这个叫延迟加载。大体的过程是这样子的。1. 读虚拟内存 —> 发现没有分配物理内存 —> 缺页中断 —> 分配物理内存 —> 与虚拟内存产生管理 —>再次读取。这样做是为了提高内存的使用效率。需要注意的是并不是任何情况下都是延迟加载的。
- 物理内存可以映射到不同的进程中的虚拟内存中去(多对多关系),所以可以实现不同的进程共享内存
交换空间
当系统物理内存吃紧时,Linux会将内存中不常访问的数据保存到swap上。一个看内存是否不够的重要指标swap的使用率
这里主要需要了解的是:
- 一般当需要使用到swap的时候,这说明系统内存不够用了,处理速度会各种慢,虽然服务暂时可用,但很慢!!!,分分钟和死掉没啥区别。
- 什么时候用swap, 什么时候不该用。结论是在内存勉强够用的情况下,可用使用swap,其它情况下不需要使用。内存不够的情况下,合理的做法是加内存。内存够的情况下swap没啥用处,除非需要休眠,但是服务器当然是24小时工作的,即便是内存泄露,让内核触发OOM Killer,重启也好过,服务变得很慢,处于假死状态。
page cache & buffer cache
cache和buffer都是对接两个速度不对称的设备一块区域,一般来说,cache是加速读,buffer是加速写的(处理数据产生的速度 > 数据写入的速度,中间加个buffer使用数据写入均匀化,每次写入更多的数据)。page cache和buffer cache都是存储到内存当中,page cache缓存的是文件系统中的内容,用来加速文件的读取,而buffer cache缓存的是块设备中内容,加速对块设备的写入。最后,cache&buffer,这两个是好东西,对于对接两个速度不对称的系统都可以用到。
这里需要了解一下的:
- 当系统中free的内存很小时,不代表真的内存不够用,当free的内存很小,buffer cache和page cache也都很小,此时才代表系统内存不够用了。
- 读写文件的路径: 读—> page cache —> buffer cache —> 磁盘 写—> page cache —> buffer cache —> 磁盘
- 可以手动的将page cache释放掉。先刷新cache中的内存到磁盘,再清除
仅清除页面缓存(一般用这个): sync; echo 1 > /proc/sys/vm/drop_caches. 除目录项和inode: sync; echo 2 > /proc/sys/vm/drop_caches 清除页面缓存,目录项和inode: sync; echo 3 > /proc/sys/vm/drop_caches
Oom killer
当系统内存不够时,将会触发oom killer通过一定的算法选取一个进程来杀掉。注意了!!!不是内存使用最高的进程会被杀掉
这个需要了解的是
- 算法的权重与分数:/proc/[pid]/oom_adj, [-17, 15] -17表示禁止被kill(可以通过写入-17来达到禁止杀死的目的), 数字越大代表越容易被杀掉。分数: /proc/[pid]/oom_score 根据oom_adj算出来的,内部不够时选最高分的来杀掉。
- 查看被oom killer死掉的log: dmesg | grep -i 'killed process'
打印出分数最高的进程的shell脚本
#!/bin/bash
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
printf "%2d %5d %s\n" \
"$(cat $proc/oom_score)" \
"$(basename $proc)" \
"$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10
内存碎片
外部碎片:未被分配的内存,由于太多零碎的不连续小内存,无法满足当前较大内存的申请要求
原因:频繁的分配与回收物理页导致大量的小块内存夹杂在已分配页面中间内部碎片:已经分配的内存,却不能被利用的内存空间
缘由:所有内存分配必须起始可被4、8或16(体系结构决定)整除的地址或者MMU分页机制限制
实例:请求一个11Byte的内存块,系统可能会分配12Byte、16Byte等稍大一些的字节,这些多余空间就产生碎片
关于linux内存的好书or好文
各种必要的基础知识详细讲解
偏向底层地讲解内存分配的操作过程
网络
套接字
套接字是一个块内存空间,用来存放控制通信操作的控制信息(例如:通信对象的IP , 端口,状态等)。由于是存放控制信息的抽象,所以不同的协议相关的控制信息是不一样的。
TCP
很细节的协议,我觉得非必要的情况下,不需要进行深入的了解。需要很明确了解的是: 网络通信是不可靠的,而 TCP是可靠的通信协议,为了保证可靠,TCP协议设计得非常复杂,例如:连接时的三次握手,通信时的应答重发机制,断开时的四次握手等等。下面是我觉得有必要去了解的。
需要了解的
协议头的重要字段
- 序号:相当于从开头的第几个字节(初始序号随机计算)
- ACK号:初始序号+接收到的总的字节数+1
- 窗口大小:接收buffer的大小(高效的利用等待ACK号的时间,发发发,只要接收buffer还有空间,用于加速传输)
- 控制位: ACK(成功接收), SYN(建立连接), FIN(端口连接)
- 校验和: 检查是否出错
整个通信的大体过程
创建套接字
主要进行的操作是分配内存空间,写入初始状态
连接
确认通信双方的IP&端口,通信窗口的大小(接受缓存的大小),初始序号。另外的,三次握手主要是为了防止服务端接收到连接请求后,客户端已经关闭了, 若不进行服务端到客户单的确认就进行连接,就造成无畏的连接,进而造成资源浪费。
收发
这一块比较有趣值得借鉴的是,提升收发效率的机制。例如:
发送buffer的刷新机制: 用buffer来连接两个速度不一致的通信对象。既然是buffer必然也是有刷新机制,TCP协议采用的是:长度+时间机制。长度机制是指当buffer满了才刷新(发送出去),时间机制是指超过一定的时间将buffer中的数据刷新,两者相互补充,如果数据一来就发送,就会发送出大量的小包,增加传输次数,效率降低,但是如果发送的频率低,每次都等到够数据量才发送,就造成了无畏的等待。
高效利用等待ACK号的机制: 如果每次都发送一个包,就要等到接收到应答包再发送下一个包,这等待的时间就白白浪费掉了,那群大牛们就想出了,在接收端设置buffer(就是指窗口),当buffer没有被填满就可以继续发发发,然后将多次应答合并成一个应答。应答的主要作用是确认是否丢包,是否出错等,而这个主要是通过序号+已经接收到的数据 + 校验和确认的,所以可以多个应答信号合并。
根据网络包平均返回时间调整ACK号的等待时间:如果等待一段时间后没有收到,会将包重发,由于网络传输又快有慢,这意味着并不能设置一个固定值,那群大牛就想出了根据网络包平均返回时间调整ACK号的等待时间的机制。
还有一块比较有趣的,当server接收到一个连接请求,协议栈会给等待的套接字复制一个副本,然后客户端的套接字和复制出来的套接字相交互,这意味着会有很多端口一样的套接字。问题来了,当服务器端收到客户端的数据后,怎么找到和自己建立连接的套机字?答案是:客户端IP+端口+服务器端IP+端口,来找到相对于的套接字。
断开连接
这个我觉得没什么特别需要了解的。需要知道的:当主动断开方进入socket进入TIME_WAIT状态后,需要经过一定时间(这个是一个固定时间,可以改)才会将套接字删除,主要是为了误操作。例如,当客户端发送FIN信号—>服务器端返回ACK号 —> 服务器端发送FIN信号 —> 客户端返回ACK号,若在客户端返回的ACK号,由于网络问题,服务器端没有收到,服务器端会重新发送FIN号,如果马上删除,相同的端口分配给了新的应用程序,此时就将新的套接字给端口开了。
套接字状态的变化
客户端: CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
服务器端: CLOSED->LISTEN->SYN_RCVD->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
重点关注的状态是: ESTABLISHED, TIME_WAIT, LAST_ACK。
UDP
特点是无连接,直接发发发。因此,资源消耗小,速度相对于TCP来说更快,但是丢包严重。所以它适用于发送的数据特点:
- 短数据。例如,一个包就可以发完的,即便出错了应用程序也能知道,重发就好。例如DNS查询(DNS默认是UDP ,当然可以改为TCP)。
- 无需重发,或者重发了也没什么用的场景,例如直播时的音频,视频,重发了也没什么意义。
DNS
DNS服务器提供通过域名查询IP地址的服务,还提供的其它服务,但主要是域名解析。记录域名到IP的映射的类型叫A记录。
需要了解的
域名映射的存储与域名的递归搜索
域名存储:DSN服务器中的映射信息是按照域名以分层次的结构来保存的,一个域作为一个整体存放在DNS服务器中,上一级也可能会缓存了下一级的域名映射。例如,www.api.test.com ,以.来做分割,越靠右层级越高(隐藏了一个根域/)。每一个域都作为一个整体存储并向上层机的域注册存储当前域的服务器的IP,因此在本域就能找到本域所又的映射,没有该映射就提供可以找到该域映射的DNS服务器的IP。
递归搜索: 这个语言不好描述,这个直接上图说明
查询顺序
DNS的查询结果各个阶段都有缓存,查询顺序是这样的:客户端—>浏览器缓存→系统缓存→路由器缓存→ISP DNS 缓存→递归搜索
一个域名映射多个IP时的处理
当一个域名映射有多个IP时,通过轮询算法得到一个ip返回给客户端。这个就是最简单的负载均衡方案。缺点:如果其中一台服务器挂掉了,DNS然后会返回,现在的浏览器返回的IP地址失败了,会尝试再次请求获取第二个IP地址。
CDN与DSN
CDN作为缓存服务器,本质上是一个代理,此时CDN服务器的IP将代替web服务器注册到DNS服务中。
防火墙与DNS
大家都知道,在中国大陆是访问不了google的,做个限制的其中一个简单的做法就是当对www.google.com这个域名解析时,返回一个错误的IP地址,这样就限制了对google的访问。
防火墙
防火墙有好几种,这里主要需要了解的是包过滤的方式。包过滤的主要思路是指允许指定的包通过,包括发送和接收,通对各种协议包的头部信息进行过滤。例如,TCP协议的请求头中,有客户端的IP,端口信息,有服务器端的IP,端口信息,通过SYN和ACK控制位,还知道是否是连接请求。有了这些信息就可以制定相对应的过滤规则了,只能对IP为xxx的通过连接请求,只开放xxx端口等等。云服务提供商都会提供相应的设置界面设置过滤规则,在机器中也可以通过iptables来自己设置。额外的,可以通过iptable来搭建透明代理,将请求都转发到代理中。
关于网络的好书or好文
网络是怎么连接的: 上面关于网络的知识大部分都是出自于这本书的知识点的整理,非常不错的一本科普级别的书,适合仅仅是像我只需稍微了解一下网络的人看
CPU
CPU利用率和CPU负载没有关系,两个是不同维度的东西。
cpu利用率
显示的是程序在运行期间实时占用的CPU百分比。
各个CPU状态
us: 用户空间占用CPU百分比
sy: 内核空间占用CPU百分比
ni: 用户进程空间内改变过优先级的进程占用CPU百分比
id: 空闲CPU百分比
wa: 等待输入输出的CPU时间百分比
hi: 硬件中断
si: 软件中断一般来说,主要关注的是us, sy, id, wa, 例如当wa过高,此时就表示网络或者是硬盘出现了性能问题了。
cpu负载
一定时间内处于就绪状态的平均进程数,一定时间一般是1分钟,5分钟,15分钟。当负载大于CPU核数时,这意味着有的进程需要等待,不能及时的分配到CPU,因此总体性能就会下降。推荐的CPU的平均负载是小于CPU个数X核数X0.7。一般创业公司经常是一台服务器,部署多个服务,突然有一个服务的负载突然增加,CPU负载超过核数后,各个服务的整体性能就会下降。
权限
linux的权限是基于用户来组织的,用户划分为:所有者,用户组,其它人。权限划分为:读(r),写(w),执行(x),读写执行对于文件和目录的含义是不同的。刚开始玩linux会经常遇到各种权限问题
需要说明一下的
- cp后,文件属性默认是为系统创建该类型文件的默认属性,可以加-P 保持和原来一样的属性
- 程序对文件的各种增删改读和允许该程序的用户有关
- 子文件夹的权限与父文件夹的权限无关,不具继承关系
文件
r: 可读取文件的内容
w: 可修改文件的内容
x: 可被系统执行
目录
r: 可查看目录下的文件信息
w: 具有更改改目录结构的权限,这里的更改是指,增,删,重命名,移动
x: 是否允许进入该目录
进程
一点基础知识
进程在内存中的组成
代码段:程序代码数据
栈段:存储函数的放回地址,函数的参数,局部变量等
数据段:
- data: 初始化数据,例如静态变量
- BSS: 存储初始化为0的数据
- 堆:使用malloc等函数,动态分配的内存
task_struct结构:linxu用于管理进程的数据结构,里面记录了进程的很多信息,例如:进程的状态,调度信息,进程号,打开的文件, 堆栈信息等等。
进程号: 用于标识一个进程
INIT进程: 它是内核启动的第一个用户级进程,进程号为1
父进程: 创建子进程的进程,会承担子进程的资源的回收,每个进程都有父进程,idle进程除外
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,这些进程没有了爸爸所以就叫孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成一些资源的回收工作。它没啥危害。
僵死进程:进程已经退出,但父进程没有进行资源(主要是task_struct结构)回收。一般来说,肯定是有短暂的时间是处于这个状态,但是如果父进程因为bug等原因,不回收子进程的资源,这会导致资源的泄露,例如没那么多进程号可以分配。当进程处于僵尸进程,可以kill掉父进程,此时僵尸进程将会成为孤儿进程,由INIT进程回收。
进程组: 一个或多个进程的集合,组长进程的进程号等于进程组ID,进程组的生存周期是进程组中生存最久的那个进程,因此组长进程退出后进程组任然在
会话: 一个或多个进程组的集合。例如,登录一个shell,此时就是一个会话,包含了会话首进程,一个前台进程组,多个后台进程组。前台进程组的进程特点就是,可以向终端设备进行读写操作,而后台进程组的进程只能是向终端设备进行写操作。
守护进程: 特点独立于控制终端,在后台运行。独立与终端环境,这意味着需要和启动它的进程运行环境相隔离,例如会话,控制终端,进程组,文件描述符,文件权限掩码,工作目录等。很多服务类的进程都是以守护进程的形式存在的,例如crond,redis, ngnix等。
信号: 进程之间相互传递消息的一种方法,全称为软中断信号。进程收到信号后的处理:1. 进行相关的处理 2. 忽略 3. 安装系统默认值去处理。kill -l 可以看到所有的信号,本质上就是一个约定俗成的整数。
进程使用的系统资源的限制:进程可使用的系统资源,例如,打开文件的个数是有所限制的。在cat /proc/pid/limits文件可以看到相关的限制。存在感比较强的限制是最大文件打开数。
进程优先级:进程优先级高的可以获得更多的cpu运行时间。这个值越小,表示进程”优先级”越高,而值越大“优先级”越低, -20至19共40个级别。只有root权限才能更改为负数级别。
进程的生命周期与状态变化
从进程的生命周期的角度可以很好的了解进程,进程的生命周期:创建—>执行—>终止—>删除,每一步都涉及到一些进程相关的知识。
创建
进程的创建主要是调用系统fork()来创建一个进程
这里需要了解的是
- fork()函数调用之后,在父进程会返回子进程的进程号,子进程返回0
- 子进程与父进程共享代码段,复制了服进程的栈段和数据段,也就是说拥有自己独立的栈段和数据段,但是一开始只是逻辑上的拥有,而非物理上的拥有。如果每次都是完全拷贝进程的栈段和数据段,那么系统开销就很大,不会那么傻。为了提高效率,其实只是子进程拥有自己独立的虚拟内存空间,映射的物理内存还是和父进程共享,当子进程中的数据有所改变才会分配新的物理空间。
执行—>终止—>删除
这里主要是涉及到了进程运行时状态的变化
进程的状态有:
R(TASK_RUNNING): 运行状态 | 在运行队列中等待
S(TASK_INTERRUPTIBLE): 可中断的睡眠状态。可中断其实就是指,当某条件满足后可以被唤醒,也就是我们程序员见惯的阻塞,例如: 等待IO,sleep, 等待信号等待。
D(TASK_UNINTERRUPTIBLE): 不可中断的睡眠状态,此时进程是不可以被中断的,这意味着此时的进程不会响应各种异步信号,例如,不会响应SIGKILL信号。主要意义在于,内存的某些处理流程不能被打断。额外的,TASK_UNINTERRUPTIBLE状态总是非常短暂的,通过ps命令基本上不可能捕捉到。
T(TASK_STOPPED or TASK_TRACED): 暂停状态或者跟踪状态。这里需要分开说。
- TASK_STOPPED状态: 当向进程发送SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU信号,进程响应后(TASK_UNINTERRUPTIBLE状态不会响应)会进入该状态,发送SIGCONT信号,又会恢复到TASK_RUNNING状态。
- TASK_TRACED状态: 进程调试时处于的状态和TASK_STOPPED类似,主要区别在于不会响应SIGCONT。
Z(TASK_DEAD - EXIT_ZOMBIE): 僵尸状态,当子进程退出,但父进程没有将子进程的资源回收时处于的状态。进程退出后,进程占用的资源就会被回收,例如栈段,数据段等,但是task_struct结构并没有被回收,这个需要父进程来回收。我猜这样做的主要原因是,父进程可能会需要到子进程的一些信息。
状态变化如下图
一些重要的配置文件
登录读取的配置文件
读取顺序:/etc/environment -> /etc/profile ->/etc/bashrc-> (~/.bash_profile | ~/.bash_login | ~/.profile) -> ~/.bashrc -> ~/.bash_logout
各个配置文件的作用
/etc/environment: 一般是配置一下基本的环境变量,例如,系统当前语言变量
/etc/profile: 此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行。注意了,是用户登录时执行的,如su 采用non-loin模式,将不会读取该文件。
~/.bash_profile | ~/.bash_login | ~/.profile: 这三个都是属于用户级别的配置文件,依次读取,成功读取一个后面的将不再读取。
/etc/bashrc: 对所有用户打开新的shell都是生效的
~/.bashrc: 打开一个新的shell读取的配置文件
~/.bash_logout: 若是以login模式登录的,当每次退出系统(退出bash shell)时,执行该文件,一些退出操作可以写入改shell。
系统启动过程
不同版本的linux采用的init系统有所不同
sysvinit: 古老的东西
init进程 —> 读取/etc/inittab —>/etc/rc.d/rc.sysinit —> /etc/rc.d/rc[0-6].d —> /etc/rc.d/rc.local
不同发行版本会有点点区别,这里主要是需要了解
/etc/rc.d/rc[0-6].d: 存放了各个启动级别需要启动的服务,开启启动可以添加进去
/etc/rc.d/rc.local: 本地初始化程序, 可以自定义的开机启动
/etc/rc.d/init.d: 含有各类服务程序, /etc/rc.d/rc[0-6].d中的服务都软链到该目录。upstart:这个ubuntu在采用,具体的可以看看 UpStart
systemd: CentOS 7之后在采用,具体的可以看看 systemd
启动过程这个东西了解它,主要是为了弄一下开机开启的服务,上面的三种启动方式有所不同。但是都是有init.d目录,启动级别的rc[0-6]d的概念,rc.local文件,但目录或文件的路径有点不一样。