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编程中用close()方法关闭文件的教程
May 24 Python
Python用list或dict字段模式读取文件的方法
Jan 10 Python
Django学习笔记之Class-Based-View
Feb 15 Python
Python实现通讯录功能
Feb 22 Python
Python机器学习算法之k均值聚类(k-means)
Feb 23 Python
python字典快速保存于读取的方法
Mar 23 Python
详解Python中is和==的区别
Mar 21 Python
使用python实现mqtt的发布和订阅
May 05 Python
Django 开发调试工具 Django-debug-toolbar使用详解
Jul 23 Python
python+rsync精确同步指定格式文件
Aug 29 Python
解决python对齐错误的方法
Jul 16 Python
python openpyxl模块的使用详解
Feb 25 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 静态变量与自定义常量的使用方法
2010/01/26 PHP
基于curl数据采集之单页面并行采集函数get_htmls的使用
2013/04/28 PHP
在wamp集成环境下升级php版本(实现方法)
2013/07/01 PHP
PHP内核探索:变量存储与类型使用说明
2014/01/30 PHP
php使用socket post数据到其它web服务器的方法
2015/06/02 PHP
详解WordPress中简码格式标签编写的基本方法
2015/12/22 PHP
如何打开php的gd2库
2017/02/09 PHP
PHP实现的多维数组排序算法分析
2018/02/10 PHP
jquery.tmpl JQuery模板插件
2011/10/10 Javascript
JavaScript 学习笔记之一jQuery写法图片等比缩放以及预加载
2012/06/28 Javascript
js获取php变量的实现代码
2013/08/10 Javascript
jquery仿百度百科底部浮动导航特效
2015/08/08 Javascript
AngularJs  Understanding Angular Templates
2016/09/02 Javascript
angularjs中回车键触发某一事件的方法
2017/04/24 Javascript
js简易版购物车功能
2017/06/17 Javascript
jQuery除指定区域外点击任何地方隐藏DIV功能
2017/11/13 jQuery
vue小白入门教程
2018/04/02 Javascript
vue 实现在函数中触发路由跳转的示例
2018/09/01 Javascript
jQuery实现的中英文切换功能示例
2019/01/11 jQuery
使用element-ui table expand展开行实现手风琴效果
2019/03/15 Javascript
Python的Django框架中设置日期和字段可选的方法
2015/07/17 Python
浅谈用Python实现一个大数据搜索引擎
2017/11/28 Python
python如何发布自已pip项目的方法步骤
2018/10/09 Python
python实现两张图片拼接为一张图片并保存
2019/07/16 Python
python 使用opencv 把视频分割成图片示例
2019/12/12 Python
css3媒体查询中device-width和width的区别详解
2020/03/27 HTML / CSS
LocalStorage记住用户和密码功能
2017/07/24 HTML / CSS
世界第一曲奇连锁店:Mrs. Fields Cookies
2017/02/04 全球购物
澳大利亚最好的厨具店:Kitchen Warehouse
2018/03/13 全球购物
中国双语服务优势的在线购票及活动平台:247tickets
2018/10/26 全球购物
世界上最大的乐谱选择:Sheet Music Plus
2020/01/18 全球购物
给老师的道歉信
2014/01/11 职场文书
植树造林的宣传标语
2014/06/23 职场文书
使用react-virtualized实现图片动态高度长列表的问题
2021/05/28 Javascript
Unicode中的CJK(中日韩统一表意文字)字符小结
2021/12/06 HTML / CSS
Android开发实现极为简单的QQ登录页面
2022/04/24 Java/Android