集羣機器的磁盤經常會被某些進程打滿
- 磁盤空間被打滿,其他進程無法繼續寫入
- 磁盤的讀寫通道被打滿,其他進程因爲讀寫緩慢而影響性能
問題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 -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