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中的高级函数map/reduce使用实例
Apr 13 Python
python实现各进制转换的总结大全
Jun 18 Python
Flask框架响应、调度方法和蓝图操作实例分析
Jul 24 Python
浅谈Python 多进程默认不能共享全局变量的问题
Jan 11 Python
pycharm 更改创建文件默认路径的操作
Feb 15 Python
keras 简单 lstm实例(基于one-hot编码)
Jul 02 Python
pycharm如何使用anaconda中的各种包(操作步骤)
Jul 31 Python
Python连接Mysql进行增删改查的示例代码
Aug 03 Python
单身狗福利?Python爬取某婚恋网征婚数据
Jun 03 Python
Python实现查询剪贴板自动匹配信息的思路详解
Jul 09 Python
python异步的ASGI与Fast Api实现
Jul 16 Python
使用Python通过企业微信应用给企业成员发消息
Apr 18 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
解决文件名解压后乱码的问题 将文件名进行转码的代码
2012/01/10 PHP
php新浪微博登录接口用法实例
2014/12/23 PHP
php使用GD创建保持宽高比缩略图的方法
2015/04/17 PHP
PHP在线书签系统分享
2016/01/04 PHP
Zend Framework教程之Bootstrap类用法概述
2016/03/14 PHP
PHP设置Cookie的HTTPONLY属性方法
2017/02/09 PHP
关于实现代码语法标亮 dp.SyntaxHighlighter
2007/02/02 Javascript
JS关键字变色实现思路及代码
2013/02/21 Javascript
ZeroClipboard插件实现多浏览器复制功能(支持firefox、chrome、ie6)
2014/08/30 Javascript
JavaScript中的函数嵌套使用
2015/06/04 Javascript
jquery实现最简单的滑动菜单效果代码
2015/09/12 Javascript
jQuery倒计时代码(超简单)
2017/02/27 Javascript
Bootstrap标签页(Tab)插件使用方法
2017/03/21 Javascript
JavaScript登录记住密码操作(超简单代码)
2017/03/22 Javascript
Jquery+Ajax+xml实现中国地区选择三级联动菜单效果(推荐)
2017/06/09 jQuery
JavaScript原型继承_动力节点Java学院整理
2017/06/30 Javascript
jQuery实现的文字逐行向上间歇滚动效果示例
2017/09/06 jQuery
Bootstrap栅格系统的使用详解
2017/10/30 Javascript
vue filter 完美时间日期格式的代码
2019/08/14 Javascript
vue-mugen-scroll组件实现pc端滚动刷新
2019/08/16 Javascript
解决vue项目中出现Invalid Host header的问题
2020/11/17 Javascript
python基于xmlrpc实现二进制文件传输的方法
2015/06/02 Python
python中pygame针对游戏窗口的显示方法实例分析(附源码)
2015/11/11 Python
Anaconda入门使用总结
2018/04/05 Python
Python中的异常处理try/except/finally/raise用法分析
2019/02/28 Python
Python 读取用户指令和格式化打印实现解析
2019/09/02 Python
详解Python高阶函数
2020/08/15 Python
CSS3的resize属性使用初探
2015/09/27 HTML / CSS
Becextech新西兰:数码单反相机和手机在线商店
2018/04/27 全球购物
英国优质家居用品网上品牌:URBANARA
2018/06/01 全球购物
俄语翻译实习生的自我评价分享
2013/11/06 职场文书
创建省级文明单位实施方案
2014/02/27 职场文书
《爱如茉莉》教后反思
2014/04/12 职场文书
2014年客服工作总结范文
2014/11/13 职场文书
css3实现背景图片半透明内容不透明的方法示例
2021/04/13 HTML / CSS
tensorflow中的数据类型dtype用法说明
2021/05/26 Python