spark merge文件

刚开始使用spark第一个功能就是合并两个文件,相比于python的pandas合并两个文件,spark在速度上快了不少,而且几乎不在乎文件大小,最大尝试过150G文件大小的merge,而对于pandas而言超过10G的文件已经就无法处理了,使用spark处理文件已经成了刚需。

使用spark合并两个文件比较简单,只不过使用过程中发现了不少的坑,主要分为以下几个步骤

  • 读取文件
var df = spark.read.option("delimiter",delimiter).option("header",true).option("inferSchema", inferSchema).option("maxColumns",50000).csv(path=path)

spark 是sparkSession对象,option("maxColumns",50000)这里是其中一个坑,在使用过程中当数据文件列数少于20000时没有什么异常,但是当列数变多的时候,发现读取文件会报错,因此将上限提高为5w,因为读取操作比较常用,将该功能封装为公共函数

def read(spark:SparkSession,path:String,delimiter:String,inferSchema:String):DataFrame={
    var df = spark.read.option("delimiter",delimiter).option("header",true).option("inferSchema", inferSchema).option("maxColumns",50000).csv(path=path)
    println("rdd partions :"+df.rdd.partitions.length)
    if(df.columns.contains("loan_dt")){
      df=df.withColumn("loan_dt",Utils.formatLoan_dt(df("loan_dt")))
    }
    return df
  } 
def read(path:String,delimiter:String="\t",inferSchema:String="false"):DataFrame={
    val spark = SparkEnv.getSession
    return read(spark,path,delimiter=delimiter,inferSchema=inferSchema)
}

 普通的文件这样处理就没什么问题了,如果dataframe的列名里面包含了"." 在进行select操作的时候就会报错,为了解决这个问题,在读取完文件后先将列名中的“.”替换为‘#’,等处理完成后再替换回来,columns是替换的列名

df = df.toDF(columns:_*)
  • 合并文件

标准的spark的dataframe提供了join函数,通过该函数就能直接join,but这里有个坑存在,如果合并的列中存在空值,在最终合并的文件中会join不上,具体原因不详。这里提供了一种解决方案,先将待合并的两个文件中join的列里面的空值替换为“none”字符串,再进行合并,合并完成后再替换回来

    val ts = udf((x:String)=>if(x==null)"None" else x)
    val rts = udf((x:String)=>if(x=="None")null else x)

    for(cols<-(usingCols.toSet)){
      leftDfts=leftDfts.withColumn(cols,ts(leftDf(cols)))
      rightDfts=rightDfts.withColumn(cols,ts(rightDfts(cols)))
    }
    var res = leftDfts.join(rightDfts,usingCols,joinType = joinType)
    for(cols<-(usingCols.toSet)){
      res=res.withColumn(cols,rts(res(cols)))
    }
  • 其他功能

merge操作在平时使用频率很高,本着解放生产力的原则对一些功能进行了封装,对常用参数进行了默认处理

package com.test.spark

object Merge {

  def main(args: Array[String]): Unit = {

    //val test = Array[String]("--input","test","test2","--out","test3","--joinType","inner")
    val ps = Utils.getPs(args)
    val out =  ps.get("out").get(0)
    val joinType = ps.get("joinType").getOrElse(List("inner"))(0)
    var filepathlist =  ps.get("input").get
    var filelist = filepathlist.map(line=>Utils.read(line))
    if(ps.contains("repartions")){
      val repartions = ps.get("repartions").get(0).toInt
      filelist = filelist.map(line=>line.repartition(repartions))
    }
    var res = filelist(0)
    for(f<-filelist.drop(1))
    {
        res = Utils.join(res,f,List(),joinType)
    }
    res = Utils.rtsCols(res)
    Utils.write(res,out)
  }

}

其中 Utils.getPs(args) 是自己封装的一个函数,主要是将命令行调用传入的参数转换为 key-value的形式,这样调用命令行的时候参数的顺序和个数就可以任意比较灵活,代码如下

  def getPs(args: Array[String],prex:String="--"):Map[String,List[String]]={
    var params= Map[String,List[String]]()
    var key = ""
    for(item<-args){
      if(item.length>=prex.length && prex==item.substring(0,prex.length)){
        key = item.substring(prex.length)
        params+=(key->List[String]())
      }
      else{
        var value = params.get(key).get
        value = value:+item
        params+=(key->value)
      }
    }
    params
  }
  def getParam(params:Map[String,List[String]],key:String):String={
    var res = ""
    res = if(params.contains(key)) params.get(key).get(0) else ""
    res
  }

将上述功能进行了封装,调用形式 spark_submit xx.jar --input file1 file2 file2 ... --out file_out --joinType inner 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值