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 = '