Spark处理数据排序问题如何避免OOM


Posted in Python onMay 21, 2020

错误思想

举个列子,当我们想要比较 一个 类型为 RDD[(Long, (String, Int))] 的RDD,让它先按Long分组,然后按int的值进行倒序排序,最容易想到的思维就是先分组,然后把Iterable 转换为 list,然后sortby,但是这样却有一个致命的缺点,就是Iterable 在内存中是一个指针,不占内存,而list是一个容器,占用内存,如果Iterable 含有元素过多,那么极易引起OOM

val cidAndSidCountGrouped: RDD[(Long, Iterable[(String, Int)])] = cidAndSidCount.groupByKey()
    // 4. 排序, 取top10
    val result: RDD[(Long, List[(String, Int)])] = cidAndSidCountGrouped.map {
      case (cid, sidCountIt) =>
        // sidCountIt 排序, 取前10
        // Iterable转成容器式集合的时候, 如果数据量过大, 极有可能导致oom
        (cid, sidCountIt.toList.sortBy(-_._2).take(5))
    }

首先,我们要知道,RDD 的排序需要 shuffle, 是采用了内存+磁盘来完成的排序.这样能有效避免OOM的风险,但是RDD是全部排序,所以需要针对性的过滤Key值来进行排序

方法一 利用RDD排序特点

//把long(即key值)提取出来
    val cids: List[Long] = categoryCountList.map(_.cid.toLong)
    val buffer: ListBuffer[(Long, List[(String, Int)])] = ListBuffer[(Long, List[(String, Int)])]()
    //根据每个key来过滤RDD
    for (cid <- cids) {
      /*
      List((15,(632972a4-f811-4000-b920-dc12ea803a41,10)), (15,(f34878b8-1784-4d81-a4d1-0c93ce53e942,8)), (15,(5e3545a0-1521-4ad6-91fe-e792c20c46da,8)), (15,(66a421b0-839d-49ae-a386-5fa3ed75226f,8)), (15,(9fa653ec-5a22-4938-83c5-21521d083cd0,8)))
      目标:
      (9,List((199f8e1d-db1a-4174-b0c2-ef095aaef3ee,9), (329b966c-d61b-46ad-949a-7e37142d384a,8), (5e3545a0-1521-4ad6-91fe-e792c20c46da,8), (e306c00b-a6c5-44c2-9c77-15e919340324,7), (bed60a57-3f81-4616-9e8b-067445695a77,7)))
       */
      val arr: Array[(String, Int)] = cidAndSidCount.filter(cid == _._1)
        .sortBy(-_._2._2)
        .take(5)
        .map(_._2)
      buffer += ((cid, arr.toList))
    }
    buffer.foreach(println)

这样做也有缺点:即有多少个key,就有多少个Job,占用资源

方法二 利用TreeSet自动排序特性

def statCategoryTop10Session_3(sc: SparkContext,
                  categoryCountList: List[CategroyCount],
                  userVisitActionRDD: RDD[UserVisitAction]) = {
    // 1. 过滤出来 top10品类的所有点击记录
    // 1.1 先map出来top10的品类id
    val cids = categoryCountList.map(_.cid.toLong)
    val topCategoryActionRDD: RDD[UserVisitAction] = userVisitActionRDD.filter(action => cids.contains(action.click_category_id))


    // 2. 计算每个品类 下的每个session 的点击量 rdd ((cid, sid) ,1)
    val cidAndSidCount: RDD[(Long, (String, Int))] = topCategoryActionRDD
      .map(action => ((action.click_category_id, action.session_id), 1))
      // 使用自定义分区器 重点理解分区器的原理
      .reduceByKey(new CategoryPartitioner(cids), _ + _)
      .map {
        case ((cid, sid), count) => (cid, (sid, count))
      }
    
    // 3. 排序取top10
//因为已经按key分好了区,所以用Mappartitions ,在每个分区中新建一个TreeSet即可
    val result: RDD[(Long, List[SessionInfo])] = cidAndSidCount.mapPartitions((it: Iterator[(Long, (String, Int))]) => {
//new 一个TreeSet,并同时指定排序规则
   var treeSet: mutable.TreeSet[CategorySession] = new mutable.TreeSet[CategorySession]()(new Ordering[CategorySession] {
          override def compare(x: CategorySession, y: CategorySession): Int = {
            if (x.clickCount >= y.clickCount) -1 else 1
          }
        })
   var id = 0l
  iter.foreach({
    case (l, session) => {
      id = l
      treeSet.add(session)
    if (treeSet.size > 10) treeSet = treeSet.take(10)
          }
        })
        Iterator(id, treeSet)
      })
  
    result.collect.foreach(println)
    
    Thread.sleep(1000000)
  }
}

