十三 Spark机器学习ALS设计

ALS:协同过滤推荐算法
一  通过kafka的日志来做分析处理
二 SparkSQL 离线场景,把数据存入到msyql当中,然后处理
三 SparkScreaming: 一般情况下,只拿最近几天(例如淘宝)
ALS设计之优化方案
推荐系统需要基于Spark用ALS算法对近一天的数据进行实时训练,然后进行推荐,输入的数据有114G,但是训练时间+预测时间
近1个小时,业务需要在10分钟左右,远远达不到实时推荐要求,需要对Spark进行优化

优化分析
  从数据分析,虽然数据有114G,但是 ALS的模型训练时间不长,反而是数据加载和ALS预测所占用的时间较长,因此需要在数据加载和预测上进行优化
从Spark的Web UI可以看到,数据加载的job中task的数量较多,较小,模型预测的job也有这样的问题,并行度过大造成集群的协调负荷过重。

一 仅降低并行度和优化JVM参数
  常见的"rdd.repartitionBy"把并行度降低后,作业计算耗时并不明显,且在模型预测的job中会有executor死掉的现象,进而查看日志,发现时内存占用过多,yarn把Spark应用杀掉了
为解决该现象, 加入这些JVM参数: “ -XX: +UseParNewGC - XX:+UseConcMarkSweepGC - XX: MaxTenuringThreshold = 72 - XX:NewRatio = 2 - XX:SurvivorRatio = 6 - XX:+CMSParallelRemarkEnabled - XX: +ParallelRefProcEnabled - XX:+CMSPermGenSweepingEnabled - XX: +CMSClassUnloadingEnabled - XX:MaxTenuringThreshold = 31 - XX:SurvivorRatio = 8 - XX:+ExplicitGCInvokesConcurrent - XX: +ExplicitGCInvokesConcurrentAndUnloadsClasses - XX:+AlwaysPreTouch - XX: -OmitStackTraceInFastThrow - XX:+UseCompressedStrings - XX: +UseStringCache - XX:+OptimizeStringConcat - XX: +UseCompressedOops - XX:+CMSScavengeBeforeRemark - XX: +UseBiasedLocking - XX:+AggressiveOpts
"
二 笛卡尔积操作中的预先分块
  源码中,在ALS预测中用的是笛卡尔积操做,我们优化可以将数据预先分块,而不必一行行的进行笛卡尔积,加速了笛卡尔积的速度
val recommendations = ExtMatrixFactorizationModelHelper.recommendProductsForUsers( model.get, numRecommendations, 420000, StorageLevel.MEMORY_AND_DISK_SER )
该方法会对model中的userFeatures和itemFeatures矩阵进行预先分块,减少网络包的传输量和笛卡尔积的计算量
  这里的第三个参数是每一个块所包含的行数,此处的420000表示当我们对userFeatures或itemFeatures进行分块时,每个块包含了矩阵的420000行
  计算方法,由于ALS算法中设置的rank是10,因此生成的userFeatures和itemFeatures的个数是10,他们的每一行是(Int, Array[Double]),其中Array.size是10
  因此可根据如下计算每行所展的空间大小
  空Array[10]的大小=16+8*10=96Byte ,数组中的元素是Double,10个Double对象的大小是16*10=160Byte,
  作为key的Integer的大小事16Byte.因此每行占空间96+160+16=212Byte

  另外,要计算带宽:由于是千兆网卡,1Gbit,转换为Byte也就是128MByte的带宽
  考虑到 "spark.akka.frameSize=100"以及网络包包头需要占的空间,和Java的各种封装要占的空间,我们计划让1个block就几乎占满带宽,也就是一个block会在100MByte左右

  因此,一个block要占60*1024*1024/212=296766行,因此blocksize=494611,考虑到各个object 也占内存,因此行数定在420000左右
  在分块后ExtMatrixFactorizationModelHelper.recommendProductsForUsers中会对块进行重新分区, 以达到基于块的均匀分布.

三提高文件加载速度
  以前都是加载小文件,每个文件才几M大小,远远低于Hadoop的块大小,使得IO频繁,文件也频繁打开关闭,加载速度自然慢,
  为了解决这个问题,使用sc.wholeTextFiles(dirstr,inputSplitNum)来加载HDFS的文件到Spark中,该方法使用
  Hadoop的CombineFileInputFormat把多个小文件合并成一个Split再加载到Spark中。
  但其实,加载速度对整个Job的运行速率影响不大,效果有限
3.1
  val inputData = sc.wholeTextFiles(dirstr,80) 和RangePartition. 效果好不到哪里
3.2
  val inputData = sc.textFile(dirstr)和RangeRepartition,同等情况下,加载也需要11min
3.3
  val inputData = sc.wholeTextFiles(dirstr,120) 和RangePartition.
  提高了wholeTextFiles()的split数量可以提升性能, 从8.4min多(此时split为80左右)到现在的8.1min
3.4
  val inputData = sc.wholeTextFiles(dirstr,220) 和RangePartition. 第一次加载提升到6.0min.
3.5
  val inputData = sc.wholeTextFiles(dirstr,512) 和RangePartition. 第一次加载要7.1min. 估计220个split应该是个比较好的值了. 按比例就是 220(file split数) / 27000(小文件数) = 0.8% , 该场景中每个小文件10M左右. 也就是每个file split包含123个小文件­­­­, 每个file split 1230M, 也就是约1G左右.

四减少笛卡尔积的计算量
  过Array.mkString.hashCode作为key并不能保证数据的均匀散列,因此disable掉了再笛卡尔积计算钱的预分块时的再分区,而是把
  再分区提到分块之前,会在task分部稍微均衡一些,
  因此,进一步使用了Spark扩展版本的自动分区功能
  val userFactors = ExtSparkHelper.repartionPairRDDBySize(oldUsrFact, ThreeM)
  第一个是需要分区的RDD, 第二个是我们期望的每个task的input data的大小,
  input data与计算产生的data的大小相差不大,但是笛卡尔积却不同,有可能产生上百倍的中间数据量,因此这里设置的每个task的input data是3M,计算产生的中间数据刚好在1G左右,
  一个executro可以同事跑3个task ,算是比较理想的

 

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试