最近面试被问到让写一个日志切割的脚本,最开始是使用 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
仅仅 8M 多的日志文件,尼玛,5分钟,运行时间确实让人揪心啊,于是有了后面的改进版。
最近刚好看到别人用 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()
运行结果:
这个结果确实让人高兴多了。由于我的服务器是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()
运行结果:
结果并非我想象的那样,还是没有多线程块,我猜测应该是 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