1.  背景介绍

  将一份数据量很大的用户属性文件解析成结构化的数据供查询框架查询剖析,其中用户属性包含用户标识,平台类型,性别,年龄,学历,兴趣爱好,购物倾向等等,大概共有七百个左右的标签属性。为了查询框架能够快速查询出有特定标签的人群,将最终的存储结果定义为了将七百个左右的标签属性展平存储为parquet文件,这样每个标签属性对于用户而言只有存在和不存在两种情况。

2. 第一版实现过程

   第一步,将用户所有标签标识作为一个资源文件保存到spark中,并读取该资源文件的标签标识为一个标签集合(定义为listAll),并通过sparkContext来进行广播;
     第二步,使用spark core读取hdfs上的用户属性文件(其中每行是一个用户所拥有的标签),将单个用户所拥有的标签解析成一个标签集合(定义为listUser),也就是说listUser是listAll的一个子集;
   第三步,对于单个用户而言,遍历步骤一的结果集listAll,对于每一个标签判断该用户是否存在,如果存在则将标签设置为1(表示存在),否则设置为0(表示不存在),并将所有标签及相应的值保存为一个Map(定义为map)
   第四步,根据第三步的map构造成spark sql中的Row
   第五步,依据第一步的集合listAll构造出spark sql的Schema
   第六步,将第四步和第五步的结果通过spark sql的createDataFrame构造成DataFrame。
   第七步,通过DataFrame.write.parquet(output)将结果保存到hdfs上
     通过上述的七步,认为已经很easy的处理完了这个需求,但是真正测试时发现性能比想象的要慢的多,严重的达不到性能要求。对于性能影响究竟出现在什么地方?初步猜测,问题出现在第四步,第六步,第七步的可能性比较大。经过实际的测试,发现性能主要消耗在第七步,其他步骤的执行都特别快。这样也就定位到了问题
  而且通过测试知道,生成parquet消耗的性能最高,生成json的话很快就能完成,如果不生成任何对象,而是直接foreach执行的话,性能会更高。而且相同数据量下,如果列数在七百多个时,json写入时间是parquet写入时间的三分之一,如果列数在四百个时,json写入时间是parquet写入时间的二分之一,如果列数在五十个,json写入时间是parquet写入时间的三分之二。也就是列数越少,json和parquet的写入速度越接近。至于为什么生成parquet性能很差,待后续分析spark sql的save方法。
  测试的例子
  private def CTRL_A = '\001'

  private def CTRL_B = '\002'

  private def CTRL_C = '\003'

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

    val resourcePath = this.getClass.getResource("/resource.txt").getFile
