Hadoop小文件优化:通过Spark实现小文件合并

Spark批处理小文件合并

/**
 * 
 * @param sparkSession
 * @param absDir 要进行小文件合并的路径
 * @param partitionSize 分块的大小(一般 128 ,即 128M )
 * @param isDeleteBak 是否删除备份路径数据,1:删除;0:不删除(为确保安全,一般手动删除)
 */
def mergeFile(sparkSession: SparkSession, absDir: String, partitionSize: String, isDeleteBak: String): Unit = {
    // 文件备份路径
  	val tmpDir = "/tmp/HdfsMergeSmallFile"
    
    val hdfs = FileSystem.get(sparkSession.sparkContext.hadoopConfiguration)
    val path = new Path(absDir)

    if (hdfs.exists(path)) {
      val contentSummary = hdfs.getContentSummary(path)
      // 路径大小
      val fileLength = contentSummary.getLength
      // 文件数量
      val fileCount = contentSummary.getFileCount
      // 分区数量
      val numPartitions = math.ceil(fileLength / (partitionSize.toFloat * 1024 * 1024)).toInt
      // 目录数量
      val dirCount = contentSummary.getDirectoryCount

      /**
       * 安全起见,不能对存在子路径的路径进行处理。
       * 如,路径结构
       *    /tmp/test1/test2/a.parquet
       *    /tmp/test1/b.parquet
       *    absDir = /tmp/test1 会报错,因为存在子路径 test2。
       *    这样的设定是为了防止误传参数导致整个根目录的数据被覆盖了。
       *    若传参 absDir = /tmp/test1,由于test1下存在b.parquet,故不会报错。
       *    代码 sparkSession.read.parquet(absDir) 会将a.parquet读取然后输出到备份路径下。
       *    然后读取备份路径再覆盖写入absDir的路径,即会将absDir下的所有内容都删除。这就会存在风险隐患。
       */
      if (dirCount == 1l) {   // dirCount == 1l 为本路径
        // numPartitions + 1 和 fileCount - 1 是因为需要排除“_SUCCESS”文件的影响
        if (numPartitions + 1 >= fileCount) {
          println(s"路径[${absDir}]的分区数合理。目前路径大小为[${fileLength}]B,分区数为[${fileCount - 1}],计划调整的分区数为[${numPartitions}]。")
        } else {
          println(s"路径[${absDir}]的分区数不合理。目前路径大小为[${fileLength}]B,分区数为[${fileCount - 1}],计划调整的分区数为[${numPartitions}]。")
          // 备份的临时路径
          val outputDir = tmpDir+absDir
          println(s"正在写入到备份的临时路径[${outputDir}]。")
          sparkSession.read.parquet(absDir).repartition(numPartitions).write.mode(SaveMode.Overwrite).parquet(outputDir)
          println(s"正在覆盖写入路径[${absDir}]。")
          sparkSession.read.parquet(outputDir).write.mode(SaveMode.Overwrite).parquet(absDir)
          // 1 删除备份的临时路径
          if ("1".equals(isDeleteBak)) {
            hdfs.delete(new Path(outputDir), true)
            println(s"已删除备份的临时路径[${outputDir}]。")
          }

          println(s"合并结束")
        }
      } else {
        System.err.println(s"路径[${absDir}]下还存在子路径,请检查参数,或先删除子路径。")
//        System.exit(1)
      }
    } else {
      System.err.println(s"路径[${absDir}]不存在。")
//      System.exit(1)
    }
  }
posted @ 2024-07-23 15:47  MrSponge  Views(95)  Comments(0Edit  收藏  举报