立即注册 登录
About云-梭伦科技 返回首页

easthome001的个人空间 https://www.aboutyun.com/?1321 [收藏] [复制] [分享] [RSS]

日志

排查Linux下进程的IO活动状况的两个案例及方法介绍

已有 1484 次阅读2017-3-4 08:51 | 服务器, Linux

第一个案例
前段时间,几台测试服务器的Web应用响应速度非常慢,系统负载也比较高,> 10, 但CPU和内存却很闲,于是怀疑是磁盘的性能瓶颈,通过vmstat和iostat看到IO的读写量非常大,尤其是用iostat -x 1命令可以很直观的看到IO的使用率一直在100%。
但究竟是什么进程导致的高IO呢,由于每台服务器上都有JBoss和MySQL的存在,JBoss会不停的产生很多小的数据文件和生成文本数据库的数据,而MySQL则会不停的从Master同步新的数据。因此我们怀疑是这两个进程导致的高IO,通过停止了JBoss和MySQL之后,IO立刻降为0%. 但我们还是不能确定谁是主因,于是寻找可以查看特定进程IO的方法。

最后,找到了两个方法可以查看进程IO的活动状况。

1. 第一个方法是通过一个python脚本来实现。
方法是将以下内容另存为一个叫io.py的脚本中,然后直接以root身份执行脚本,就可以看到如下图所示的信息(由于我们已经通过升级到SSD硬盘解决了MySQL的IO问题,所以不能提供关于MySQL的截图了),其中出现次数最多,数据最大的进程,就是导致高IO的主因。不过比较遗憾的是这个脚本并不能显示进程在每一秒的准确的IO读写。

# vim io.py
# chmod +x io.py
# ./io.py

#!/usr/bin/python
# Monitoring per-process disk I/O activity
# written by http://www.vpsee.com 

import sys, os, time, signal, re

class DiskIO:
    def __init__(self, pname=None, pid=None, reads=0, writes=0):
        self.pname = pname
        self.pid = pid
        self.reads = 0
        self.writes = 0

def main():
    argc = len(sys.argv)
    if argc != 1:
        print "usage: ./iotop"
        sys.exit(0)

    if os.getuid() != 0:
        print "must be run as root"
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    os.system('echo 1 > /proc/sys/vm/block_dump')
    print "TASK              PID       READ      WRITE"
    while True:
        os.system('dmesg -c > /tmp/diskio.log')
        l = []
        f = open('/tmp/diskio.log', 'r')
        line = f.readline()
        while line:
            m = re.match(\
                '^(\S+)\((\d+)\): (READ|WRITE) block (\d+) on (\S+)', line)
            if m != None:
                if not l:
                    l.append(DiskIO(m.group(1), m.group(2)))
                    line = f.readline()
                    continue
                found = False
                for item in l:
                    if item.pid == m.group(2):
                        found = True
                        if m.group(3) == "READ":
                            item.reads = item.reads + 1
                        elif m.group(3) == "WRITE":
                            item.writes = item.writes + 1
                if not found:
                    l.append(DiskIO(m.group(1), m.group(2)))
            line = f.readline()
        time.sleep(1)
        for item in l:
            print "%-10s %10s %10d %10d" % \
                (item.pname, item.pid, item.reads, item.writes)

def signal_handler(signal, frame):
    os.system('echo 0 > /proc/sys/vm/block_dump')
    sys.exit(0)

if __name__=="__main__":
    main()

2. 另一个方法是将Linux的内核升级到 >=2.6.20,然后安装一个iotop软件来实现。
不过这种改动并不适用于生产环境,因为在RHEL5.6和5.7上,内核都在 2.6.20以下。但是它所显示的结果是非常准确的,所以对于新上线的机器以及测试环境,非常值得一试,具体方法如下:

下载和升级新内核(>=2.6.20),编译时打开 TASK_DELAY_ACCT 和 TASK_IO_ACCOUNTING 选项。
解压内核后进入配置界面:
# wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.39.tar.gz
# tar jxvf linux-2.6.39.tar.gz
# mv linux-2.6.39 /usr/src/
# cd /usr/src/linux-2.6.39

# make oldconfig //使用make oldconfig可以继承老的kernel的配置,为自己的配置省去很多麻烦。
# make menuconfig

把General setup - Enable per-task storage I/O accounting这个选项选上。


# vim .config
将#CONFIG_SYSFS_DEPRECATED_V2 is not set的注释去掉的,将其改为y,即修改为CONFIG_SYSFS_DEPRECATED_V2=y。

保存内核后编译内核:
# make
# make modules
# make modules_install
# make install

修改默认以新的内核启动:
# vi /boot/grub/grub.conf
default=0

将新的内核配置文件复制到/boot目录:
# cp /usr/src/linux-2.6.39/.config /boot/config-2.6.39

重启服务器:
# reboot

# uname –r
2.6.39

重启完成后确认内核版本是否正确。

源码安装iotop所需的Python 2.7.2(>= 2.5):
# wget http://www.python.org/ftp/python/2.7.2/Python-2.7.2.tgz
# tar xzvf Python-2.7.2.tgz
# cd Python-2.7.2
# ./configure
# make; make install

下载并安装iotop:
# wget http://guichaz.free.fr/iotop/files/iotop-0.4.4.tar.bz2
# tar -xjvf iotop-0.4.4.tar.bz2
# cd iotop-0.4.4
# python setup.py build
# python setup.py install

然后就可以使用iotop看到如下图所示的信息:


第二个案例

