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 uuid模块使用实例
Apr 08 Python
详解Python中的循环语句的用法
Apr 09 Python
利用Python读取文件的四种不同方法比对
May 18 Python
Python字符串拼接六种方法介绍
Dec 18 Python
Python使用matplotlib绘制正弦和余弦曲线的方法示例
Jan 06 Python
对Python random模块打乱数组顺序的实例讲解
Nov 08 Python
在django view中给form传入参数的例子
Jul 19 Python
浅谈Pycharm的项目文件名是红色的原因及解决方式
Jun 01 Python
keras读取训练好的模型参数并把参数赋值给其它模型详解
Jun 15 Python
Python unittest基本使用方法代码实例
Jun 29 Python
使用matplotlib的pyplot模块绘图的实现示例
Jul 12 Python
如何在scrapy中集成selenium爬取网页的方法
Nov 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
用缓存实现静态页面的测试
2006/12/06 PHP
Zend studio文件注释模板设置方法
2013/09/29 PHP
php操作mysql数据库的基本类代码
2014/02/25 PHP
使用ExtJS技术实现的拖动树结点
2010/08/05 Javascript
JavaScript去掉数组中的重复元素
2011/01/13 Javascript
jquery.bgiframe.js在IE9下提示INVALID_CHARACTER_ERR错误
2013/01/11 Javascript
通过JS来判断页面控件是否获取焦点
2014/01/03 Javascript
javascript中取前n天日期的两种方法分享
2014/01/26 Javascript
jquery插件jquery.dragscale.js实现拖拽改变元素大小的方法(附demo源码下载)
2016/02/25 Javascript
AngularJS入门(用ng-repeat指令实现循环输出
2016/05/05 Javascript
老生常谈jquery中detach()和remove()的区别
2017/03/02 Javascript
BootStrap 导航条实例代码
2017/05/18 Javascript
静态页面实现 include 引入公用代码的示例
2017/09/25 Javascript
JS常见DOM节点操作示例【创建 ,插入,删除,复制,查找】
2018/05/14 Javascript
PHP 502bad gateway原因及解决方案
2020/11/13 Javascript
如何在vue-cli中使用css-loader实现css module
2021/01/07 Vue.js
[52:36]VGJ.S vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
Python2.x版本中maketrans()方法的使用介绍
2015/05/19 Python
Python实现自定义顺序、排列写入数据到Excel的方法
2018/04/23 Python
[原创]Python入门教程3. 列表基本操作【定义、运算、常用函数】
2018/10/30 Python
Python3实现监控新型冠状病毒肺炎疫情的示例代码
2020/02/13 Python
python实现简单颜色识别程序
2020/02/19 Python
Python使用configparser库读取配置文件
2020/02/22 Python
会计出纳岗位职责
2013/12/25 职场文书
责任担保书范文
2014/05/21 职场文书
爱护公共设施标语
2014/06/24 职场文书
党员自我对照检查材料
2014/08/19 职场文书
纪念九一八事变演讲稿:牢记九一八,屈辱怎能忘
2014/09/14 职场文书
机关干部三严三实心得体会
2014/10/13 职场文书
群众路线四风对照检查材料
2014/11/04 职场文书
家长给老师的感谢信
2015/01/20 职场文书
三孔导游词
2015/02/05 职场文书
五四青年节活动总结
2015/02/10 职场文书
奖励申请报告范文
2015/05/15 职场文书
一般纳税人申请报告
2015/05/18 职场文书
小米11和iphone12哪个值得买?小米11对比iphone12评测
2021/04/21 数码科技