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魔术方法详解
Feb 14 Python
用Python实现一个简单的线程池
Apr 07 Python
在Python的Flask中使用WTForms表单框架的基础教程
Jun 07 Python
动态规划之矩阵连乘问题Python实现方法
Nov 27 Python
Python装饰器(decorator)定义与用法详解
Feb 09 Python
对Python中的@classmethod用法详解
Apr 21 Python
python3 遍历删除特定后缀名文件的方法
Apr 23 Python
Python实现数据可视化看如何监控你的爬虫状态【推荐】
Aug 10 Python
Python sql注入 过滤字符串的非法字符实例
Apr 03 Python
python 如何实现遗传算法
Sep 22 Python
python 30行代码实现蚂蚁森林自动偷能量
Feb 08 Python
python3美化表格数据输出结果的实现代码
Apr 14 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
一拳超人中怪人协会钦定! S级别最强四人!
2020/03/02 日漫
PHP中的Trait 特性及作用
2016/04/03 PHP
ext监听事件方法[初级篇]
2008/04/27 Javascript
javascript 动态table添加colspan\rowspan 参数的方法
2009/07/25 Javascript
jquery URL参数判断,确定菜单样式
2010/05/31 Javascript
javascript getElementsByClassName实现代码
2010/10/11 Javascript
原生Js与jquery的多组处理, 仅展开一个区块的折叠效果
2011/01/09 Javascript
jquery js 重置表单 reset()具体实现代码
2013/08/05 Javascript
热点新闻滚动特效的js代码
2013/08/17 Javascript
jquery插件开发之实现jquery手风琴功能分享
2014/03/10 Javascript
jquery表单验证插件(jquery.validate.js)的3种使用方式
2015/03/28 Javascript
JavaScript模拟实现键盘打字效果
2015/06/29 Javascript
基于jQuery实现音乐播放试听列表
2016/04/14 Javascript
从零学习node.js之模块规范(一)
2017/02/21 Javascript
vue2.0 axios前后端数据处理实例代码
2017/06/30 Javascript
vue中接口域名配置为全局变量的实现方法
2018/09/20 Javascript
Vue组件内部实现一个双向数据绑定的实例代码
2019/04/04 Javascript
vue在App.vue文件中监听路由变化刷新页面操作
2020/08/14 Javascript
JavaScript本地储存:localStorage、sessionStorage、cookie的使用
2020/10/13 Javascript
python通过ElementTree操作XML获取结点读取属性美化XML
2013/12/02 Python
python 编程之twisted详解及简单实例
2017/01/28 Python
Django实现组合搜索的方法示例
2018/01/23 Python
Ubuntu下使用python读取doc和docx文档的内容方法
2018/05/08 Python
获取python的list中含有重复值的index方法
2018/06/27 Python
详解django2中关于时间处理策略
2019/03/06 Python
简单了解Django项目应用创建过程
2020/07/06 Python
python在CMD界面读取excel所有数据的示例
2020/09/28 Python
详解前端HTML5几种存储方式的总结
2016/12/27 HTML / CSS
中粮集团旗下食品网上购物网站:我买网
2016/09/22 全球购物
澳大利亚体育和露营装备在线/实体零售商:Find Sports
2020/06/03 全球购物
写一个函数,要求输入一个字符串和一个字符长度,对该字符串进行分隔
2015/07/30 面试题
淘宝中秋节活动方案
2014/01/31 职场文书
中专生毕业个人鉴定
2014/02/26 职场文书
服务承诺口号
2014/05/22 职场文书
借钱欠条怎么写
2015/07/03 职场文书
网络营销实训总结
2015/08/03 职场文书