集群机器的磁盘经常会被某些进程打满
- 磁盘空间被打满,其他进程无法继续写入
- 磁盘的读写通道被打满,其他进程因为读写缓慢而影响性能
问题1: 磁盘空间被打满
1.1. 检查机器磁盘使用
主要是使用df命令来查看磁盘空间的使用,如下图所示:
Linux中df命令的输出清单的第1列是代表文件系统对应的设备文件的路径名(一般是硬盘上的分区);第2列是文件系统的类型;第3,4,5列给出分区总大小、已使用、剩余。用户也许会感到奇怪的是,第4,5列之和不等于第3列。这是因为缺省的每个分区都留了少量空间供系统管理员使用。即使遇到普通用户空间已满的情况,管理员仍能登录和留有解决问题所需的工作空间。清单中Use% 列表示普通用户空间使用的百分比,即使这一数字达到100%,分区仍然留有系统管理员使用的空间。最后,Mounted on列表示文件系统的挂载点。
上图中文件系统的类型有两种,ext4和tmpfs,可能会有疑问tmpfs是什么。ext4是基于硬盘的文件系统,tmpfs是基于内存的文件系统,具体可看:https://blog.csdn.net/cherisegege/article/details/79311621
由上图可知,通过df命令,上诉的%user列就是我们要找的磁盘使用情况,因此我们只要编写一个命令来获取磁盘使用即可
##df 获取磁盘使用情况
##grep 过滤出以dev开头的设备(比如基于内存的文件系统不以/deve开头)
##awk 输出df命令的第1,6,7列,分别是设备路径、磁盘使用率、挂载点
##sed 将一行中的%去除
df -lhT |grep '^/dev/'|awk '{print $1,$6,$7}' |sed 's/%//g'
1.2 找出占用空间最大的目录
## du -a 显示指定目录下文件的大小
## sort -n -r 按数值大小降序
## head -n 10 保留top 10
du -a /dump/15 | sort -n -r | head -n 10
## find指令为找出500M以上的文件,print0和xargs -0配合使用,用来解决文件名中有空格或特殊字符问题
## du -m是查看这些文件的大小,并以m为单位显示。
## sort -nr是按照数字反向排序(大的文件在前)
find /dump/15 -size +500M -print0|xargs -0 du -m|sort -nr
问题2: 磁盘读写通道被打满
2.1 使用iotop找到读写最大的进程
若是io util很高,但是无法快速的定位到IO负载的来源进程和来源文件,可以使用iotop来定位。iotop是一个用来监视磁盘I/O使用状况的 top 类工具,可监测到哪一个程序使用的磁盘IO的信息(https://www.cnblogs.com/yinzhengjie/p/9934260.html)
## -o:只显示正在产生I/O的进程或线程
## -d 10: 输出间隔为10s
sudo iotop -o -P -d 10
使用iotop找出大量读写的进程
然后使用以下命令查看该进程的io详情信息
## -b : 非交互模式,一般用来记录日志
## -o : 只显示正在产生I/O的进程或线程
## -q :禁止头几行,非交互模式
## -t : 加上时间戳,非交互非模式
sudo iotop -boqt -p 1377
2.2 使用lsof找出进程读写的文件
在上述2.1 找到读写最大的进程号后,可以使用lsof来看该进程读写的文件(https://blog.csdn.net/qq_27870421/article/details/92803453)
$sudo lsof -p 1377
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
jbd2/sdj1 1377 root cwd DIR 8,3 4096 2 /
jbd2/sdj1 1377 root rtd DIR 8,3 4096 2 /
jbd2/sdj1 1377 root txt unknown /proc/1377/exe
2.3 利用 block_dump 找出进程的写入量
除了上述方案,还可以使用block_dump来找出进程的读写量。当block_dump为非零值时(echo 1 > /proc/sys/vm/block_dump ),block_dump启用块I / O调试。 设置此标志后,Linux将报告发生的所有磁盘读写操作,block_dump的输出将写入内核输出,并且可以使用“ dmesg”进行检索。 当您使用block_dump调试消息时,您可能希望关闭系统日志(/etc/init.d/klogd stop),否则系统日志的太多可能会掩盖掉真正想要找到的问题
## 切换到root
sudo bash -c "su root"
## 打印dmesg当前信息后清除缓存中留存的当前信息
dmesg -c
#开启block_dump
echo 1 > /proc/sys/vm/block_dump
##删除上次的临时文件
rm /tmp/disklog
## 收集一段时间的日志 到disklog,一段时间后 crtl+c ,中断收集
watch "dmesg -c >> /tmp/disklog"
## 关闭block_dump
echo 0 > /proc/sys/vm/block_dump
## 分析收集的日志
cat /tmp/disklog | awk '/WRITE block/{a[$2]+=$5}END{for(i in a) print i,a[i]}'|column -t
输出结果为:
列分别是: 进程名(pid): 写入量
3: 使用 iodump
3.1 iodump介绍
iodump就利用内核tracepoint静态探针点技术实现的一个io问题排查工具。通过iodump工具,我们可以获取每一个IOPS(w/s和r/s)的详细信息,不仅包括IO请求的size大小,还包括IO请求的扇区地址,同时还包含IO请求的发生时间、读写的文件全路径、产生IO请求的进程信息等。这其中最具有特色的就是读写的文件全路径功能。
sudo yum install -b current iodump -y
sudo iodump -p sda
iodump的输出如下所示:
Timestamp(us) P_name Pid RW RqSize Sector Ino Partition FilePath
1572573081340474 syslog-ng 193050 R 32768 84803792 2622641 sda3 /usr/lib64/libsyslog-ng-3.6.so.0.0.0
1572573082629797 jbd2/sda5-8 1033 W 8192 2716230616 87295156 sda5 /home/admin/cloud/log/mi_config.INFO.log.190
3.2 利用iodump找出pid/FilePath的读写量
我们将iodump的结果,按照pid/FilePath聚合RqSize,即可获得每一个pid/FIlePath的读写量
#!/usr/bin/python
# -*- coding: utf-8 -*-
import commands
import logging
import sys
logging.getLogger().setLevel(logging.INFO)
TIMESTAMP = 0
PNAME = 1
PID = 2
RW = 3
RQSIZE = 4
SECTOR = 5
INFO = 6
PARTITION = 7
FILEPATH = 8
## 获取IODump的输出
def get_iodump_output(disk='sda',last_time=5):
"""
return:[ ['Timestamp','P_name','Pid','RW','RqSize','Sector','Ino','Partition','FilePath'] ]
"""
iodump_output = []
## 获取sda盘的ipos信息
cmd = "sudo iodump -p %s -t %s" % (disk,last_time)
logging.info(cmd)
status,output = commands.getstatusoutput(cmd)
## 第1行和最后两行是冗余的无效数据,需去除
output_lines = output.split("\n")[1:-2]
for line in output_lines:
### 按一个或多个空格分隔
iodump_output.append(line.split())
return iodump_output
def group_data_by_pid(data):
"""
data: [ ['Timestamp','P_name','Pid','RW','RqSize','Sector','Ino','Partition','FilePath'] ]
return : ['Pid':"11",'R':0,"W":1,FilePath:{"path1":{"R":0,":W":1,"FilePath":"xxx"}}]
"""
pid_ipos_dict = {}
for item in data:
try:
pid = item[PID]
rw = item[RW]
filepath = item[FILEPATH]
### 按照pid聚合
if not pid_ipos_dict.has_key(pid):
pid_ipos_dict[pid] = {
"Pid": pid,
"R": 0,
"W": 0,
"FilePath": {}
}
pid_ipos_dict[pid][rw] = pid_ipos_dict[pid][rw] + int(item[RQSIZE])
### 同一pid下,再按照FilePath聚合RW
if not pid_ipos_dict[pid]['FilePath'].has_key(filepath):
pid_ipos_dict[pid]['FilePath'][filepath] = {
"FilePath": filepath,
"R":0,
"W":0
}
pid_ipos_dict[pid]['FilePath'][filepath][rw] = pid_ipos_dict[pid]['FilePath'][filepath][rw] + int(item[RQSIZE])
except Exception as e:
logging.error(e)
return pid_ipos_dict.values()
def print_format(arr):
"""
arr: ['Pid':"11",'R':0,"W":1,FilePath:{"path1":{"R":0,":W":1,"FilePath":"xxx"}}]
"""
logging.info("----------iodump group by pid----------")
for item in arr:
logging.info('PID:{Pid}, R:{R} ,W:{W}'.format(Pid=item['Pid'],R=item['R'],W=item['W']) )
filePaths = item['FilePath'].values()
for path in filePaths:
logging.info('\t R:{R},W:{W},FilePath:{FilePath}'.format(FilePath=path['FilePath'],R=path['R'],W=path['W']))
if __name__ == '__main__':
disk = sys.argv[1]
last_time = sys.argv[2]
## 获取iodump的输出
iodump_data = get_iodump_output(disk,last_time)
## 将iodump的输出按pid聚合
pid_ipos_dict = group_data_by_pid(iodump_data)
## 格式化输出结果
print_format(pid_ipos_dict)
运行脚本,输出命令如下:
借此,可以找出IO最大的PID和以及写的FilePath。就可以找出一段时间(工具里 -t 10 表示10秒)内IO最大的进程和读写量最大的目录
问题
- 如何获取iodump工具?
刚了解到大神的iodump还只在内部使用,尚未开源(T.T等着大神完工吧)。其本质是获取磁盘每一个进程的iops详情信息。
参考自:
https://blog.csdn.net/saga_gallon/article/details/82891709
https://blog.csdn.net/Aaron528928/article/details/80548139
https://www.cnblogs.com/peida/archive/2012/12/07/2806483.html
https://blog.csdn.net/qq_27870421/article/details/92803453
https://www.cnblogs.com/yinzhengjie/p/9934260.html
https://stackoverflow.com/questions/249570/how-can-i-record-what-process-or-kernel-activity-is-using-the-disk-in-gnu-linux
https://blog.csdn.net/zwjzqqb/article/details/78959952