[toc]
ECS运维指南之Linux系统诊断
目录
Linux启动与登陆问题
-
超详细系统启动与登陆异常排查点
-
grub.conf 文件内容被清空了怎么办
-
巧妙利用 strace 查找丢失的文件
-
小心 PAM 不让你登录
-
CentOS 登录卡住的原因被我找到了
Linux 性能问题
-
找到 Linux 虚机 Load 高的"元凶"
-
OOM killer 是被谁触发的
-
我的服务器内存去哪儿了
-
CPU 占用不高但网络性能很差的一个原因
-
一次 IO 异常捕获过程
Linux 主机网络问题
-
ifdown ifup 命令丢失处理
-
网络不通? strace 二度出手
-
TIME_WAIT & CLOSE_WAIT 的讨论总结
-
一次网络抖动经典案例分析
Linux 系统服务与参数问题
-
4 个 limits 生效的问题
-
6 步排查 ss& netstat 统计结果不一样的原因
-
为什么明明内存很充足但是 java 程序仍申请不到内存
-
请不要忽略 min_free_kbytes 的设置
最后的彩蛋
- 某地区口罩项目架构演进及优化经验
1.Linux启动与登陆问题
说明
Linux 启动与登录问题是 ECS 的高频问题,而往往处理不及时会直接影响到用户业务的正常可持续运行,因此也变成了我们处理问题优先级的重中之重。 在云环境上影响 ECS 启动与登录的因素非常多,镜像、管控、虚拟化、底层硬件、系统与文件异常等等,本文仅从系统与文件本身角度,在大量处理经验的基础上,归纳总结了一些可能会引起系统启动与登录问题的排查点,并给出几个比较常见的典型案例来具体展示和说明。
1.1 超详细系统启动与登陆异常排查点
1.1.1 系统启动异常
-
部分 CentOS 系统启动黑屏,无异常报错的场景,可以 fsck 一下系统盘。
-
根分区空间满,以及 inode 数量耗尽。
-
升级内核或者从老的共享实例迁移到独享规格导致的启动异常。
- 手动注入驱动 (mkinitrd virtio 相关驱动 )。
- 修改 grub 的启动顺序,优先尝试使用老内核启动。
- /boot 目录下面内核的关联文件是否全(下面仅为 demo,不同系统内核版 本文件不一致,部分内核版本 boot 下的 i386 目录也是有用的)。
config-4.9.0-7-amd64
initrd.img-4.9.0-7-amd64
System.map-4.9.0-7-amd64
vmlinuz-4.9.0-7-amd64
-
/boot/grub/device.map
里面的 hda 改成 vda。 -
fstab/grub
中的 uuid 不对,可以直接修改为/dev/vda1
这种形式尝试。 数据盘分区异常加载起不来的场景,可以去注释 fstab 所有的行,添加类似下面的启动项尝试, 也适用于系统盘快照创建云盘挂载后,uuid 一致导致的启动异 常,改成非 UUID 的挂载即可。
/dev/vda1 / ext4 defaults 1 1
- 根目录权限 777(部分目录 777)也会导致启动异常,或者 ssh 登陆异常。
- 常见的关键目录缺失,有的是软链,也可以看看对应目录下面的文件数量(文件数量要跟同内核版本或者相差不大的版本对比),简单判断。
/bin /sbin /lib /lib32 /lib64 /etc /boot /usr/bin /usr/sbin /usr/lib / usr/lib64 等目录或文件缺失
for i in /bin /sbin /lib /lib32 /lib64 /etc /boot /usr/bin /usr/sbin / usr/lib /usr/lib64 ;do ls -l $i |wc -l ;done
- 影响启动的参数。 如果参数设置不当,是会导致启动异常的,如
/etc/sysctl.conf
以及检查rc.local
的配置,profile
的检查。
vm.nr_hugepages
vm.min_free_kbytes
- CentOS 的 selinux 需要关闭。
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of three two values:
# targeted - Targeted processes are protected,
# minimum - Modification of targeted policy. Only selected processes are protected.
# mls - Multi Level Security protection.
SELINUXTYPE=targeted
1.1.2 root 登陆异常
-
/etc/passwd
、/etc/shadow
( 用户名 root polikt dbus 等关键用户存在与否,文 件为空,格式乱(dos2unix)。 -
/etc/pam.d
目录下是否有为空的文件及参数设置是否正常, 如常见的system-auth passwd
。 -
/etc/pam.d
下面所有文件里面涉及的 so 文件,看看文件是否存在,是否为空/ usr/lib64/security
。 -
查
/etc /lib64
、/bin
、/sbin
、/usr/bin
、/usr/sbin
等目录有没有 size 为 0 的文件。 -
/etc/profile
、/etc/profile.d
( 打 印 列 表 )、/etc/bashrc
、/root/.bash_profile
、/root/.bashrc
等涉及登陆环境设置的文件是否异常。 -
注意内核版本,是否存在新 老内核,多更换几个内核试下。
-
系统日志也是一个比较重要的检查项(后面会介绍无法登陆怎么检查)。
-
Ubuntu 12.04 登陆异常在
/etc/login.defs
里面配置了错误的ERASECHAR
导致,恢复默认 0177 即可。
configuration error - cannot parse erasechar value
- 输入 root 后直接 login 失败三连,日志如下。
Feb 12 18:18:03 iZbp1cabe6lyx26ikmjie2Z login: FAILED LOGIN 1 FROM tty1 FOR root, Authentication failure
Feb 12 18:18:03 iZbp1cabe6lyx26ikmjie2Z login: FAILED LOGIN 2 FROM tty1 FOR (unknown), Authentication failure
Feb 12 18:18:03 iZbp1cabe6lyx26ikmjieZZ login: FAILED LOGIN SESSION FROM tty1 FOR (unknown), Authentication failure
找个同内核版本的机器对比发现没有 /etc/pam.d/login
。
rpm 包校验一下,确认 login 文件没了,手动创建一个,内容拷贝过来,好了。
[root@iZbp1cabe6lyx26ikmjie2Z pam.d]# rpm -V util-linux
missing c /etc/pam.d/login
[root@iZbp1cabe6lyx26ikmjie2Z pam.d]# rpm -ql util-linux|egrep -vi "gz|mo|share"
/etc/mtab
/etc/pam.d/chfn
/etc/pam.d/chsh
/etc/pam.d/login
/etc/pam.d/runuser
/etc/pam.d/runuser-l
/etc/pam.d/su
/etc/pam.d/su-l
-
/etc/ssh/sshd_config
相关参数如LoginGraceTime/Allowusers/PermitRootLogin
。 -
问题不好确认的时候, 可以将 shadow 密码字段清空, 看看登陆是否正常, 可以判断是否到密码验证阶段了。 之前有过一篇关于 ssh 问题排查的文档,可参考:ssh问题排查指南
1.1.3 系统登陆不进去了,不挂盘的情况下怎么操作?
上面的检查点很多是需要切换到另外的系统环境下去做检查,比如挂载 LiveCD 或者 chroot 切换;但对于使用 ECS 的用户来说,阿里云暂还未提供实例挂载 ISO 镜像的功能,那么如何进行上面的操作呢?可以借助阿里云新推出的卸载系统盘功能,可以把系统盘卸载掉,作为数据盘挂载到一个新的机器,这样就可以执行上面的检查了。
1.1.4 场景覆盖
1.1.4.1 Linux 系统常见问题诊断覆盖以下场景
- 常用端口是否正确监听(包括ssh端口、80、443)
- 常用端口安全组检测(包括ssh端口、80、443)
tcp_timestamps&tcp_tw_recycle
参数检测- cpu占用高分析
- fstab配置问题
- 软链 接缺失
- ssh相关文件权限异常(比如文件权限777)
- selinux启用检测
- hugepage参数检测
- iptables启用检测
/etc/security/limits.conf
配置检查/etc/passwd
等文件格式校验
1.1.4.2 Linux 系统常见启动问题修复覆盖以下场景
- 系统启动-fsck检测修复
- 系统启动-磁盘/inode占满
- 系统启动-系统文件/软链接检测
- 系统启动-根目录/ssh目录777
- 系统启动-fstab/grub校验
- 系统启动-selinux检测
- 系统启动-sysctl内核参数hugepage&minfree检测
- vnc登陆-sshdconfig配置检测
- vnc登陆-passwd/shadow文件格式&用户检测
- vnc登陆-pam.d文件&配置检测
- vnc登陆-相关目录0size文件检测
- vnc登陆-profile/bashrc检测
- vnc登陆-多内核检测
1.1.5 各种脚本
[OS离线修复脚本](https://public-beijing.oss-cn-beijing.aliyuncs.com/oo oos_noak.sh)
1.2 grub.conf 文件内容被清空了怎么办
简介
/boot/grub/grub.conf 被清空,系统启动就进入 grub 状态(CentOS 6.8)。
-
find /boot/grub/stage1
。 显示为(hd0,0)。 -
确认一下内核的具体版本
ls -l /boot
-
手动设置 grub 具体步骤如下
grub> root (hd0,0) # 是说跟分区在第一块硬盘的第 1 个分区 实际对于前面 的 find 出来的那个文件
grub> kernel /boot/vmlinuz-2.6.32-696.3.2.el6.x86_64 ro root=/dev/vda1 # 指明内核路径和根分区,注意 ro 是只读
grub> initrd /boot/initramfs-2.6.32-696.3.2.el6.x86_64.img # 指明 initramfs 路径启动系统加载驱动
grub> boot # 启动上面指定的系统,如果是 reboot 就等于重启整个系统了,刚才的设置就失效了如果没有报错的话,即可成功启动,进入到系统内部后需要继续支持。
-
mount -e remount,rw /
重新挂载分区为读写。 -
service network restart
。 如果提示 eth0 eth1 失败,ifconfig 看不到网卡 的话。 -
lsmod |grep net
。 看下 virtio_net 这个驱动有没有,如果没有的话(网卡报错基本都不会有)。 -
insmod /lib/modules/2.6.32-696.3.2.el6.x86_64/kernel/drivers/net/virtio_ net.ko
。 -
重启网络服务,嗨 ~ 网通了。
-
登陆 ssh, 找个同版本系统的 grub.conf,拷贝一份过来, 不然重启之后又进 grub 了。参考文章
1.3 巧妙利用 strace 查找丢失的文件
问题描述
客户反馈系统无法远程登陆,实际系统启动本身就有问题。
根据报错信息来看,是系统内读取 user 有问题,需要挂盘查看。
-
挂盘后 chroot 如下
ihave no name
,这里本身就是有问题了,说明系统内缺少了什么文件导致异常。root@debian:~# chroot/mnt
[ I have no name !@debian / ] # -
strace 跟踪一下 chroot 的过程,看下丢失的文件。
strace -F -ff -t -tt -s 256 -o ch.out chroot /mnt
grep -i "no such" ch.out.pid |grep "so" -
查看对应文件的关系(测试机补图)。
-
确认系统上丢了最终的
libnss_files-2.12.so
,尝试拷贝一个。ifconfig eth1 < 网卡 IP> netmask < 掩码地址 >
route add default gw < 网关 IP> -
此时已经可以上网了,去拷贝一个同版本的文件试试吧。
1.4 小心 PAM 不让你登陆
问题描述
ssh 可以登陆,管理终端无法登陆 root,提示 login in...
先通过 ssh 方式登录系统,查看登录日志是否有异常。
$ cat /var/log/secure
Jun 2 09:26:48 iZbp1begsz1x269nxhtip4Z login: FAILED LOGIN 1 FROM tty1 FOR root,Authentication failure
似乎是 login 验证模块的问题进一步查看对应的配置文件 /etc/pam.d/login
。
$ cat /etc/pam.d/login
#%PAM-1.0
auth required pam_succeed_if.so user != root quiet
auth [user_unknown=ignore success=ok ignore=ignore default=bad] pam_ securetty.so
auth substack system-auth
auth include postlogin
account required pam_nologin.so
account include system-auth
password include system-auth
# pam_selinux.so close should be the first session rule
session required pam_selinux.so close
session required pam_loginuid.so
session optional pam_console.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session required pam_selinux.so open
session required pam_namespace.so
session optional pam_keyinit.so force revoke
session include system-auth
session include postlogin
-session optional pam_ck_connector.so
其中一行的作用为禁止本地登录,可以将其注释掉即可。
auth required pam_succeed_if.so user != root quiet
1.5 CentOS 登陆卡住的原因被我找到了
问题描述
系统登陆卡住,需要 ctrl +c 才能进去,如图。
如果一直等的话,会提示如下截图:
原因
/etc/profile 里面有 source /etc/profile 引起死循环,注释即可。
2.Linux 性能问题
说明
Linux 性能问题的排查和处理一直是系统管理和运维人员的"心头之患", CPU 负载高但找不到消耗大的进程;系统出现 OOM(Out of Memory)只会一味地增大内存容量,而没有很好地理解和分析问题背后产生的根因。而这些都对线上业务的可靠和稳定性提出了挑战。本文将阿里云售后遇到的较为常见的几个系统性能问题进行展开分析,并给出一些合理的改进和优化方案。
2.1 找到 Linux 虚机 Load 高的"元凶"
问题描述
有客户反馈他们的一台 ECS 周期性地 load 升高,他们的业务流量并没有上升,需要 我们排查是什么原因造成的,是否因为底层异常?
要弄清 Linux 虚机 load 高,我们要搞清楚 Linux top 命令中 Load 的含义。
2.1.1 Load average 的值从何而来
在使用 top 命令检查系统负载的时候,可以看到 Load averages
字段,但是这个字段并不是表示 CPU 的繁忙程度,而是度量系统整体负载。
Load averages 采样是从 /proc/loadavg
中获取的:
0.00 0.01 0.05 1/161 29703
每个值的含义依次为:
lavg_1 (0.00) 1- 分钟平均负载
lavg_5 (0.01) 5- 分钟平均负载
lavg_15(0.05) 15- 分钟平均负载
nr_running (1)
在采样时刻,运行队列的任务的数目,与 /proc/stat 的 procs_running 表示相同意思, 这个数值是当前可运行的内核调度对象(进程,线程)。
nr_threads (161)
在采样时刻,系统中活跃的任务的个数(不包括运行已经结束的任务),即这个数值表示当前存 在系统中的内核可调度对象的数量。
last_pid(29703)
系统最近创建的进程的 PID,包括轻量级进程,即线程。
假设当前有两个 CPU,则每个 CPU 的当前任务数为 0.00/2=0.00
如果你看到 load average 数值是 10, 则表明平均有 10 个进程在运行或等待状态。 有可能系统有很高的负载但是 CPU 使用率却很低,或者负载很低而 CPU 利用率很高,因为这两者没有直接关系。源码中对于这一块的说明
static int loadavg_proc_show(struct seq_file *m,void *v)
{
unsigned long avnrun[3];
get_avenrun(avnrun,FIXED_1/200,0);
seq_printf(m,"%lu.%02lu %lu.%02lu %lu.%02lu %ld/%d %d\n",
LOAD_INT(avnrun[0]),LOAD_FRAC(avnrun[0]),
LOAD_INT(avnrun[1]),LOAD_FRAC(avnrun[1]),
LOAD_INT(avnrun[2]),LOAD_FRAC(avnrun[2]),
nr_running(),nr_threads,
task_active_pid_ns(current)->last_pid);
return 0;
}
Load 的计算函数:
static unsigned long
calc_load(unsigned long load,unsigned long exp,unsigned long active)
{
load *= exp;
load += active * (FIXED_1 - exp);
return load >> FSHIFT;
}
/*
* calc_load - update the avenrun load estimates 10 ticks after the
* CPUs have updated calc_load_tasks.
*/
void calc_global_load(void)
{
unsigned long upd = calc_load_update + 10;
long active;
if (time_before(jiffies,upd))
return;
active = atomic_long_read(&calc_load_tasks);
active = active > 0 ? active * FIXED_1 : 0;
avenrun[0] = calc_load(avenrun[0],EXP_1,active);
avenrun[1] = calc_load(avenrun[1],EXP_5,active);
avenrun[2] = calc_load(avenrun[2],EXP_15,active);
calc_load_update += LOAD_FREQ;
}
/*
* These are the constant used to fake the fixed-point load-average
* counting. Some notes:
* - 11 bit fractions expand to 22 bits by the multiplies: this gives
* a load-average precision of 10 bits integer + 11 bits fractional
* - if you want to count load-averages more often,you need more
* precision,or rounding will get you. With 2-second counting freq,
* the EXP_n values would be 1981,2034 and 2043 if still using only
* 11 bit fractions.
*/
extern unsigned long avenrun[]; /* Load averages */
extern void get_avenrun(unsigned long *loads,unsigned long offset,int shift);
#define FSHIFT 11 /* nr of bits of precision */
#define FIXED_1 (1<<FSHIFT) /* 1.0 as fixed-point */
#define LOAD_FREQ (5*HZ+1) /* 5 sec intervals */
#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */
#define CALC_LOAD(load,exp,n) \
load *= exp; \
load += n*(FIXED_1-exp); \
load >>= FSHIFT;
从这个函数中可以看到, 内核计算 load 采用的是一种平滑移动的算法,Linux 的系统负载指运行队列的平均长度, 需要注意的是:可运行的进程是指处于运行队列的进程, 不是指正在运行的进程。 即进程的状态是 TASK_RUNNING
或者 TASK_ UNINTERRUPTIBLE
。
Linux 内核定义一个长度为3的双字数组 avenrun,双字的低 11 位用于存放负载的小数部分,高 21 位用于存放整数部分。当进程所耗的 CPU 时间片数超过 CPU 在5秒内能够提供的时间片数时,内核计算上述的三个负载,负载初始化为 0。
假设最近 1、5、15 分钟内的平均负载分别为 load1、load5 和 load15,那么下一个计算时刻到来时,内核通过下面的算式计算负载:
load1 -= load1 - exp(-5 / 60) -+ n (1 - exp(-5 / 60 ))
load5 -= load5 - exp(-5 / 300) + n (1 - exp(-5 / 300))
load15 = load15 exp(-5 / 900) + n (1 - exp(-5 / 900))
其中,exp(x) 为 e 的 x 次幂,n 为当前运行队列的长度。
2.1.2 如何找出系统中 load 高时处于运行队列的进程
通过前面的讲解, 我们已经明白有可能系统有很高的负载但是 CPU 使用率却很低, 或者负载很低而 CPU 利用率很高,这两者没有直接关系,如何用脚本统计出来处于运行队列的进程呢?
每隔 1s 统计一次:
#!/bin/bash
LANG=C
PATH=/sbin:/usr/sbin:/bin:/usr/bin
interval=1
length=86400
for i in $(seq 1 $(expr ${length} / ${interval}));do
date
LANG=C ps -eTo stat,pid,tid,ppid,comm --no-header | sed -e 's/^ \*//' | perl -nE 'chomp;say if (m!^\S*[RD]+\S*!)'
date
cat /proc/loadavg
echo -e "\n"
sleep ${interval}
done
从统计出来的结果可以看到:
at Jan 20 15:54:12 CST 2018
D 958 958 957 nginx
D 959 959 957 nginx
D 960 960 957 nginx
D 961 961 957 nginx
R 962 962 957 nginx
D 963 963 957 nginx
D 964 964 957 nginx
D 965 965 957 nginx
D 966 966 957 nginx
D 967 967 957 nginx
D 968 968 957 nginx
D 969 969 957 nginx
D 970 970 957 nginx
D 971 971 957 nginx
D 972 972 957 nginx
D 973 973 957 nginx
D 974 974 957 nginx
R 975 975 957 nginx
D 976 976 957 nginx
D 977 977 957 nginx
D 978 978 957 nginx
D 979 979 957 nginx
R 980 980 957 nginx
D 983 983 957 nginx
D 984 984 957 nginx
D 985 985 957 nginx
D 986 986 957 nginx
D 987 987 957 nginx
D 988 988 957 nginx
D 989 989 957 nginx
R 11908 11908 18870 ps
Sat Jan 20 15:54:12 CST 2018
25.76 20.60 19.00 12/404 11912
注:R 代表运行中的队列,D 是不可中断的睡眠进程
在 load 比较高的时候,有大量的 nginx 处于 R 或者 D 状态,他们才是造成 load 上升的元凶,和我们底层的负载确实是没有关系的。
最后也给大家 share 一下查 CPU 使用率比较高的线程小脚本:
#!/bin/bash
LANG=C
PATH=/sbin:/usr/sbin:/bin:/usr/bin
interval=1
length=86400
for i in $(seq 1 $(expr ${length} / ${interval}));do
date
LANG=C ps -eT -o%cpu,pid,tid,ppid,comm | grep -v CPU | sort -n -r | head -20
date
LANG=C cat /proc/loadavg
{ LANG=C ps -eT -o%cpu,pid,tid,ppid,comm | sed -e 's/^ *//' | tr -s ' ' | grep -v CPU | sort -n -r | cut -d ' ' -f 1 | xargs -I{} echo -n "{} + " && echo '0'; } | bc -l
sleep ${interval}
done
fuser -k $0
2.2 OOM killer 是被谁触发的
2.2.1 问题描述
用户发现自己的服务器 CPU 在某一时刻陡然升高,但从监控上看,同一时刻的业务量却并不高,客户怀疑是云服务器有问题,希望技术支持团队予以解决。
经过我们的排查, 发现 cpu 的两次间歇飙高是由于客户系统当时发生了 OOM(out of memory)的情况, 并触发了 oom-killer 造成的。 但客户并不接受这个结论, 认为是云服务器的异常导致了 cpu 飙高,而 cpu 的升高又导致了 oom 情况的发生。也就是对于 cpu 升高和 oom 谁为因果这件事上,客户和我们持完全相反的态度。
下面我们将通过对 oom 时系统日志的解读来说明 cpu 升高和 oom 之间的因果关系。
2.2.2 知识点梳理
2.2.2.1 预备知识
在解读日志之前,我们先回顾一下 linux 内核的内存管理。
2.2.2.1.1 几个基本的概念
(1) Page 页 处理器的最小 '寻址单元' 是字节或者字,而页是内存的 '管理单元'。
(2) Zone 区
(a) 区存在的原因:
-
有些硬件设备只能对特定的内存地址执行 DMA(direct memory access) 操作。
-
在一些架构中,实际物理内存是比系统可寻址的虚拟内存要大的,这就导致有些物理内存没有办法被永久的映射在内核的地址空间中。
-
区的划分也是直接以上面两个原因为依据的。
(b) 区的种类
-
ZONE_DMA
这个区包含的 page 可以执行 DMA 操作。这部分区域的大小和 CPU 架构有关,在 x86 架构中,该部分区域大小限制为 16MB。 -
ZONE_DMA32
类似于ZOME_DMA
, 这个区也包含可以执行 DMA 操作的page。该区域只存在于64位系统中,适合32位的设备访问。 -
ZONE_NORMAL
这个区包含可以正常映射到地址空间中的 page,或者说这个区包含了除了 DMA 和 HIGHMEM 以外的内存。 许多内核操作都仅在这个区域进行。 -
ZONE_HIGHMEM
这个区包含的是 high memory,也就是那些不能被永久映射到内核地址空间的页。 -
32位的 x86 架构中存在三种内存区域,
ZONE_DMA
,ZONE_NORMAL
,ZONE_HIGHMEM
。根据地址空间划分的不同,三个区域的大小不一样:-
1G 内核空间 /3G 用户空间
区域 内存范围 ZONE_DMA < 16M ZONE_NORMAL 16M ~ 896M ZONE_HIGHMEM > 896M -
4G 内核空间 /4G 用户空间
区域 内存范围 ZONE_DMA < 16M ZONE_NORMAL 16M ~ 3968M ZONE_HIGHMEM > 3968M 64位的系统由于寻址能力的提高, 不存在 highmem 区, 所以64位系统中存在的区有 DMA,DMA32 和 NORMAL 三个区。
区域 内存范围 ZONE_DMA < 16M ZONE_DMA32 16M ~ 4G ZONE_NORMAL > 4G
-
2.2.2.1.2 内核分配内存的函数
下面是内核分配内存的核心函数之一,它会分配2的 order 次方个连续的物理页内存,并将第一页的逻辑地址返回。
unsigned long __get_free_pages(gfp_t gfp_mask,unsigned int order)
内核空间的内存分配函数和用户空间最大的不同就是每个函数会有一个 gfp_ mask
参数。
其中 gfp 代表的就是我们上面的内存分配函数 __get_free_pages()
。
gfp_mask 可以分成三种:行为修饰符(action modifier), 区修饰符(zone modifier)和类型(type)。
-
行为修饰符是用来指定内核该如何分配内存的。 比如分配内存时是否可 以进行磁盘 io,是否可以进行文件系统操作,内核是否可以睡眠(sleep) 等等。
-
区修饰符指定内存需要从哪个区来分配。
-
类型是行为修饰符和区修饰符结合之后的产物。在一些特定的内存分配场 合下, 我们可能需要同时指定多个行为修饰符和区修饰符, 而 type 就是 针对这些固定的场合,将所需要的行为修饰符和区修饰符都整合到了一起, 这样使用者只要指定一个 type 就可以了。
不同 type 所代表的含义可以参看下面的表格:
2.2.2.2 日志解读
下面是从 oom killer 被触发到进程到被杀掉的 一个大概过程, 我们来具体看 一下。
nginx invoked oom-killer: gfp_mask=0x200da,order=0,oom_score_adj=0
nginx cpuset=6011a7f12bac1c4592ce41407bb41d49836197001a0e355f5a1d9589e4001e42 mems_allowed=0
CPU: 1 PID: 10242 Comm: nginx Not tainted 3.13.0-86-generic #130-Ubuntu
Hardware name: Xen HVM domU,BIOS 4.0.1 12/16/2014
0000000000000000 ffff880070611a00 ffffffff8172a3b4 ffff88012af6c800
0000000000000000 ffff880070611a88 ffffffff8172495d ffffffff81069b76
ffff880070611a60 ffffffff810ca5ac ffff88020fff7e38 0000000000000000
Node 0 DMA free:15908kB min:128kB low:160kB high:192kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15992kB managed:15908kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_ reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_ unreclaimable? yes
lowmem_reserve[]: 0 3746 7968 7968
Node 0 DMA32 free:48516kB min:31704kB low:39628kB high:47556kB active_ anon:3619272kB inactive_anon:216kB active_file:556kB inactive_file:1516kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:3915776kB managed:3836724kB mlocked:0kB dirty:4kB writeback:0kB mapped:324kB shmem:1008kB slab_reclaimable:67136kB slab_unreclaimable:67488kB kernel_ stack:1792kB pagetables:14540kB unstable:0kB bounce:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:7365 all_unreclaimable? yes
lowmem_reserve[]: 0 0 4221 4221
Node 0 Normal free:35640kB min:35748kB low:44684kB high:53620kB active_ anon:4019124kB inactive_anon:292kB active_file:1292kB inactive_file:2972kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:4456448kB managed:4322984kB mlocked:0kB dirty:24kB writeback:4kB mapped:1296kB shmem:1324kB slab_reclaimable:81196kB slab_unreclaimable:83432kB kernel_ stack:3392kB pagetables:20252kB unstable:0kB bounce:0kB free_cma:0kB writeback_tmp:0kB pages_scanned: 7874 all_unreclaimable? yes
lowmem_reserve[]: 0 0 0 0
Node 0 DMA: 1*4kB (U) 0*8kB 0*16kB 1*32kB (U) 2*64kB (U) 1*128kB (U) 1*256kB (U) 0*512kB 1*1024kB (U) 1*2048kB (R) 3*4096kB (M) = 15908kB Node 0 DMA32: 1101*4kB (UE) 745*8kB (UEM) 475*16kB (UEM) 263*32kB (EM) 88*64kB (UEM) 25*128kB (E)12*256kB (EM) 6*512kB (E) 7*1024kB (EM) 0*2048kB 0*4096kB = 48524kB
Node 0 Normal: 5769*4kB (EM) 1495*8kB (EM) 24*16kB (UE) 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 35420kB
Node 0 hugepages_total=0 hugepages_free=0 hugepages_surp=0 hugepages_ size=2048kB
2273 total pagecache pages
0 pages in swap cache
Swap cache stats: add 0,delete 0,find 0/0
Free swap = 0kB
Total swap = 0kB
2097054 pages RAM
0 pages HighMem/MovableOnly
33366 pages reserved
[ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
[ 355] 0 355 4868 66 13 0 0
upstart-udev-br
[ 361] 0 361 12881 145 28 0 -1000
systemd-udevd
[ 499] 0 499 3814 60 13 0 0
upstart-socket-
[ 562] 0 562 5855 79 15 0 0
rpcbind
[ 644] 106 644 5398 142 16 0 0 rpc.
statd
[ 775] 0 775 3818 58 12 0 0
upstart-file-br
...(此处有省略)
[10396] 104 10396 21140 12367 44 0 0 nginx
[10397] 104 10397 21140 12324 44 0 0 nginx
[10398] 104 10398 21140 12324 44 0 0 nginx
[10399] 104 10399 21140 12367 44 0 0 nginx
Out of memory: Kill process 10366 (nginx) score 6 or sacrifice child
Killed process 10366 (nginx) total-vm:84784kB,anon-rss:49156kB,filerss:520kB
先来看一下第一行,它给出了 oom killer 是由谁触发的信息。
nginx invoked oom-killer: gfp_mask=0x200da,order=0,oom_score_adj=0
order=0 告诉我们所请求的内存的大小是多少, 即 nginx 请求了2的0次方这么多个 page 的内存,也就是一个 page,或者说是 4KB。 gfp_mask 的最后两个 bit 代表的是 zone mask,也就是说它指明内存应该从哪个区来分配。
Flag value Description
0x00u 0 implicitly means allocate from ZONE_NORMAL
__GFP_DMA 0x01u Allocate from ZONE_DMA if possible
__GFP_HIGHMEM 0x02u Allocate from ZONE_HIGHMEM if possible
(这里有一点需要注意,在 64 位的 x86 系统中,是没有 highmem 区的,64 位系统中的 normal 区就对应上表中的 highmem 区。)
在本案例中,zonemask是2,也就是说 nginx 正在从 zone - normal(64 位 系统)中请求内存。
其他标志位的含义如下:
#define __GFP_WAIT 0x10u /* Can wait and reschedule? */
#define __GFP_HIGH 0x20u /* Should access emergency pools? */
#define __GFP_IO 0x40u /* Can start physical IO? */
#define __GFP_FS 0x80u /* Can call down to low-level FS? */
#define __GFP_COLD 0x100u /* Cache-cold page required */
#define __GFP_NOWARN */ 0x200u /* Suppress page allocation failure warning */
#define __GFP_REPEAT 0x400u /* Retry the allocation. Might fail */
#define __GFP_NOFAIL 0x800u /* Retry for ever. Cannot fail */
#define __GFP_NORETRY 0x1000u /* Do not retry. Might fail */
#define __GFP_NO_GROW 0x2000u /* Slab internal usage */
#define __GFP_COMP 0x4000u /* Add compound page metadata */
#define __GFP_ZERO 0x8000u /* Return zeroed page on success */
#define __GFP_NOMEMALLOC 0x10000u /* Don’t use emergency reserves */
#define __GFP_NORECLAIM allocation 0x20000u /* No realy zone reclaim during */
所以我们当前这个内存请求带有这几个标志:GFP_NORECLAIM
,GFP_FS
, GFP_IO
,GFP_WAIT
,都是比较正常的几个标志,那么我们这个请求为什么 会有问题呢?继续往下看,可以看到下面的信息:
Node 0 Normal free:35640kB min:35748kB low:44684kB high:53620kB active_ anon:4019124kB inactive_anon:292kB active_file:1292kB inactive_file:2972kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:4456448kB managed:4322984kB mlocked:0kB dirty:24kB writeback:4kB mapped:1296kB shmem:1324kB slab_reclaimable:81196kB slab_unreclaimable:83432kB kernel_ stack:3392kB pagetables:20252kB unstable:0kB bounce:0kB free_cma:0kB writeback_tmp:0kB pages_scanned: 7874 all_unreclaimable? yes
可以看到 normal 区 free 的内存只有 35640KB,比系统允许的最小值(min)还要低,这意味着 application 已经无法再从系统中申请到内存了,并且系统会开始启动 oom killer 来缓解系统内存压力。
这里我们说一下一个常见的误区,就是有人会认为触发了 oom killer 的进程就是问题的罪魁祸首,比如 我们这个例子中的这个 nginx 进程。其实日志中 invoke oom killer 的这个进程有时候可能只是一个受害者, 因为其他应用/进程已将系统内存用尽, 而这个 invoke oomkiller 的进程恰好在此时发起了一个分配内存的请求而已。 在系统内存已经不足的情况下, 任何一个内存请求都可能触发 oom killer 的启动。
oom killer 的启动会使系统从用户空间转换到内核空间。内核会在短时间内进行大量的工作, 比如计算每个进程的 oom 分值, 从而筛选出最适合杀掉的进程。 我们从日志中也可以看到这一筛选过程:
[ pid ] uid tgid total_vm rss nr_ptes swapents oom_score_adj name
[ 355] 0 355 4868 66 13 0 0
upstart-udev-br
[ 361] 0 361 12881 145 28 0 -1000
systemd-udevd
[ 499] 0 499 3814 60 13 0 0
upstart-socket-
[ 562] 0 562 5855 79 15 0 0
rpcbind
[ 644] 106 644 5398 142 16 0 0 rpc.
statd
[ 775] 0 775 3818 58 12 0 0
upstart-file-br
...
[10396] 104 10396 21140 12367 44 0 0 nginx
[10397] 104 10397 21140 12324 44 0 0 nginx
[10398] 104 10398 21140 12324 44 0 0 nginx
[10399] 104 10399 21140 12367 44 0 0 nginx
本例中,一个 nginx 进程被选中作为缓解内存压力的牺牲进程:
Out of memory: Kill process 10366 (nginx) score 6 or sacrifice child
Killed process 10366 (nginx) total-vm:84784kB,anon-rss:49156kB, file-rss:520kB
整个过程进行的时间很短,只有毫秒级别,但是工作量/计算量很大,这就导致了 cpu 短时间内迅速飙升, 出现峰值。 但这一切工作都由内核在内核空间中完成,所以用户在自己的业务监控数据上并不会看到业务量的异常。这些短时间升高的 cpu 是内核使用的,而不是用户的业务。
本例中客户只是偶尔看到这个现象,且业务并没有受到影响。我们给客户的建议是分析业务内存需求量最大值,如果系统已经没有办法满足特定时段业务的内存需求, 建议用户升级内存来避免 oom 的情况发生, 因为严重的 oom 情况是可能引发系统崩溃的。
2.3 我的服务器内存去哪儿 了
背景
收到报警,系统的内存使用率触发阈值(部分图是后补的)。
登陆系统,使用命令(top 按M)查看内存分配。
使用命令free -m
查看内存使用情况
使用命令atop
看下内存分配(cat /proc/meminfo 也可以看到一些细化的内存使用信息)。
发现 cache 才 1.7G,slab 非常高,4.4G,slab 内存简单理解为是系统占用的。 使用 slabtop 继续分析。
看到 proc_inode_cache
使用的最多, 这个代表是 proc 文件系统的 inode 占用的。
使用命令ps -eLf
查进程,但是进程不多,再查线程,可以通过如下命令进行检查。得到如下的结果 :( 原图缺失,使用测试机查看到的截图来补充说明 )
计算 socket
$ ll /proc/22360/task/*/fd/ |grep socket |wc -l
140
计算一下有多少fd
$ ll /proc/22360/task/*/fd/ | wc -l
335
每个 socket 的 inode 也不一样。
当时看到的现场有几万个 fd, 基本全是 socket, 每个 inode 都是占用空间的, 且 proc 文件系统是全内存的。 所以我们才会看到 slab 中 proc_inode_cache 内存占用高。
建议
建议用户需要从程序上优化相关的 server 端 ~
2.4 CPU 占用不高但网络性能很差的一个原因
简介
我们经常碰到整体 cpu 不高,但是性能不佳的案例,这种案例往往跟 CPU 处理中断的核心跑满有关系,话不多说,我们来看看中断相关的案例。
2.4.1 什么是中断?
当一个硬件 ( 如磁盘控制器或者以太网卡 ),需要打断 CPU 的工作时,它就触发一个中断。该中断通知 CPU 发生了某些事情并且 CPU 应该放下当前的工作去处理这个事情。为了防止多个设置发送 相同的中断,Linux 设计了一套中断请求系统,使得计算机系统中的每个设备被分配了各自的中断号,以确保它的中断请求的唯一性。
从 2.4 内核开始,Linux 改进了分配特定中断到指定的处理器 ( 或处理器组 ) 的功能。 这被称为 SMP IRQ affinity
,它可以控制系统如何响应各种硬件事件。允许你限制或者重新分配服务器的工作负载,从而让服务器更有效的工作。
以网卡中断为例,在没有设置 SMP IRQ affinity
时,所有网卡中断都关联到 CPU0, 这导致了 CPU0 负载过高,而无法有效快速的处理网络数据包,导致了瓶颈。
通过 SMP IRQ affinity
,把网卡多个中断分配到多个 CPU 上,可以分散 CPU 压力, 提高数据处理速度。 但是 smp_affinity
要求网卡支持多队列, 如果网卡支持多队列则设置才有作用,网卡有多队列,才会有多个中断号,这样就可以把不同的中断号分配到不同 CPU 上,这样中断号就能相对均匀的分配到不同的 CPU 上。
而单队列的网卡可以通过 RPS/RFS 来模拟多队列的情况,但是该效果并不如网卡本身多队列 + 开启 RPSRFS 来的有效。
2.4.2 什么是 RPS/RFS
RPS(Receive Packet Steering)主要是把软中断的负载均衡到各个 cpu,简单来说,是网卡驱动对每个流生成一个 hash 标识, 这个 HASH 值得计算可以通过四元组来计算(SIP,SPORT,DIP,DPORT),然后由中断处理的地方根据这个 hash 标识分配到相应的 CPU 上去,这样就可以 比较充分的发挥多核的能力了。通俗点说就是在软件层面模拟实现硬件的多队列网卡功能,如果网卡本身支持多队列功能的话 RPS 就不会有任何的作用。该功能主要针对单队列网卡多 CPU 环境,如网卡支持多队列则可使用 SMP irq affinity
直接绑定硬中断。
由于 RPS 只是单纯把数据包均衡到不同的 cpu,这个时候如果应用程序所在的 cpu 和软中断处理的 cpu 不是同一个, 此时对于 cpu cache 的影响会很大, 那么 RFS (Receive flow steering)确保应用程序处理的 cpu 跟软中断处理的 cpu 是同一个,这样就充分利用 cpu 的 cache,这两个补丁往往都是一起设置,来达到最好的优化效果,主要是针对单队列网卡多 CPU 环境。
rps_flow_cnt
,rps_sock_flow_entries
, 参数的值会被进位到最近的2的幂次方值,对于单队列设备, 单队列的 rps_flow_cnt
值被配置成与rps_sock_flow_ entries
相同。
RFS 依靠 RPS 的机制插入数据包到指定 CPU 的 backlog 队列,并唤醒那个 CPU 来执行。
默认情况下,开启 irqbalance
是足够用的,但是对于一些对网络性能要求比较高的场景,手动绑定中断磨合是比较好的选择。
开启 irqbalance,会存在一些问题,比如:
a) 有时候计算出来的值不合理,导致 CPU 使用还是不均衡。
b) 在系统比较空闲 IRQ 处于 Power-save mode 时,irqbalance 会将中断集中分配给第一个 CPU,
以保证其它空闲 CPU 的睡眠时间,降低能耗。如果压力突然上升,可能会由于调整的滞后性带来性能 问题。
c) 处理中断的 CPU 总是会变,导致了更多的 context switch。
d)也存在一些情况,启动了 irqbalance,但是并没有生效,没有真正去设置处理中断的cpu。
2.4.3 如何查看网卡的队列数
Combined 代表队列个数,说明我的 测试机有 4 个队列。
# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 0
Combined: 4
Current hardware settings:
RX: 0
TX: 0
Other: 0
Combined: 4
以 CentOS7.6 为例,系统处理中断的记录在 /proc/interrupts
文件里面,默认这个文件记录比较多,影响查看,同时如果 cpu 核心也非常多的话, 对于阅读的影响非常大。
# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 141 0 0 0 IO-APIC-edge timer
1: 10 0 0 0 IO-APIC-edge i8042
4: 807 0 0 0 IO-APIC-edge serial
6: 3 0 0 0 IO-APIC-edge floppy
8: 0 0 0 0 IO-APIC-edge rtc0
9: 0 0 0 0 IO-APIC-fasteoi acpi
10: 0 0 0 0 IO-APIC-fasteoi virtio3
11: 22 0 0 0 IO-APIC-fasteoi hcd:usb1
12: 15 0 0 0 IO-APIC-edge i8042
14: 0 0 0 0 IO-APIC-edge ata_piix
15: 0 0 0 0 IO-APIC-edge ata_piix
24: 0 0 0 0 PCI-MSI-edge virtio1-config
。。。
27: 1913 0 0 0 PCI-MSI-edge virtio2-input.0
28: 3 834 0 0 PCI-MSI-edge virtio2-output.0
input0 说明是 cpu0(第 1 个 CPU)处理的网络中断
阿里云 ecs 网络中断,如果是多个中断的话,还有 input.1 input.2 input.3 这种形式
如果 ecs 的 cpu 核心非常多,那这个文件看起来就会比较费劲了,可使用下面 的命令查看处理中断的核心。
使用下面这个命令,即可将阿里云 ecs 处理中断的 cpu 找出来了(下面这个演示是 8c4个队列)
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
5
7
1
3
处理一下 sar 拷贝用
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done |tr -s '\n' ','
5,7,1,3,
# sar -P 5,7,1,3 1 每秒刷新一次 cpu 序号为 5,7,1,3 核心的 cpu 使用率
# sar -P ALL 1 每秒刷新所有核心,用于少量 CPU 核心的监控,这样我们就可以知道处理慢的原因是不是因为队列不够导致的了
Linux 3.10.0-957.5.1.el7.x86_64 (iZwz98aynkjcxvtra0f375Z) x86_64_ (4 CPU)
05:10:06 PM CPU %user %nice %system %iowait %steal %idle
05:10:07 PM all 5.63 0.00 3.58 1.02 0.00 89.77
05:10:07 PM 0 6.12 0.00 3.06 1.02 0.00 89.90
05:10:07 PM 1 5.10 0.00 5.10 0.00 0.00 89.80
05:10:07 PM 2 5.10 0.00 3.06 2.04 0.00 89.80
05:10:07 PM 3 5.10 0.00 4.08 1.02 0.00 89.80
05:10:07 PM CPU %user %nice %system %iowait %steal %idle
05:10:08 PM all 8.78 0.00 15.01 0.69 0.00 75.52
05:10:08 PM 0 10.00 0.00 16.36 0.91 0.00 75.73
05:10:08 PM 1 4.81 0.00 13.46 1.92 0.00 79.81
05:10:08 PM 2 10.91 0.00 15.45 0.91 0.00 72.73
05:10:08 PM 3 9.09 0.00 14.55 0.00 0.00 76.36
sar 小技巧
打印 idle 小于 10 的核心
sar -P 1,3,5,7 1 |tail -n+3|awk '$NF<10 {print $0}'
看所有核心是否有单核打满的把 1357 换成 ALL 即可
sar -P ALL 1 |tail -n+3|awk '$NF<10 {print $0}'
再贴一个 4c8g 规格的配置(ecs.c6.xlarge),可以看到 4c 也给了四个队列,但是默认设置的是在 cpu0 和 2 上处理中断
# grep -i "input" /proc/interrupts
27: 1932 0 0 0 PCI-MSI-edge virtio2-input.0
29: 2 0 0 1627 PCI-MSI-edge virtio2-input.1
32: 1974 0 0 0 PCI-MSI-edge virtio2-input.2
35: 3 0 284 0 PCI-MSI-edge virtio2-input.3
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
1
3
1
3
原因是 cpu 是超线程的,"每个 vCPU 绑定到一个物理 CPU 超线程", 所以即使是4个队列默认也在2个 cpu 核心上
# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit,64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
关闭 IRQbalance。
# service irqbalance status
Redirecting to /bin/systemctl status irqbalance.service
● irqbalance.service - irqbalance daemon
Loaded: loaded (/usr/lib/systemd/system/irqbalance.service; enabled; vendor preset: enabled)
Active: inactive (dead) since Wed 2020-05-27 14:39:28 CST; 2s ago
Process: 1832 ExecStart=/usr/sbin/irqbalance --foreground $IRQBALANCE_ ARGS (code=exited,status=0/SUCCESS)
Main PID: 1832 (code=exited,status=0/SUCCESS)
May 27 14:11:40 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Started irqbalance daemon.
May 27 14:39:28 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Stopping irqbalance daemon...
May 27 14:39:28 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Stopped irqbalance daemon.
手动设置 RPS。
手动设置之前我们需要先了解下面的文件(IRQ_number 就是前面 grep input 拿到的序号)。
进入 /proc/irq/${IRQ_number}/
, 关注两个文件:smp_affinity
和 smp_ affinity_list
。
smp_affinity
是 bitmask+16 进制,
smp_affinity_list
:这个文件更好理解,采用的是 10 进制,可读性高。
改这两个任意一个文件,另一个文件会同步更改。
为了方便理解,咱们直接看十进制的文件 smp_affinity_list
即可。
如果这一步没看明白,注意前面的 /proc/interrupts 的输出
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}'); do cat /proc/irq/$i/smp_affinity_list;done
1
3
1
3
手动设置处理中断的 CPU 号码可以直接 echo 修改,下面就是将序号 27 的中断放到 cpu0 上处理,一般建议可以把 cpu0 空出来
# echo 0 >> /proc/irq/27/smp_affinity_list
# cat /proc/irq/27/smp_affinity_list
0
关于 bitmas
"f" 是十六进制的值对应的 二进制是 "1111"(可以理解为 4c 的配置设置为 f 的话,所有的 cpu 参与处理中断)
二进制中的每个位代表了服务器上的每个 CPU. 一个简单的 demo
CPU序号 二进制 十六进制
CPU 0 0001 1
CPU 1 0010 2
CPU 2 0100 4
CPU 3 1000 8
需要对每块网卡每个队列分别进行设置。如对 eth0 的 0 号队列设置:
echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus
这里的设置方式和中断亲和力设置的方法是类似的。采用的是掩码的方式,但是这里通常要将所有的 CPU 设置进入,如:
4core,f
8core,ff
16core,ffff
32core,ffffffff
默认在 0 号 cpu 上
# cat /sys/class/net/eth0/queues/rx-0/rps_cpus
0
# echo f >>/sys/class/net/eth0/queues/rx-0/rps_cpus
# cat /sys/class/net/eth0/queues/rx-0/rps_cpus
f
设置 RFS 的方式。
需要设置两个地方:
-
全局表
rps_sock_flow_table
的条目数量。通过一个内核参数控制:# sysctl -a |grep net.core.rps_sock_flow_entries
net.core.rps_sock_flow_entries = 0
# sysctl -w net.core.rps_sock_flow_entries=1024
net.core.rps_sock_flow_entries = 1024 -
每个网卡队列 hash 表的条目数:
# cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
0
# echo 256 >> /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
# cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
256
需要启动 RFS,两者都需要设置。 建议机器上所有的网卡队列设置的 rps_flow_cnt
相加应该小于或者等于 rps_sock_flow_entries
。 因为是4个队列, 因此每个队列设置 256, 可以根据实际情况增大。
2.5 一次 IO 异常捕获过程
简介
遇到一个 IO 异常飙升的问题,IO 起飞后系统响应异常缓慢,看不到现场一直无法定位问题, 检查对应时间点应用日志也没有发现异常的访问, 这种问题怎么办呢?
2.5.1 采集系统 IO,确认 IO 异常发生在系统盘,还是数据盘,使用系统自带的 iostat 即可采集
# iostat -d 3 -k -x -t 30
Linux 3.10.0-957.21.3.el7.x86_64 (tencent) 08/05/2020 _x86_64_ (1 CPU)
06/12/2018 11:51:49 AM
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.12 12.66 4.59 8.55 127.03 143.35 41.16 0.07 7.91 15.92 3.61 0.65 0.85
scd0 0.00 0.00 0.00 0.00 0.00 0.00 7.10 0.00 1.03 1.03 0.00 1.02 0.00
06/12/2018 11:51:52 AM
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 27.61 10.44 69.70 247.81 397.31 16.10 0.99 15.57 1.71 17.65 0.16 1.28
scd0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
每隔 3 秒采集一次磁盘 io,输出时间,一共采集 30 次,想一直抓的话把 30 去掉即可,注意磁盘空余量。
通过这个命令我们可以确认如下信息:
- 问题发生的时间
- 哪块盘发生的 io
- 磁盘的 IOPS( r/s w/s)以及吞吐量( rkB/s wkB/s )