我们服务器搭建了cacti进行监控,一次通过查看磁盘IO图像的时候,发现每天凌晨3:20-3:35的时候磁盘IO一下飙的很高,然后想知道到底是哪个进程占用那么高的IO,下面是解决方法:

写个检测脚本check_io_process.sh,当磁盘IO占用高的时候进行磁盘IO读写进程次数的检测:

#!/bin/bash

# Date: 2013/8/20

# Author: zhangkai

# Description: This script is used to check IO higher process.

# History:

iostat_log=/data/logs/iostat/iostat.log

dmesg_log=/data/logs/iostat/dmesg.log

dstat_log=/data/logs/iostat/dstat.log

if [ ! -d /data/logs/iostat ];then

mkdir -p /data/logs/iostat

fi

add(){

str=$@

sum=`echo ${str// /+}|bc -l`

}

iostat -x 1 5 > $iostat_log

idle_percent=`cat $iostat_log | awk 'BEGIN{flag=0} {if(flag ==1){print $12; flag=0;} if (index($0, "%util" )) {flag = 1;}}'`

add $idle_percent

#求5次查询IO占用率的平均值

avg=`echo $sum/5|bc`

if [[ $avg -ge 70 ]];then

echo 1 > /proc/sys/vm/block_dump

echo "-----------------------------------------------" >> $dmesg_log

echo `date "+%Y-%m-%d %H:%M:%S"` >> $dmesg_log

python /data/dmesg_io.py >> $dmesg_log


echo "-----------------------------------------------" >> $dstat_log

echo `date "+%Y-%m-%d %H:%M:%S"` >> $dstat_log

dstat -d --top-bio 1 10 >> $dstat_log

echo 0 > /proc/sys/vm/block_dump 

 fi

其中该shell脚本调用了检测磁盘IO读写进程次数的python脚本,下面是dmesg_io.py的代码:

#!/usr/bin/python

# Monitoring per-process disk I/O activity

# written by http://www.vpsee.com

import sys, os, time, signal, re

class DiskIO:

def __init__(self, pname=None, pid=None, reads=0, writes=0):

self.pname = pname

self.pid = pid

self.reads = 0

self.writes = 0

def main():

argc = len(sys.argv)

if argc != 1:

print "usage: ./iotop"

sys.exit(0)

if os.getuid() != 0:

print "must be run as root"

sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

os.system('echo 1 > /proc/sys/vm/block_dump')

print "TASK PID READ WRITE"

# while True:

os.system('dmesg -c > /tmp/diskio.log')

l = []

f = open('/tmp/diskio.log', 'r')

line = f.readline()

while line:

m = re.match(\

'^(\S+)\((\d+)\): (READ|WRITE) block (\d+) on (\S+)', line)

if m != None:

if not l:

l.append(DiskIO(m.group(1), m.group(2)))

line = f.readline()

continue

found = False

for item in l:

if item.pid == m.group(2):

found = True

if m.group(3) == "READ":

item.reads = item.reads + 1

elif m.group(3) == "WRITE":

item.writes = item.writes + 1

if not found:

l.append(DiskIO(m.group(1), m.group(2)))

line = f.readline()

time.sleep(1)

for item in l:

print "%-10s s d d" % \

(item.pname, item.pid, item.reads, item.writes)

def signal_handler(signal, frame):

os.system('echo 0 > /proc/sys/vm/block_dump')

sys.exit(0)

if __name__=="__main__":

main()

发现在3:20-3:35的日志如下(仅列出部分):

[root@localhost iostat]# cat dmesg.log

-----------------------------------------------

2013-08-22 03:23:06

TASK PID READ WRITE

updatedb 18661 2951 0

kjournald 804 0 525

kjournald 1826 0 576

-----------------------------------------------

2013-08-22 03:24:05

TASK PID READ WRITE

updatedb 18661 3007 0

kjournald 804 0 238

kjournald 1826 0 112

flush-8:0 11687 0 18

-----------------------------------------------

2013-08-22 03:25:05

TASK PID READ WRITE

updatedb 18661 2689 0

kjournald 804 0 229

kjournald 1826 0 44

说明是updatedb这进程惹的祸,google查之,这个是由[cron]自动运行的更新系统数据的脚本。
其作用是为你系统里面的文件建立索引,以便于locate和whereis等查询命令的能够快速执行

而我们服务器/data/目录每天会产生大量的小文件,导致建立索引的时候占用很高的磁盘IO

服务器每天定时对硬盘上的文件进行索引,简单的说就是建立一个数据库,把所有文件目录信息存放到这个库里面,当使用whereis和locate命令搜索文件时,它直接到这个数据库中读取数据。而不是像find一样在硬盘上找文件。Whereis搜索一个文件几乎只要几秒钟就可以搞定,而find需要花费几分钟或者更长时间。updatedb.Conf使搜索的效率提高了很多倍。但是有缺点,它每天都需要索引更新,这会导致IO负载过高,因为不是时时更新,所以会出现搜索到已经删除的文件,搜不出新添加的文件,平时管理中我们很少用到。如果文件数量多而且更新平凡,我们大可把这个功能关闭

优化方法:

1.停止对/data目录进行建立索引操作

vim /etc/updatedb.conf

找到PRUNEPATHS,在后面添加上你不想让这个updatedb建立索引的目录

2.设置定时更新的工作频率从每天一次降低到每周执行一次,命令如下:

mv /etc/cron.daily/mlocate.cron /etc/cron.weekly/
当然如果你服务器用不着建立索引,也可以直接移除






路过

雷人

握手

鲜花

鸡蛋

评论 (0 个评论)

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 立即注册

关闭

推荐上一条 /2 下一条