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 相关文章推荐
在Django的URLconf中使用多个视图前缀的方法
Jul 18 Python
Python的Django REST框架中的序列化及请求和返回
Apr 11 Python
Python中正则表达式详解
May 17 Python
django初始化数据库的实例
May 27 Python
Python使用reportlab模块生成PDF格式的文档
Mar 11 Python
Django Form 实时从数据库中获取数据的操作方法
Jul 25 Python
浅谈Python类中的self到底是干啥的
Nov 11 Python
什么是Python中的顺序表
Jun 02 Python
详细分析Python可变对象和不可变对象
Jul 09 Python
Python plt 利用subplot 实现在一张画布同时画多张图
Feb 26 Python
Python3 使用pip安装git并获取Yahoo金融数据的操作
Apr 08 Python
Python趣味挑战之用pygame实现简单的金币旋转效果
May 31 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
浅谈电磁辐射对健康的影响
2021/03/01 无线电
用php随机生成福彩双色球号码的2种方法
2013/02/04 PHP
Zend Framework教程之请求对象的封装Zend_Controller_Request实例详解
2016/03/07 PHP
基于php实现的php代码加密解密类完整实例
2016/10/12 PHP
PHP实现支持CURL字符串证书传输的方法
2019/03/23 PHP
JS 页面内容搜索,类似于 Ctrl+F功能的实现代码
2007/08/13 Javascript
JavaScript对象模型-执行模型
2008/04/28 Javascript
ajax无刷新动态调用股票信息(改良版)
2008/11/01 Javascript
自己动手制作jquery插件之自动添加删除行的实现
2011/10/13 Javascript
通过jQuery源码学习javascript(二)
2012/12/27 Javascript
javascript 实现简单的table排序及table操作练习
2012/12/28 Javascript
JS实现回到页面顶部动画效果的简单实例
2016/05/24 Javascript
jQuery EasyUI基础教程之EasyUI常用组件(推荐)
2016/07/15 Javascript
JS中使用mailto实现将用户在网页中输入的内容传递到本地邮件客户端
2016/10/08 Javascript
js仿手机页面文件下拉刷新效果
2016/10/14 Javascript
JavaScript基础心法 数据类型
2018/03/05 Javascript
JavaScript中的回调函数实例讲解
2019/01/27 Javascript
微信小程序实现简易table表格
2020/06/19 Javascript
es6 filter() 数组过滤方法总结
2019/04/03 Javascript
React传值 组件传值 之间的关系详解
2019/08/26 Javascript
浅析JavaScript中的事件委托机制跟深浅拷贝
2021/01/20 Javascript
Python使用Beautiful Soup包编写爬虫时的一些关键点
2016/01/20 Python
django 多数据库配置教程
2018/05/30 Python
python中计算一个列表中连续相同的元素个数方法
2018/06/29 Python
python 读取文本文件的行数据,文件.splitlines()的方法
2018/07/12 Python
python模块导入的细节详解
2018/12/10 Python
python之当你发现QTimer不能用时的解决方法
2019/06/21 Python
基于Python获取城市近7天天气预报
2019/11/26 Python
纯css3实现照片墙效果
2014/12/26 HTML / CSS
css3给背景图片加颜色遮罩的方法
2019/11/05 HTML / CSS
三陽商会官方网站:Sanyo iStore
2019/05/15 全球购物
SOA面试题:如何在SOA中实现松耦合
2013/07/21 面试题
《藏戏》教学反思
2014/02/11 职场文书
学生会干部自我鉴定2014
2014/09/18 职场文书
离职报告范文
2014/11/04 职场文书
甲午风云观后感
2015/06/02 职场文书