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 time模块用法实例详解
Sep 11 Python
Python创建系统目录的方法
Mar 11 Python
用python写的一个wordpress的采集程序
Feb 27 Python
python select.select模块通信全过程解析
Sep 20 Python
Python3.6连接Oracle数据库的方法详解
May 18 Python
python防止随意修改类属性的实现方法
Aug 21 Python
python使用opencv在Windows下调用摄像头实现解析
Nov 26 Python
python从内存地址上加载python对象过程详解
Jan 08 Python
python匿名函数lambda原理及实例解析
Feb 07 Python
Python调用接口合并Excel表代码实例
Mar 31 Python
浅析python函数式编程
Sep 26 Python
Python中的面向接口编程示例详解
Jan 17 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
ie6 动态缩略图不显示的原因
2009/06/21 PHP
PHP5各个版本的新功能和新特性总结
2014/03/16 PHP
微信营销平台系统?刮刮乐的开发
2014/06/10 PHP
twig模板常用语句实例小结
2016/02/04 PHP
php7下的filesize函数
2019/09/30 PHP
jquery 元素相对定位代码
2010/10/15 Javascript
JS+CSS实现淡入式焦点图片幻灯切换效果的方法
2015/02/26 Javascript
javascript中slice(),splice(),split(),substring(),substr()使用方法
2015/03/13 Javascript
PHP实现基于Redis的MessageQueue队列封装操作示例
2019/02/02 Javascript
Vue 利用指令实现禁止反复发送请求的两种方法
2019/09/15 Javascript
在Linux下使用Python的matplotlib绘制数据图的教程
2015/06/11 Python
Django中redis的使用方法(包括安装、配置、启动)
2018/02/21 Python
Python多重继承的方法解析执行顺序实例分析
2018/05/26 Python
django框架自定义用户表操作示例
2018/08/07 Python
Python实现多进程的四种方式
2019/02/22 Python
详解Django中CBV(Class Base Views)模型源码分析
2019/02/25 Python
python实现文件助手中查看微信撤回消息
2019/04/29 Python
在Python中过滤Windows文件名中的非法字符方法
2019/06/10 Python
Django对models里的objects的使用详解
2019/08/17 Python
用HTML5.0制作网页的教程
2010/05/30 HTML / CSS
基于HTML5 audio元素播放声音jQuery小插件
2011/05/11 HTML / CSS
html5配合css3实现带提示文字的输入框(摆脱js)
2013/03/08 HTML / CSS
俄罗斯的精英皮具:Wittchen
2018/01/29 全球购物
美国最大最全的亚洲购物网站:美国亚米网(Yamibuy)
2020/05/05 全球购物
铭万公司.net面试题笔试题
2014/07/20 面试题
教师档案管理制度
2014/01/23 职场文书
公司办公室岗位职责
2014/03/19 职场文书
爱心活动计划书
2014/04/26 职场文书
公司承诺书怎么写
2014/05/24 职场文书
美食节策划方案
2014/05/26 职场文书
篮球比赛拉拉队口号
2014/06/10 职场文书
应届大专生自荐书
2014/06/16 职场文书
国庆节标语大全
2014/10/08 职场文书
优秀党员申报材料
2014/12/18 职场文书
留学推荐信(中英文版)
2015/03/26 职场文书
办公室主任岗位职责范本
2015/03/31 职场文书