HDFS是一个大数据存储系统,主要面向的场景是一次写入多次读取,大文件。但是在实际使用过程中由于各种原因集群中总是充斥着各种小文件,而且数据惊人。无论社区还是公司都在积极解决这个问题。(为什么要解决小文件,这个问题我想看到这篇文章的人应该都知道啊!)

无论你怎么优化集群,指定各种规范,让业务方优化程序,小文件总是无法避免的。

都说解决一个问题,不能只从表面去解决,需要从根上彻底解决,需要根据产生问题的n个原因去解决,但是这种方式对目前小文件这个问题上确实不好办。即使你知道小文件产生过多的原因很大一部分归结于用户的应用程度在执行的时候没有很好的预估写出数据量的规模,导致写出过多的小文件。你依然无法从根上解决这个问题,因为你要想从根上解决这个问题需要用户对自己程序的逻辑和数据有较高的了解,而且用户的水平也参差不齐,最最重要的是用户认为这是尼玛平台该优化的事,干毛浪费我的时间给你搞。所以种种原因造成收益较少。

不能从根上解决这个问题,那就只能从面上解决这个问题了。怎么解决?那就是定期对小文件进行合并(是不是比较low,嘿嘿)。

合并方案

  1. Hadoop Archives
    很早提出的一种解决方案,把历史数据进行归档。个人感觉比较鸡肋,不怎么好用
  2. SequenceFile
    SequenceFile是hadoop的一种二进制文件,随着Hadoop的问世就出现了,当时主要用来存储一些mr的中间结果。因为其也是key/value形式存储的,所以现在也有很多公司使用SequenceFile来存储小文件,key是文件名,value是内容。这种方式较依赖使用的场景,感觉不是一个普适的解决方案
  3. 业务方优化代码
    业务方在代码中根据数据量控制分区的个数,直接从源头解决小文件问题。这种方案太理想,进度无法把控,而且小文件总会在新上线的程序中复现
  4. 平台定期合并
    编写合并小文件的小工具,对集群中小文件top的目录进行定期自动合并。实现方式较简单,而且容易把控,主动权在平台手里
  5. 黑科技
    实时关注业界动态,期待社区提供黑科技完美解决小文件问题。目前社区提供了一个Ozone对象存储系统,还不太完善,持续关注中,随后写个简单的调研结果

合并实践

利用工具合并小文件时,需要注意的是不同文件类型的合并方式不一样,所以在合并之前需要知道文件的类型从而选择合适的方法去合并。

目前平台中的文件类型主要为3中,分别为普通文本、lzo压缩文件和parquet列存储文件,所以自动识别文件类型也就是自动文件的编码格式。

自动识别文件类型

  1. 根据压缩格式中序列化的特殊信息识别压缩格式,如parquet文件的头为’PAR1’
  2. 根据文件的后缀名进行识别,如lzo的文件都有后缀

个人感觉识别文件类型最好的方法是通过后缀名,通过改Hadoop和Hive的代码,对普通文本增加后缀.txt,parquet文件增加后缀.parquet,目前暂时使用的通过读取文件头信息的方式来识别文件类型,只所以没有使用增加后缀的方式是因为这种方式需要进行较全面的测试,为了快速验证合并小文件的可行性选择了识别文件头的方式。

合并方式

  1. 普通文本和lzo文件利用CombineFileInputFormat将同一目录下的小文件合并读取,然后调用CombineTextInputFormat.setMaxInputSplitSize根据读取文件的大小进行切分,然后直接通过map输出,达到小文件根据期望输出文件的大小进行合并的效果。
  2. parquet列存储文件有点特殊,目前最好的方法是使用spark api进行读取,然后通过repartition进行合并。repartition的个数可以通过输入文件的总大小和期望输出文件的大小进行预计算而得。

这样初期由平台统一进行合并(这里合并只能对历史文件进行合并),随后可以慢慢督促各个业务进行优化,先解决燃眉之急。