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 ,算是比较理想的