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脚本来获取Google搜索结果的示例
May 04 Python
Python 由字符串函数名得到对应的函数(实例讲解)
Aug 10 Python
Python解决抛小球问题 求小球下落经历的距离之和示例
Feb 01 Python
python 对txt中每行内容进行批量替换的方法
Jul 11 Python
Python tkinter的grid布局及Text动态显示方法
Oct 11 Python
python实现推箱子游戏
Mar 25 Python
python3+selenium实现qq邮箱登陆并发送邮件功能
Jan 23 Python
Python字符串通过'+'和join函数拼接新字符串的性能测试比较
Mar 05 Python
Pyqt5实现英文学习词典
Jun 24 Python
Python迭代器模块itertools使用原理解析
Dec 11 Python
Sublime Text3最新激活注册码分享适用2020最新版 亲测可用
Nov 12 Python
Python探索生命起源 matplotlib细胞自动机动画演示
Apr 21 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
PHP4在WinXP下IIS和Apache2服务器上的安装实例
2006/10/09 PHP
实用函数2
2007/11/08 PHP
php array_intersect()函数使用代码
2009/01/14 PHP
PHP页面间参数传递的四种方法详解
2013/06/09 PHP
基于GD2图形库的PHP生成图片缩略图类代码分享
2015/02/08 PHP
Symfony2联合查询实现方法
2016/03/18 PHP
javascript 解析url的search方法
2010/02/09 Javascript
基于jQuery的message插件实现右下角弹出消息框
2011/01/11 Javascript
JS获取单击按钮单元格所在行的信息
2014/06/17 Javascript
函数式 JavaScript(一)简介
2014/07/07 Javascript
js精美的幻灯片画集特效代码分享
2015/08/29 Javascript
ES6的新特性概览
2016/03/10 Javascript
jQuery+正则+文本框只能输入数字的实现方法
2016/10/07 Javascript
NodeJS使用formidable实现文件上传
2016/10/27 NodeJs
详解在 Angular 项目中添加 clean-blog 模板
2017/07/04 Javascript
基于JavaScript实现贪吃蛇游戏
2020/03/16 Javascript
详解Vue中的自定义指令
2020/12/07 Vue.js
[02:36]DOTA2上海特锦赛 回忆电竞生涯的重要瞬间
2016/03/25 DOTA
用Python进行TCP网络编程的教程
2015/04/29 Python
Python基于有道实现英汉字典功能
2015/07/25 Python
python杀死一个线程的方法
2015/09/06 Python
Python初学时购物车程序练习实例(推荐)
2017/08/08 Python
基于python select.select模块通信的实例讲解
2017/09/21 Python
python3.x实现发送邮件功能
2018/05/22 Python
python实现杨氏矩阵查找
2019/03/02 Python
通过Turtle库在Python中绘制一个鼠年福鼠
2020/02/03 Python
Python爬虫防封ip的一些技巧
2020/08/06 Python
python 两种方法删除空文件夹
2020/09/29 Python
Can a struct inherit from another struct? (结构体能继承结构体吗)
2016/09/25 面试题
JavaScript实现前端网页版倒计时
2021/03/24 Javascript
大学奖学金获奖感言
2014/08/15 职场文书
学校班子个人对照检查材料思想汇报
2014/09/27 职场文书
2014年化验员工作总结
2014/11/18 职场文书
初婚未育证明样本
2015/06/18 职场文书
2016元旦晚会主持词
2015/07/01 职场文书
mybatis使用oracle进行添加数据的方法
2021/04/27 Oracle