val sourcePath = this.getClass.getResource("/*.gz").getFile
val output = "/home/dev/output" val conf = new SparkConf().setAppName("user test").setMaster("local")
val sc = new SparkContext(conf)
val sqlContext = new SQLContext(sc)
sqlContext.setConf("spark.sql.parquet.binaryAsString", "true")
sqlContext.setConf("spark.sql.inMemoryColumnarStorage.compressed", "true")
sqlContext.setConf("spark.sql.parquet.compression.codec", "snappy")
val map: Map[String, String] = buildResource(resourcePath)
val schema = buildSchema(map)
val bd = sc.broadcast(map)
val bdSchema = sc.broadcast(schema) val start=System.currentTimeMillis()
val rdd = sc.textFile(sourcePath)
.map(line => {
val map = buildUser(line, bd.value)
buildRow(map._3, map._1, map._2)
})
// rdd.foreach(_=>())
// sqlContext.createDataFrame(rdd, bdSchema.value).write.mode(SaveMode.Overwrite).json(output)
sqlContext.createDataFrame(rdd, bdSchema.value).write.mode(SaveMode.Overwrite).parquet(output)
val end = System.currentTimeMillis()
System.out.print(end - start)
} /**
* 读取资源文件
* @param file
* @return
*/
def buildResource(file: String): Map[String, String] = {
val reader = Source.fromFile(file)
val map = new mutable.HashMap[String, String]()
for (line <- reader.getLines() if !Strings.isNullOrEmpty(line)) {
val arr = StringUtils.splitPreserveAllTokens(line, '\t')
map.+=((arr(0), "0"))
} map.toMap
} /**
* 生成用户属性
* @param line
* @param map
* @return
*/
def buildUser(line: String, map: Map[String, String]): (String, Int, Map[String, String]) = {
if (Strings.isNullOrEmpty(line)) {
return ("", 0, Map.empty)
}
val array = StringUtils.splitPreserveAllTokens(line, CTRL_A)
val cookie = if (Strings.isNullOrEmpty(array(0))) "-" else array(0)
val platform = array(1).toInt
val base = buildFeature(array(2))
val interest = buildFeature(array(3))
val buy = buildFeature(array(4))
val features = base ++ interest ++ buy
val result = new mutable.HashMap[String, String]()
for (pair <- map) {
val value = if (features.contains(pair._1)) "1" else "0"
result.+=((pair._1, value))
}
(cookie, platform, result.toMap)
} /**
* 抽取用户标签
* @param expr
* @return
*/
def buildFeature(expr: String): Array[String] = {
if (Strings.isNullOrEmpty(expr)) {
return Array.empty
}
val arr = StringUtils.splitPreserveAllTokens(expr, CTRL_B)
val buffer = new ArrayBuffer[String]()
for (key <- arr) {
val pair = StringUtils.splitPreserveAllTokens(key, CTRL_C)
buffer += (s"_${pair(0)}")
}
buffer.toArray
} /**
* 动态生成DataFrame的Schema
* @param map
* @return
*/
def buildSchema(map: Map[String, String]): StructType = {
val buffer = new ArrayBuffer[StructField]()
buffer += (StructField("user", StringType, false))
buffer += (StructField("platform", IntegerType, false))
for (pair <- map) {
buffer += (StructField(s"_${pair._1}", IntegerType, true))
}
return StructType(List(buffer: _*))
} /**
* 将用户属性构造成Spark SQL的Row
* @param map
* @param user
* @param platform
* @return
*/
def buildRow(map: Map[String, String], user: String, platform: Int): Row = {
val buffer = new ArrayBuffer[Any]()
buffer += (user)
buffer += (platform)
for (pair <- map) {
buffer += (pair._2.toInt)
}
return Row(buffer: _*)
}

3. 第二版实现过程

  在第一版中初步怀疑是DataFrame在生成parquet时进行了一些特殊逻辑的处理,所以决定自己实现ParquetWriter方法来测试下性能,采用了avro来向parquet中写入数据。方法大概包含定义好avro资源文件,然后使用AvroParquetWriter类来向parquet中写入内容,具体的写入方法类似于https://blog.csdn.net/gg584741/article/details/51614752。通过这种方式来写入parquet,相同数据量的情况下,性能提升了一倍多。至于为什么性能有这么大的提升,有待后续研究。到此优化就告一段落了。

   在此优化期间,遇到了下列问题:
  1.  avro 的资源文件在生成java类时,属性限制必须255个一下。该限制在https://issues.apache.org/jira/browse/AVRO-1642 提到。
      2.  java 类属性和方法参数也需要小于255个,详见https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.11,https://stackoverflow.com/questions/30581531/maximum-number-of-parameters-in-java-method-declaration
 
     对于上述显示的解决方案是在maven配置文件中不适用avro-maven-plugin插件来自动生成java类,而是在程序运行时通过

val Schema = (new Schema.Parser()).parse(new File(file))

来动态生成Schema来供后续AvroParquetWriter使用。

 