/*
根据传入的key值来决定分区号,让相同key进入相同的分区,能够避免多次shuffle
 */
class CategoryPartitioner(cids: List[Long]) extends Partitioner {
  // 用cid索引, 作为将来他的分区索引.
  private val cidWithIndex: Map[Long, Int] = cids.zipWithIndex.toMap
  
  // 返回集合的长度
  override def numPartitions: Int = cids.length
  
  // 根据key返回分区的索引
  override def getPartition(key: Any): Int = {
    key match {
      // 根据品类id返回分区的索引!  0-9
      case (cid: Long, _) =>
        cidWithIndex(cid)
    }
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
实例说明Python中比较运算符的使用
May 13 Python
python Django框架实现自定义表单提交
Mar 25 Python
Python随机数random模块使用指南
Sep 09 Python
Python3.6安装及引入Requests库的实现方法
Jan 24 Python
Python动态导入模块的方法实例分析
Jun 28 Python
python系列 文件操作的代码
Oct 06 Python
Python序列对象与String类型内置方法详解
Oct 22 Python
python实现差分隐私Laplace机制详解
Nov 25 Python
快速解决jupyter notebook启动需要密码的问题
Apr 21 Python
Python中的wordcloud库安装问题及解决方法
May 27 Python
python某漫画app逆向
Mar 31 Python
Python中super().__init__()测试以及理解
Dec 06 Python
Django 解决开发自定义抛出异常的问题
May 21 #Python
Python logging模块写入中文出现乱码
May 21 #Python
django的403/404/500错误自定义页面的配置方式
May 21 #Python
python 3.8.3 安装配置图文教程
May 21 #Python
Python中的xlrd模块使用原理解析
May 21 #Python
python中sklearn的pipeline模块实例详解
May 21 #Python
Python使用re模块验证危险字符
May 21 #Python
You might like
PHPEXCEL 使用小记
2013/01/06 PHP
PHP include任意文件或URL介绍
2014/04/29 PHP
php修改上传图片尺寸的方法
2015/04/14 PHP
PHP将URL转换成短网址的算法分享
2016/09/13 PHP
php基于dom实现读取图书xml格式数据的方法
2017/02/03 PHP
用php实现分页效果的示例代码
2020/12/10 PHP
js实现基于正则表达式的轻量提示插件
2015/08/29 Javascript
JavaScript中日期的相关操作方法总结
2015/10/24 Javascript
实例讲解jquery中mouseleave和mouseout的区别
2016/02/17 Javascript
AngularJS基础 ng-readonly 指令简单示例
2016/08/02 Javascript
jQuery下拉菜单的实现代码
2016/11/03 Javascript
Cookies 和 Session的详解及区别
2017/04/21 Javascript
解决vue.js在编写过程中出现空格不规范报错的问题
2017/09/20 Javascript
javascript基于牛顿迭代法实现求浮点数的平方根【递归原理】
2017/09/28 Javascript
如何快速解决JS或Jquery ajax异步跨域的问题
2018/01/08 jQuery
Nodejs中的JWT和Session的使用
2018/08/21 NodeJs
微信小程序左滑删除功能开发案例详解
2018/11/12 Javascript
细说Vue组件的服务器端渲染的过程
2019/05/30 Javascript
Python迭代用法实例教程
2014/09/08 Python
Python读取网页内容的方法
2015/07/30 Python
Eclipse中Python开发环境搭建简单教程
2016/03/23 Python
python批量制作雷达图的实现方法
2016/07/26 Python
python中的decorator的作用详解
2018/07/26 Python
解决每次打开pycharm直接进入项目的问题
2018/10/28 Python
Python多线程模块Threading用法示例小结
2019/11/09 Python
python的Jenkins接口调用方式
2020/05/12 Python
H5 canvas中width、height和style的宽高区别详解
2018/11/02 HTML / CSS
三星美国官网:Samsung美国
2017/02/06 全球购物
德国EGOIST网店:销售畅销的设计师品牌
2017/04/18 全球购物
网络教育毕业生自我鉴定
2013/10/10 职场文书
代理商会议邀请函
2014/01/27 职场文书
教导主任竞聘演讲稿
2014/05/16 职场文书
党支部对照检查材料
2014/08/25 职场文书
2015年度房地产工作总结
2015/04/09 职场文书
Oracle安装TNS_ADMIN环境变量设置参考
2021/11/01 Oracle
「魔法少女伊莉雅」美游粘土人开订
2022/03/21 日漫