日志切割之性能优化

python struggling 1573次浏览 3个评论

最近面试被问到让写一个日志切割的脚本,最开始是使用 shell 写的,像下面这样,当时并没有考虑性能问题,只是试了可以切割。但是拿来在服务器上执行后,傻眼了:

#!/bin/bash

LANG=en_US

Usage() {
        echo "Usage: $0 Logfile"
}

if [ $# -eq 0 ] ;then
    Usage
    exit 0
else
    Log=$1
fi


cat $Log | while read line;do 
    Year=$(echo $line | awk '{print $4}'| awk -F '/' '{print $3}' |awk -F ':' '{print $1}')
    Month=$(echo $line | awk '{print $4}'| awk -F '/' '{print $2}')
    Day=$(echo $line | awk '{print $4}'| awk -F '/' '{print $1}' | awk -F '[' '{print $2}')
    echo $line >> /tmp/log2/${Year}-${Month}-${Day}
done

log-1

仅仅 8M 多的日志文件,尼玛,5分钟,运行时间确实让人揪心啊,于是有了后面的改进版。

log-2

log-3

最近刚好看到别人用 shell 使用多线程,于是现学现卖,搞了个多线程的版本试试看:

[root@sta shell]# cat log_cut.sh 
#!/bin/bash

LANG=en_US

Usage() {
        echo "Usage: $0 Logfile"
}

if [ $# -eq 0 ] ;then
    Usage
    exit 0
else
    Log=$1
fi

while read line 
do
{
    {
        Year=$(echo $line | awk '{print $4}'| awk -F '/' '{print $3}' |awk -F ':' '{print $1}')
        Month=$(echo $line | awk '{print $4}'| awk -F '/' '{print $2}')
        Day=$(echo $line | awk '{print $4}'| awk -F '/' '{print $1}' | awk -F '[' '{print $2}')
        echo $line >> /tmp/log2/${Year}-${Month}-${Day}
    }&
}
done < $Log
wait

但是结果并没有想象中的好,执行依旧很慢,不知道是我是我脚本的问题,要是哪位看出这脚本的问题了,麻烦告知我下,非常感谢!最近正好学了 python,于是拿来玩玩,下面来看 python 的多线程版本:

[root@sta nginx]# cat log_cut1.py 
#!/usr/bin/env python
# coding=utf-8

import threading 
import re

pattern = re.compile(r'.*\[(\d+)\/(\w+)\/(\d+)\:.*')
#pattern = re.compile('.*\[([0-9]+)\/([a-zA-Z]+)\/([0-9]+)\:.*')     这种匹配方式也可以

def parse_log(file_line):
    resu = pattern.match(file_line)
    day,month,year = resu.groups()
    
    with open('/tmp/log/%s-%s-%s' %(year,month,day),'a') as f:
        f.write(file_line)

if __name__ == '__main__':
    with open('/tmp/access.log') as f:
        for line in f: 
            log_cut_thread = threading.Thread(target=parse_log,args=(line,))
            log_cut_thread.setDaemon(1)
            log_cut_thread.start()

运行结果:

log-4

这个结果确实让人高兴多了。由于我的服务器是4核的,本来想到 python GIL 的限制,可能使用多进程会更快,于是改为多进程的试了下:

[root@sta nginx]# cat multi_log_cut.py 
#!/usr/bin/env python
# coding=utf-8

import multiprocessing
import re

pattern = re.compile(r'.*\[(\d+)\/(\w+)\/(\d+)\:.*')

def parse_log(file_line):
    resu = pattern.match(file_line)
    day, month, year = resu.groups()

    with open('/tmp/log1/%s-%s-%s' %(year,month,day),'a') as f: 
        f.write(file_line)  

if __name__ == '__main__':
    with open('/tmp/access.log') as f:
        for line in f:
            log_cut_process = multiprocessing.Process(target=parse_log,args=(line,))
            log_cut_process.start()

运行结果:

log-5

结果并非我想象的那样,还是没有多线程块,我猜测应该是 IO 密集型由于使用多进程上下文切换比较消耗cpu资源所以导致变慢。后面再进行深入研究。

看完了 python 的多线程和多进程版本,速度并不是很感人,但是还可以改进吗?经过进一步探索,python 原来还有很多的奇技淫巧,下面是一个改进的版本:

#!/usr/bin/env python
# coding=utf-8

import threading 
import re
from multiprocessing.dummy import Pool as ThreadPool
 
pattern = re.compile(r'.*\[(\d+)\/(\w+)\/(\d+)\:.*')
 
def parse_log(file_line):
    resu = pattern.match(file_line)
    day,month,year = resu.groups()
    
    with open('/tmp/log/%s-%s-%s' %(year,month,day),'a') as f:
        f.write(file_line)
 
if __name__ == '__main__':
    pool = ThreadPool(4)
    with open('/tmp/access.log') as f:
           pool.map(parse_log,f)

运行结果:

[root@sta nginx]# time python map-multi.py   #结果还算不错

real	0m1.908s
user	0m1.789s
sys	0m2.295s

以下是网友 @ll104567 提供的一个日志切割脚本,使用相同的日志文件测试效果非常好,值得借鉴:


#!/bin/bash

Usage(){
    echo "Usage: $0 Logfile"
}
         
if [ $# -eq 0 ] ;then
    Usage
    exit 0
else
    Log=$1
fi

date_log=$(mktemp)

cat $Log |awk -F'[ :]' '{print $4}'|awk -F'[' '{print $2}'|uniq > date_log

for i in `cat date_log`
do
	grep $i $Log > /tmp/log/${i:7:10}-${i:3:3}-${i:0:2}.access

done

rm -f date_log

[root@sta nginx]# time sh  cutlog2.sh access.log    #运行结果

real	0m2.795s
user	0m2.014s
sys	0m0.848s

DevOps-田飞雨 》》转载请注明源地址
喜欢 (6)or分享 (0)
发表我的评论
取消评论
*

表情 贴图 加粗 链接 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(3)个小伙伴在吐槽
  1. Very Good!thinks you!一定要中文么,好
    Lynn2016-05-13 19:40 回复
  2. 脚本优化的话有的时候可以考虑多用系统的内置变量、比如${var:3:3}来代替awk的那个cut,每一次管道会启一个子shell,awk像这样awk -F'[ /:]' '{print $6}'可以直接取出年,别的暂时还没想到
    ll1045672016-05-19 14:20 回复
    • 恩 好 你专帮我找程序的bug 多谢 shell内置的一些方法我确实不太用,这确实也是个不错的想法
      struggling  2016-05-19 16:14 回复