总结一下之前的项目,主要用到了python多进程的知识,其他的一些零碎的辅助知识也会用到,这里主要对整体框架进行总结,至于性能,因为经验问题,不能优化的很好,加上本项目有很多文件的读写,只能算稳定而已。
这个项目是大量的音频文件格式和频率转换,大概300多万个,一个转成7个,原来那个也要用,也就是说最后大概有300*8W个文件,总共大概2T的数据。好了,回忆下要求,首先转换过程中不能破坏原始文件的目录结构;第二,保证所有文件都转换过;第三,异常检测,日志分析,进度检测;第四,保证意外死机等情况下,重新跑代码可以继续执行上次中断的位置而不是要重复在进行转换(重复转换会出现1个文件本来转成8个了,那重复执行,这8个又会各自再生出8个,就产生错误了);最后,分析汇总错误日志,得到最终处理结果,问题文件汇总查看原因。
其实说白了,就是保证不出现不可逆的错误,保证稳定执行,保证准确,保证完整,保证效率。那么我们要有一个解决问题的思路,一开始说是,有好几台机器可用,就用分布式框架吧,例如hadoop。。可惜时间短任务重,从头学的话,不得至少一个月?还是Python来的快,多进程执行,多核利用,有几个核就开几个进程,标准库multiprocessing完全可以胜任,一开始想的是thread多线程,后来想到Python的多线程不是类似java的多线程,Python有一个全局锁机制,多个线程其实并不是并行执行(是不是并发更准确?),而是轮换执行,况且不能有效利用多核,那就确定最终的路线是:multiprocessing+多核(我用的进程池Pool,因为自己一个一个造的进程容易出错,而且性能也没有Pool好),具体的代码后面再说。
好了,下面就说代码吧,首先要从给定的目录中找到目标文件作为待处理任务列表,我写的函数如下:
def get_filepath(src):
filepath = collections.deque()
for root,dirs,files in os.walk(src):
for afile in files:
if "_" not in afile:
filepath.append(os.path.join(root,afile))
return filepath
all_filepath = get_filepath(src)
for line in all_filepath:
ret = os.path.splitext(line)[-1]
if (ret == '.amr'):
work_filepath.append(line)
为了保证代码不会被意外操作所停止,我们还注册了信号量,不过这个实现的很粗糙,没有往下细细研究:
signal.signal(signal.SIGINT, my_exits)
signal.signal(signal.SIGTERM, my_exits)
signal.signal(signal.SIGTSTP, my_exits)
my_exits是自己实现的函数,就是打印收到了什么信息,并不退出程序,这里就不贴了,下面贴出Pool的使用代码:
pool_size = multiprocessing.cpu_count()
pool = multiprocessing.Pool(processes=pool_size,initializer=start_process,)
#pool_outputs = pool.map_async(amr2amr_worker.convert_rm_file,work_filepath)
Sum_work_deque = 'total_numbers:%s'%len(work_filepath)
total_log_file = FILE+'/log/total_numbers.log'
with open(total_log_file,'w') as f:
f.write(str(Sum_work_deque))
while len(work_filepath)>0:
try:
file = work_filepath.popleft()
print file
result = pool.apply_async(amr2amr_worker.convert_rm_file,(file,))
except:
break
pool.close()
pool.join()
后面的代码没啥好贴的了,只是有的情况是自己没考虑完全的,在自己测试的时候没发现问题,真跑的时候有很多小缺陷,例如,文件转换完后,总数不对,却没打出日志,在执行结束后,发现文件数量在某台机器上高的离谱,后来发现其实是这台机器的小文件(小于10k的大约一百万个)数量超多,搞得我还以为这是重复转换了。
当然这都不是最重要的教训,最重要的是在做这个东西的时候,偷懒,用了别人的一个写的一个模块,也没仔细推敲,直接用了,而且在测试的时候,非常稳定。。。直到真正跑的时候,你会发现真正的数据会奇形怪状,竟然有0KB文件的存在。。。刚好在调用别人模块时崩了,而我又没在那个模块加日志和异常控制,查了大半天才发现有0kB文件的存在。所以,在写代码的时候,除了实现正常流程之外,各种异常情况一定要考虑到,这就是经验积累的开始了。
再说个日志分析的吧,这个处理的难度取决于你当时日志的可读性,如果你直接一个字符串毫无格式可言,那以后分析日志,简直是惨,下面贴出我的日志:
time:Mon Jul 6 16:14:44 2015 ok_count:1 source_count:1 dest_count:8
time:Mon Jul 6 16:14:51 2015 ok_count:2 source_count:2 dest_count:16
time:Mon Jul 6 16:14:44 2015 ok_count:1 source_count:1 dest_count:8
time:Mon Jul 6 16:14:45 2015 ok_count:1 source_count:1 dest_count:8
time:Mon Jul 6 16:14:44 2015 ok_count:1 source_count:1 dest_count:8
time:Mon Jul 6 16:14:52 2015 ok_count:2 source_count:2 dest_count:16
time:Mon Jul 6 16:14:51 2015 ok_count:2 source_count:2 dest_count:16
time:Mon Jul 6 16:14:51 2015 ok_count:2 source_count:2 dest_count:16
#!/usr/bin/env python
import glob
log_list = glob.glob("*.log")
ok_count = 0
dest_count = 0
source_count = 0
if len(log_list) != 0:
log_list.remove("total_numbers.log")
for file in log_list:
with open(file,'r') as f:
word_list = f.readline().split(' ')
dest_str = (word_list[-1].strip())
dest_number = int((dest_str).split(':')[-1].strip())
dest_count += dest_number
source_str = (word_list[-2].strip())
source_number = int(str(source_str).split(':')[-1].strip())
source_count += source_number
ok_str = (word_list[-3].strip())
ok_number = int(str(ok_str).split(':')[-1].strip())
ok_count += ok_number
print "dest_count:%s"% dest_count
source_count_new = source_count*8
print "source_count:%s"% source_count_new
print "source_count:%s"% source_count
print "ok_count:%s"% ok_count
with open('total_numbers.log') as f:
print "total_numbers:%s"% int(f.read().split(':')[-1].strip())
else:
print "no log file,please check others!!!"
当时只为了快速出结果,可以看到,我连函数都没写。。直接把思路用python写出来了,我想这也是python的魅力吧,反正我用着挺爽,如果让我用C写的话,写着写着发现。。。咦,我当时咋想的来着?哈哈,开个玩笑,自己的C一写就烦。。不知道为何,但还得练,以后还有用的。。纠结中努力吧!加油!