小文件相关问题的处理思路
友情链接
问题背景
很多客⼾在⽣产环境都会遇到小文件问题,小文件可能来⾃于上游系统,可能来⾃于书写不当的sql,也可能是错误数据导致join ⽣成⼤量小文件。 用户侧观察到的现象就是,性能下降,任务报错,甚⾄executor lost。
小文件问题隐患
- 导致map 任务⾮常多,从而性能下降;
- 小文件过多,可能会影响到后续reduce任务数量暴增,从⽽导致出现task 超过10w的情况,这时候引擎会出于⾃我保护会报错;
- 小文件如果非常小,在进⾏automerge后,还可能导致executor端因为每个task需要读太多小文件,所以会在⼀个task中new过多DFSClient,DFSInputStream,Path,JobConf对象等,导致 executor端内存和gc 压⼒很⼤,甚⾄executor lost;
解决思路
一般来说,一个任务是由几个步骤组成的,而小文件的产生也来自任务的各个流程和步骤:
上游 => 本地文件系统 => HDFS => Map => Reduce => FileSink
所以解决小文件问题就是从上面步骤中入手。而且解决小文件的隐患,肯定是越早越好的。跟过滤下推一样,能尽早做的过滤条件一定是尽早过滤。
就比如,能在本地文件系统解决的问题,没必要放到hdfs上解决,HDFS本身就不适合存储大量小文件,小文件过多会导致namenode元数据特别大, 占用太多内存,严重影响HDFS的性能。
方案
- 上游系统改进
- linux文件系统合并上游系统改进
- hdfs 合并:hdfs dfs -appendToFile a.txt b.txt c.txt hdfs://${namenode}/data/all.txt
- 存储端合并(不同表格式采用不同的compact机制,具体可参考小文件详解文章)
- maptask阶段合并:使用automerge功能(使用方法具体可参考小文件详解文章中计算端合并的章节内容)
- reduce 阶段合并:reduce tasks 设置,mapred.reduce.tasks,如果没有sql本来没有reduce,可以加上:distribute by rand();(具体设置值可参考小文件详解文章中计算端合并的章节内容)
- 设置合理的分区和分桶,过多的分桶,或者不合理的分区,可能导致分区和分桶比较多,小文件++
- 数据正确性检查,有时候是数据错误导致生成了过多小文件。
实例
某用户,上游应用产生了57000 个小文件,每个文件一行数据。创建text 外表后,执行任务报了task 超过10w。
第一反应先走automerge,开启automerge后,数据本地合并后大概合并了出了不到10 个task,然后就把executor 跑lost了。
问题是每个任务需要读的文件太多了,一个task中连续new DFSClient,DFSInputStream,Path,JobConf 等对象,导致gc压力太大,最后executor lost。
所以给到该用户的解决方案是:
- 尽量在本地文件系统上合并
- 本地文件系统如果不能合并,在hdfs 上使用append file 合并
- 如果不能在local filesystem或者hdfs合并,考虑创建中间表,通过reduce task + distribute by来降低文件数量