spark生成大宽表的parquet性能优化的更多相关文章

  1. Spark Tungsten揭秘 Day1 jvm下的性能优化

    Spark Tungsten揭秘 Day1 jvm下的性能优化 今天开始谈下Tungsten,首先我们需要了解下其背后是符合了什么样的规律. jvm对分布式天生支持 整个Spark分布式系统是建立在分 ...

  2. Hadoop如何将TB级大文件的上传性能优化上百倍?

    这篇文章,我们来看看,Hadoop的HDFS分布式文件系统的文件上传的性能优化. 首先,我们还是通过一张图来回顾一下文件上传的大概的原理. 由上图所示,文件上传的原理,其实说出来也简单. 比如有个TB ...

  3. android app性能优化大汇总(UI渲染性能优化)

    UI性能测试 性能优化都需要有一个目标,UI的性能优化也是一样.你可能会觉得“我的app加载很快”很重要,但我们还需要了解终端用户的期望,是否可以去量化这些期望呢?我们可以从人机交互心理学的角度来考虑 ...

  4. 一次EF批量插入多表数据的性能优化经历

    距离上次的博客已经有15个多月了,感慨有些事情还是需要坚持,一旦停下来很有可能就会停很久或者从此再也不会坚持.但我个人一直还坚持认为属于技术狂热份子,且喜欢精益求精的那种.最近遇到两个和数据迁移相关的 ...

  5. kettle大数据量读写mysql性能优化

       修改kettleDB连接设置 1. 增加批量写的速度:useServerPrepStmts=false  rewriteBatchedStatements=true  useCompressio ...

  6. Sql Server RowNumber和表变量分页性能优化小计

    直接让代码了,对比看看就了解了 当然,这种情况比较适合提取字段较多的情况,要酌情而定 性能较差的: WITH #temp AS                       (              ...

  7. android app性能优化大汇总

    这里根据网络上各位大神已经总结的知识内容做一个大汇总,作为记录,方便后续“温故知新”. 性能指标: (1)使用流畅度:  图片处理器每秒刷新的帧数(FPS),可用来指示页面是否平滑的渲染.高的帧率可以 ...

  8. Oracle12c 性能优化攻略:攻略1-3: 匹配表类型与业务需求

    注:目录表 <Oracle12c 性能优化攻略:攻略目录表> 问题描述 你刚开始使用oracle数据库,并且学习了一些关于可用的各种表类型的知识.例如:可以在堆组织表.索引组织表等之间支出 ...

  9. Elasticsearch 通关教程(七): Elasticsearch 的性能优化

    硬件选择 Elasticsearch(后文简称 ES)的基础是 Lucene,所有的索引和文档数据是存储在本地的磁盘中,具体的路径可在 ES 的配置文件../config/elasticsearch. ...

随机推荐

  1. mac npm编译的时候,一直报 node 镜像找不到

    目前我所知的方法就是卸载.重装node,或者是升级node 卸载: brew uninstall node 安装: brew install node 升级: brew upgrade node

  2. css3奇数偶数的伪属性

    <style> /*奇数*/ ul li:nth-child(odd){ background-color: green; } /*偶数*/ ul li:nth-child(even){ ...

  3. react子传父

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. php 判断手机号 和 手机号中间四位以**** 代替

    代码: //自定义函数手机号隐藏中间四位 function get_phone($str){ $str=$str; $resstr=substr_replace($str,'****',3,4); r ...

  5. VS调试时JSON格式文件加载不了

    在使用VS2012进行调试时发现加载数据为JSON格式的都加载不了,应该是MIME类型没有正确设置的问题. 直接通过浏览器地址栏访问时会收到提示,根据提示在IIS EXPRESS的安装目录下面执行一条 ...

  6. Nginx负载均衡后端健康检查

    参考文档:https://www.cnblogs.com/kevingrace/p/6685698.html 本次使用第三方模块nginx_upstream_check_module的,要使用这个第三 ...

  7. [Day12]构造方法、关键字this、super

    1.构造方法:对象创建时要执行的方法 (1)构造方法的格式: 修饰符 构造方法名(参数列表){} 体现: a.构造方法没有返回值类型,也不需要返回值 b.构造方法名称必须和类型保持一致 c.构造方法没 ...

  8. Python脚本之安装linux源码包-Jenkins

    最近开始学Python,按照网上的教程,写了一个Python脚本下载Jenkins并运行的脚本,很简单. 首先使用vi命令编辑一个新文件auto_built_jenkins.py(关于vi的使用可以见 ...

  9. debian使用nginx创建静态文件存储

    vim /etc/nginx/sites-available/default 在server下添加 location ~ .*\.(gif|jpg|jpeg|png)$ { expires 24h; ...

  10. MySQL行转列与列转行

    行转列 例如:把图1转换成图2结果展示 图1 图2 CREATE TABLE `TEST_TB_GRADE` ( `ID` ) NOT NULL AUTO_INCREMENT, `) DEFAULT ...