原文地址

从50多分钟到3分钟的优化

某推荐系统需要基于Spark用ALS算法对近一天的数据进行实时训练, 然后进行推荐. 输入的数据有114G, 但训练时间加上预测的时间需要50多分钟, 而业务的要求是在15分钟左右, 远远达不到实时推荐的要求, 因此, 我们与业务侧一起对Spark应用进行了优化.

另外提一下, 该文最好与之前我写的另一篇blog < Spark + Kafka 流计算优化 > 一起看, 因为一些细节我不会再在该文中描述.

优化分析

从数据分析, 虽然数据有114G, 但ALS的模型训练时间并不长, 反而是数据加载和ALS预测所占用的时间较长. 因此我们把重点放在这两点的优化中.

从Spark的Web UI可以看到, 数据加载的job中task的数量较多, 较小. 模型预测的job也是有这样的问题, 因此, 可以猜测并行度过大造成了集群的协调负荷过重.

仅降低并行度和优化JVM 参数

我们用常见的”rdd.repartitionBy”把并行度降低后, 作业计算耗时减少并不明显, 且在模型预测的job中会有executor死掉的现象. 进而查看日志, 发现是内存占用过多, yarn把Spark应用给kill了.

为解决该现象, 加入这些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 " 具体说明和原因请参考本人的另一篇blog < Spark + Kafka 流计算优化 >.

笛卡尔积操作中的预先分块

作了上述例行优化后,  ALS预测步骤的耗时减少依然不明显, 为发掘原因, 我们看了下源码, 惊奇的发现在ALS预测中竟然是用了笛卡尔积操作, 114G的数据少说也有几千万行记录, 几千万行记录进行笛卡尔积, 不慢才怪吧.

还好, 我们有扩展版的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, 十个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的运行效率影响不大, 效果有限.

上图,是val inputData = sc.wholeTextFiles(dirstr,80) 和RangePartition.

貌似加载速度也好不到哪儿去.第一个job用了11min

'

上图, 用的是val inputData = sc.textFile(dirstr)和RangeRepartition,同等情况下,加载也需要11min. (见job6)

上图,是val inputData = sc.wholeTextFiles(dirstr,120) 和RangePartition.

貌似提高了wholeTextFiles()的split数量可以提升性能, 从8.4min多(此时split为80左右)到现在的8.1min

上图,是val inputData = sc.wholeTextFiles(dirstr,220) 和RangePartition. 第一次加载提升到6.0min.

其它job由于loclity的问题,时间有所拉长, 影响不大.

上图,是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左右.

减少笛卡尔积计算量

回到对笛卡尔积计算的优化, 因为50分钟的计算量基本上都是耗在笛卡尔积的计算上的.

我们先看一下task的分布图, 由图看到, 数据并不是十分散列:

    猜测原因肯是通过Array.mkString.hashCode作为key并不能保证数据的均匀散列.因此, 我们disable掉了在笛卡尔积计算前的预分块时的再分区, 而是把再分区提到分块之前.

这样一来, task分布稍微均衡了一些, 但依然不甚理想. 为了合理的降低task数量和均匀task的分布, 我们进一步使用了Spark扩展版本的自动分区功能.

val userFactors = ExtSparkHelper.repartionPairRDDBySize(oldUsrFact, ThreeM)

这个方法有两个参数, 第一个是需要分区的RDD, 第二个是我们期望的每一个task的input data的大小. 一般来说, input data与计算产生的data的大小相差不大, 但笛卡尔积却不同, 有可能产生上百倍的中间数据量. 因此, 我这里设置的每个task的input data是3M, 计算产生的中间数据刚好在1G左右, 一个executor可以同时跑3个task, 也算是比较理想的.

优化后的task分布图也比较理想, 十分均匀且没有任何浪费, 如下:

因此, 这一步优化后, 原笛卡尔积的运行速度从几十分钟变为十几秒.

整个ALS应用原来跑114G数据需要20多分钟,如下图现在只需要不到4min:

试了一下跑一天的全量数据, 共231.9G, 则原来需要1个多小时,现在只要不到6分钟, 如下图:

