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中关于日期时间处理的问答集锦
Mar 08 Python
Python文件夹与文件的操作实现代码
Jul 13 Python
python调用机器喇叭发出蜂鸣声(Beep)的方法
Mar 23 Python
基于Linux系统中python matplotlib画图的中文显示问题的解决方法
Jun 15 Python
如何用Python合并lmdb文件
Jul 02 Python
Python实现的括号匹配判断功能示例
Aug 25 Python
Python 列表去重去除空字符的例子
Jul 20 Python
Django 后台带有字典的列表数据与页面js交互实例
Apr 03 Python
Python 实现敏感目录扫描的示例代码
May 21 Python
如何基于Python Matplotlib实现网格动画
Jul 20 Python
python 批量压缩图片的脚本
Jun 02 Python
python数据处理之Pandas类型转换
Apr 28 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
dedecms中使用php语句指南
2014/11/13 PHP
php使用pear_smtp发送邮件
2016/04/15 PHP
浅谈php调用python文件
2019/03/29 PHP
js跨域问题之跨域iframe自适应大小实现代码
2010/07/17 Javascript
JavaScript高级程序设计 读书笔记之十 本地对象Date日期
2012/02/27 Javascript
JS对HTML标签select的获取、添加、删除操作
2013/10/17 Javascript
jQuery手机浏览器中拖拽动作的艰难性分析
2015/02/04 Javascript
sso跨域写cookie的一段js脚本(推荐)
2016/05/25 Javascript
Node.js配合node-http-proxy解决本地开发ajax跨域问题
2016/08/31 Javascript
基于JS实现的随机数字抽签实例
2016/12/08 Javascript
JS中Attr的用法详解
2017/10/09 Javascript
Vue-Router2.X多种路由实现方式总结
2018/02/09 Javascript
详解Vue文档中几个易忽视部分的剖析
2018/03/24 Javascript
node实现的爬虫功能示例
2018/05/04 Javascript
JavaScript实现的DOM树遍历方法详解【二叉DOM树、多叉DOM树】
2018/05/07 Javascript
jQuery实现的页面详情展开收起功能示例
2018/06/11 jQuery
JavaScript变速动画函数封装添加任意多个属性
2019/04/03 Javascript
p5.js码绘“跳动的小正方形”的实现代码
2019/10/22 Javascript
关于vue组件事件属性穿透详解
2019/10/28 Javascript
python连接mysql实例分享
2016/10/09 Python
Face++ API实现手势识别系统设计
2018/11/21 Python
python 抓取知乎指定回答下视频的方法
2020/07/09 Python
详解Pandas 处理缺失值指令大全
2020/07/30 Python
通用的Django注册功能模块实现方法
2021/02/05 Python
波兰家具和室内装饰品购物网站:Vivre
2018/04/10 全球购物
Mankind美国/加拿大:英国领先的男士美容护发用品公司
2018/12/05 全球购物
活动邀请函范文
2014/01/19 职场文书
葛优非诚勿扰搞笑征婚台词
2014/03/17 职场文书
幼儿园的门卫岗位职责
2014/04/10 职场文书
锦旗标语大全
2014/06/23 职场文书
爱护公共设施倡议书
2014/08/29 职场文书
幼儿园安全教育月活动总结
2015/05/08 职场文书
2016年清明节网上祭英烈活动总结
2016/04/01 职场文书
Go语言并发编程 sync.Once
2021/10/16 Golang
Python实战实现爬取天气数据并完成可视化分析详解
2022/06/16 Python
Python使用pandas导入xlsx格式的excel文件内容操作代码
2022/12/24 Python