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中编写模版节点及注册标签的方法
Jul 20 Python
总结Python编程中函数的使用要点
Mar 20 Python
详解python发送各类邮件的主要方法
Dec 22 Python
Python 专题四 文件基础知识
Mar 20 Python
Python中使用支持向量机(SVM)算法
Dec 26 Python
python组合无重复三位数的实例
Nov 13 Python
Python异常处理例题整理
Jul 07 Python
Python 最强编辑器详细使用指南(PyCharm )
Sep 16 Python
Python银行系统实战源码
Oct 25 Python
在tensorflow以及keras安装目录查询操作(windows下)
Jun 19 Python
Python的scikit-image模块实例讲解
Dec 30 Python
Python3.8官网文档之类的基础语法阅读
Sep 04 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
基于PHP的cURL快速入门教程 (小偷采集程序)
2011/06/02 PHP
对php 判断http还是https,以及获得当前url的方法详解
2019/01/15 PHP
javascript实现的使用方向键控制光标在table单元格中切换
2010/11/17 Javascript
浅析LigerUi开发中谨慎载入common.css文件
2013/07/09 Javascript
Jquery实现自定义弹窗示例
2014/03/12 Javascript
图片放大镜jquery.jqzoom.js使用实例附放大镜图标
2014/06/19 Javascript
Javascript 创建类并动态添加属性及方法的简单实现
2016/10/20 Javascript
原生js更改css样式的两种方式
2017/03/15 Javascript
JS实现微信摇一摇原理解析
2017/07/22 Javascript
javaScript之split与join的区别(详解)
2017/11/08 Javascript
vue单页面打包文件大?首次加载慢?nginx带你飞,从7.5M到1.3M蜕变过程(推荐)
2018/01/16 Javascript
Vue-Router模式和钩子的用法
2018/02/28 Javascript
微信小程序swiper实现滑动放大缩小效果
2018/11/15 Javascript
JS使用JSON.parse(),JSON.stringify()实现对对象的深拷贝功能分析
2019/03/06 Javascript
VUE项目初建和常见问题总结
2019/09/12 Javascript
如何在Vue中抽离接口配置文件
2019/10/31 Javascript
用python实现的可以拷贝或剪切一个文件列表中的所有文件
2009/04/30 Python
Python中for循环详解
2014/01/17 Python
在Linux下调试Python代码的各种方法
2015/04/17 Python
Python实现的建造者模式示例
2018/08/06 Python
python GUI实现小球满屏乱跑效果
2019/05/09 Python
python列表,字典,元组简单用法示例
2019/07/11 Python
django之状态保持-使用redis存储session的例子
2019/07/28 Python
python list转置和前后反转的例子
2019/08/26 Python
在python中创建指定大小的多维数组方式
2019/11/28 Python
利用HTML5实现使用按钮控制背景音乐开关
2015/09/21 HTML / CSS
HTML5超炫酷粒子效果的进度条的实现示例
2019/08/23 HTML / CSS
可打印的优惠券、杂货和优惠券代码:Coupons.com
2018/06/12 全球购物
乌克兰机票、铁路和巴士票、酒店搜索、保险:Tickets.ua
2020/01/11 全球购物
构造器Constructor是否可被override?
2013/08/06 面试题
电子商务专业自我鉴定
2013/12/18 职场文书
初中班主任经验交流材料
2014/05/16 职场文书
书法大赛策划方案
2014/06/04 职场文书
运动会宣传语
2015/07/13 职场文书
小学英语课教学反思
2016/02/15 职场文书
三好学生竞选稿范文
2019/08/21 职场文书