[转] - spark推荐 - 从50多分钟到3分钟的优化的更多相关文章

  1. 1.java小作业-计算1到100的整合-指定输入多少行输出就打印多少行-打印24小时60分钟每一分钟-重载基础练习-面向java编程初学者

    可能有和我一样刚开始学习java的小伙伴们, 可以或多或少了解一点别的语言知识,我就是中途转过来的, 明白一点,关键不在语言本身····· 所以面对初学者来说,基础要学好, 下面列举几个没什么难度的小 ...

  2. 【MySQL】花10分钟阅读下MySQL数据库优化总结

    1.花10分钟阅读下MySQL数据库优化总结http://www.kuqin.com2.扩展阅读:数据库三范式http://www.cnblogs.com3.my.ini--->C:\Progr ...

  3. top,它们的意思分别是1分钟、5分钟、15分钟内系统的平均负荷。

    理解Linux系统负荷   作者: 阮一峰 日期: 2011年7月31日   一.查看系统负荷 如果你的电脑很慢,你或许想查看一下,它的工作量是否太大了. 在Linux系统中,我们一般使用uptime ...

  4. 个性化推荐调优:重写spark推荐api

    最近用spark的mlib模块中的协同过滤库做个性化推荐.spark里面用的是als算法,本质上是矩阵分解svd降维,把一个M*N的用户商品评分矩阵分解为M*K的userFeature(用户特征矩阵) ...

  5. 2016最新 wamp2.5+windows 10安装CoedSgniffer代码格式检查:5分钟安装 30分钟入门和浏览常用命令

    14:59 2016/1/112016最新 wamp2.5+windows 10安装CoedSgniffer代码格式检查:注意问题:1.手动安装2.5.0和pear安装方式都成功但是执行时无任何反映, ...

  6. python获取时间————前一天后一天前一小时后一小时前一分钟后一分钟

    获取当天日期 一: import time print(time.strftime("%Y-%m-%d")) #输出当前日期 2018-05-01 二: import dateti ...

  7. hadoop的mapReduce和Spark的shuffle过程的详解与对比及优化

    https://blog.csdn.net/u010697988/article/details/70173104 大数据的分布式计算框架目前使用的最多的就是hadoop的mapReduce和Spar ...

  8. mysql 中两个日期相减获得 天 小时 分钟 或者 小时:分钟的格式

    /**有一个需求,要求获得两个日期想减的天数,小时数,分钟数.通过查找资料,于是乎我写出了如下代码,来获得两个字段.*/ IFNULL(CONCAT( ,'-',''), ),),'天')), ),) ...

  9. 用笛卡尔积来创建一千六百万大表 整体19分钟 大表建成两分钟 设置id13分钟

    昨天拙文中讲述了用自增方式创建一千六百万大表的方案,这回讨论的是用笛卡儿积,实践证明这种方案更快. 2020年3月15日08点58分实验开始 创建仅有四千数据的tb_4thousand1表: SQL& ...

随机推荐

  1. eclipse-在编译项目时js特别慢的问题

    eclipse在编译项目时,当项目中导入了很多第三方js库时,eclipse在验证js时会消耗大量的时间,而我们却完全不用考虑那些js是否有错误 步骤: 去除eclipse的JS验证: 1.将wind ...

  2. Docker 管理应用程序数据

    1.将Docker主机数据挂载到容器 Docker提供三种不同的方式将数据从宿主机挂载到容器中:volumes , bind mounts 和tmpfs volumes:  Docker管理宿主机文件 ...

  3. AJAX技术之DWR框架

    DWR(Direct Web Remoting)是一个用于改善web页面与Java类交互的远程服务器端Ajax开源框架,可以帮助开发人员开发包含AJAX技术的网站.它可以允许在浏览器里的代码使用运行在 ...

  4. 分享:android图片浏览器—类微信朋友圈相片浏览【android代码下载】

    今天给大家分享个android图片/相册浏览器,类似微信朋友圈相片浏览,可以左右滑动,可以双击放大,捏拉放大 效果如下:<ignore_js_op> device-2013-09-04-1 ...

  5. 第三部分:Android 应用程序接口指南---第二节:UI---第八章 Toast通知

    第8章 Toast通知 Toast通知是在窗口前面弹出的信息.它只占有信息所需要的空间量,并且用户当前的activity仍然是可见的.可互动的.这种通知自动地淡入和淡出,它不接受交互事件.他相当于一种 ...

  6. vue-worker的介绍和使用

    vue-worker把复杂的web worker封装起来,提供一套非常简明的api接口,使用的时候可以说像不接触worker一样方便.那么具体怎么使用呢? 安装 npm i -S vue-worker ...

  7. epoll的由来

    reference https://www.zhihu.com/question/20122137 感谢 @静海听风 @蓝形参 数据流有两个重要的参与者: 1.往流中写入数据者 2.从流中读取数据者 ...

  8. 每日英语:How To Survive The Windows XPiration Date

    The default background for Microsoft's Windows XP operating system -- a perfect blue sky full of cot ...

  9. 【转】Django中使用POST方法获取POST数据

    1.获取POST中表单键值数据 如果要在django的POST方法中获取表单数据,则在客户端使用JavaScript发送POST数据前,定义post请求头中的请求数据类型: xmlhttp.setRe ...

  10. js实现cookie跨域功能

    /** * 设置cookie方法 * @param {string} name cookie键值 * @return {*} 返回cookie值 */ function setCookie_log(c ...