本文参考了July的教你如何迅速秒杀掉:99%的海量数据处理面试题和quicktest的Python处理海量数据的实战研究。写这篇文章意义是:1)记录自己的经验;2)对大量小文件数据的排序使用了“先Hash,后堆排序”的策略。对小文件先Hash,后堆排列的代码来自July提供的网址,做了微小修改,加了自己的理解。
场景:海量日志数据,提取出某日访问百度次数最多的K个IP
想法:
1.hash映射:顺序读取10个文件,按照hash(ip)%10的结果将数据写入到另外10个文件中。
2. hash统计:依次对小文件用hash_map(ip, ip_count)来统计每个ip出现的次数。
3.堆/快速/归并排序:利用快速/堆/归并排序按照出现次数进行排序,将排序好的ip和对应的ip_cout输出到文件中,这样得到了10个排好序的文件。最后,对这10个文件进行归并排序(内排序与外排序相结合)
实践:
0. 模拟海量数据分布式存储
0.1生成海量数据
import random from time import ctime # 生成海量数据 def generateRandom(rangeFrom, rangeTo): return random.randint(rangeFrom,rangeTo) def generageMassiveIPAddr(fileLocation,numberOfLines): IP = [] file_handler = open(fileLocation, 'a+') for i in range(numberOfLines): IP.append('10.197.' + str(generateRandom(0,255))+'.'+ str(generateRandom(0,255)) + '\n') file_handler.writelines(IP) file_handler.close() if __name__ == '__main__': print(ctime()) for i in range(10): print(' ' + str(i) + ": " + ctime()) generageMassiveIPAddr('e:\\massiveIP.txt', 1000000) print(ctime())
from time import ctime import os #将海量数据拆分成小的文件 def splitFile(fileLocation, targetFoler): file_handler = open(fileLocation, 'r') block_size = 1006633 # 14.4M line = file_handler.readline() temp = [] countFile = 1 while line: for i in range(block_size): if i == (block_size-1): # write block to small files file_writer = open(targetFoler +"\\file_"+str(countFile)+".txt", 'a+') file_writer.writelines(temp) file_writer.close() temp = [] print(" file " + str(countFile) + " generated at: " + str(ctime())) countFile = countFile + 1 else: line=file_handler.readline() temp.append(line) file_handler.close() if __name__ == '__main__': print("Start At: " + str(ctime())) os.makedirs('e:\\massiveData') splitFile("e:\\massiveIP.txt", "e:\\massiveData")
1. 对10个小文件进行hash映射,使得相同的ip分在同一个小文件中
from time import ctime import os datadir = "e:\\massiveData" tempdir = "e:\\temp" def hashfiles(): fs = [] if not os.path.exists(tempdir): os.makedirs(tempdir)#创建缓冲区 for f in range(0,10): fs.append(open(tempdir +"\\tmp_"+str(f)+".txt", 'w')) for parent, dirnames, filenames in os.walk(datadir):#遍历datadir for filename in filenames: f = open(os.path.join(parent, filename),'r') for ip in f: fs[hash(ip)%10].write(ip) f.close() for f in fs: f.close() if __name__ == '__main__': print("Start At: " + str(ctime())) hashfiles() print("End At: " + str(ctime()))
2. 对10个小文件中的ip数进行统计,重复最多的ip放在前面,包括ip和次数
from time import ctime import os import operator tempdir = "e:\\temp" def sortipinfile(): '''对每个小文件中的数据进行统计排序''' fs = [] if not os.path.exists(tempdir): return for f in range(0,10): fs.append(open(tempdir +"\\tmp_"+str(f)+".txt", 'r+')) for f in fs: D = {} for ip in f: if ip in D: D[ip] += 1 else: D[ip] = 1 sorted_D = sorted(D.items(), key=operator.itemgetter(1), reverse=True) f.seek(0,0) f.truncate()#清空小文件内容 for item in sorted_D:#将排好序的内容写入小文件 f.write(str(item[1]) + "\t" + item[0]) f.close() if __name__ == '__main__': print("Start At: " + str(ctime())) sortipinfile() print("End At: " + str(ctime()))
3. 堆排序
from time import ctime import os import heapq tempdir = "e:\\temp" destfile = "e:\\sorted.txt" def decorated_file(f): """ Yields an easily sortable tuple. """ # 迭代函数,避免将数据一次读入内存 for line in f: count, ip = line.split('\t',2) yield (-int(count), ip) def mergefiles(): fs = [] if not os.path.exists(tempdir): return for f in range(0,10): #已排序文件tmp_i,txt列表 fs.append(open(tempdir +"\\tmp_"+str(f)+".txt", 'r+')) f_dest = open(destfile,"w")#存放最终排好序的结果 lines_written = 0 #调用堆排序算法 merge(*iterables) for line in heapq.merge(*[decorated_file(f) for f in fs]): f_dest.write(line[1]) lines_written += 1 return lines_written if __name__ == '__main__': print("Start At: " + str(ctime())) print("sorting completed, total queries: ", mergefiles()) print("End At: " + str(ctime()))
最终的结果ip按重复次数的从高到低保留在sorted.txt中。
总结:分布式数据,hash映射,hash统计,外\堆排序是处理海量数据的一把利器,有机会可以在Hadoop上实现reducer和mapper的过程。