#背景
项目中, 由网络爬虫爬取的日志文件, 需要导入到 hive 数据仓库中, 但日志文件包含很多的小文件(指代文件大小远小于HDFS块大小的文件,hadoop 2.x之后,HDFS块大小默认未128M,那么1M,3M之类的文件都称为小文件), 散落在许多子文件夹下.
总所周知, 当 hive 的输入端如果由许多小文件组成的话, 每个小文件都会启动一个 map 任务, 每调用一次map()函数将创建一个映射器,如果文件多而小, 会造成 map 任务启动和初始化的时间远大于逻辑处理的时间, 造成数据处理时间大大增加, 甚至会造成 OOM的后果.
另外,HDFS的文件元信息,包括位置、大小、分块信息等,都是保存在NameNode的内存中的。每个对象大约占用150个字节,因此一千万个文件及分块就会占用约3G的内存空间,一旦接近这个量级,NameNode的性能就会开始下降了。
此外,HDFS读写小文件时也会更加耗时,因为每次都需要从NameNode获取元信息,并与对应的DataNode建立连接。对于MapReduce程序来说,小文件还会增加Mapper的个数,每个脚本只处理很少的数据,浪费了大量的调度时间。
鉴于此, 当我们执行一个hive任务时, 当发现数据量不大, 但任务数巨多时, 就要考虑是不是小文件在作祟. 如果是, 请在 Map 前进行输入的合并.
hive 通过设置启动参数的方式, 社区有很多文章介绍, 这里引用一下这篇文章:
Hive小文件合并
以下主要介绍编程方式合并.
#代码
话不多少, 直接上 python 代码:
#!/usr/bin/python
# -*- coding:utf8 -*-
import os
allFileNum = 0
def merge_small_file(level, path):
global allFileNum
'''''
打印一个目录下的所有文件夹和文件
'''
# 所有文件夹,第一个字段是次目录的级别
dirList = []
# 所有文件
fileList = []
# 返回一个列表,其中包含在目录条目的名称(google翻译)
files = os.listdir(path)
# 先添加目录级别
dirList.append(str(level))
for f in files:
targetFile = "/data/admin/dong_bao_spider/merge_json_files.json"
output_json_file = open(targetFile,'a')
if(os.path.isdir(path + '/' + f)):
# 排除隐藏文件夹。因为隐藏文件夹过多
if(f[0] == '.'):
pass
else:
# 添加非隐藏文件夹
dirList.append(f)
getJsonFilePath(level, path+'/'+f)
if(os.path.isfile(path + '/' + f)):
# 判断文件是以".json"结尾的
if str(f).endswith('.json'):
# 添加文件
fileList.append(path + '/' + f)
allFileNum += 1
with open(str(path + '/' + f)) as input_json_file:
for r in input_json_file.readlines():
# 去掉每一行后面的换行符,先加换行符
output_json_file.write(r.strip('\n') + '\n')
output_json_file.close()
# 以下代码为演示程序扫描过的文件夹, 分层显示
# 当一个标志使用,文件夹列表第一个级别不打印
# i_dl = 0
# for dl in dirList:
# if(i_dl == 0):
# i_dl = i_dl + 1
# else:
# # 打印至控制台,不是第一个的目录
# print '-' * (int(dirList[0])), dl
# # 打印目录下的所有文件夹和文件,目录级别+1
# printPath((int(dirList[0]) + 1), path + '/' + dl)
# for fl in fileList:
# if str(fl).endswith(".json"):
# # 打印文件
# print '-' * (int(dirList[0])), fl
# # 随便计算一下有多少个文件
# allFileNum = allFileNum + 1
# for file in fileList:
# print file
if __name__ == '__main__':
merge_small_file(1, '/Users/david/Downloads/dong_bao_output/')
print 'Files in total =', allFileNum