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 数据库 (sqlite3)应用
Dec 07 Python
python实现的二叉树定义与遍历算法实例
Jun 30 Python
Python数据结构之单链表详解
Sep 12 Python
python的dataframe和matrix的互换方法
Apr 11 Python
Flask框架WTForm表单用法示例
Jul 20 Python
一百多行python代码实现抢票助手
Sep 25 Python
python查询文件夹下excel的sheet名代码实例
Apr 02 Python
pybind11在Windows下的使用教程
Jul 04 Python
Tensorflow 多线程与多进程数据加载实例
Feb 05 Python
python3连接MySQL8.0的两种方式
Feb 17 Python
Python tcp传输代码实例解析
Mar 18 Python
python中元组的用法整理
Jun 15 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中的一些数组排序方法分享
2012/07/20 PHP
WordPress中查询文章的循环Loop结构及用法分析
2015/12/17 PHP
WordPress中给文章添加自定义字段及后台编辑功能区域
2015/12/19 PHP
PHP配合fiddler抓包抓取微信指数小程序数据的实现方法分析
2020/01/02 PHP
jQuery对象[0]是什么含义?
2010/07/31 Javascript
网页源代码保护(禁止右键、复制、另存为、查看源文件)
2012/05/23 Javascript
JS控件的生命周期介绍
2012/10/22 Javascript
js判断设备是否为PC并调整图片大小
2014/02/12 Javascript
JavaScript原型及原型链终极详解
2016/01/04 Javascript
利用jQuery实现打字机字幕效果实例代码
2016/09/02 Javascript
AngularJS的依赖注入实例分析(使用module和injector)
2017/01/19 Javascript
js中的DOM模拟购物车功能
2017/03/22 Javascript
Easyui Datagrid自定义按钮列(最后面的操作列)
2017/07/13 Javascript
解决Vue2.0中使用less给元素添加背景图片出现的问题
2018/09/03 Javascript
nodejs搭建本地服务器并访问文件操作示例
2019/05/11 NodeJs
vue实现图片上传预览功能
2019/12/23 Javascript
vue单文件组件无法获取$refs的问题
2020/06/24 Javascript
解决await在forEach中不起作用的问题
2021/02/25 Javascript
Python中DJANGO简单测试实例
2015/05/11 Python
Python生成随机验证码的两种方法
2015/12/22 Python
Python 搭建Web站点之Web服务器网关接口
2016/11/06 Python
python版opencv摄像头人脸实时检测方法
2018/08/03 Python
Python大数据之网络爬虫的post请求、get请求区别实例分析
2019/11/16 Python
python 实现简单的FTP程序
2019/12/27 Python
python数据库编程 Mysql实现通讯录
2020/03/27 Python
python构造IP报文实例
2020/05/05 Python
使用Django的JsonResponse返回数据的实现
2021/01/15 Python
浅谈基于HTML5的在线视频播放方案
2016/02/18 HTML / CSS
Dr. Martens马汀博士德国官网:马丁靴鼻祖
2019/12/26 全球购物
村委会主任先进事迹
2014/01/15 职场文书
大学军训感言1000字
2014/02/25 职场文书
人事部专员岗位职责
2014/03/04 职场文书
公司开业庆典策划方案
2014/06/04 职场文书
学习教师敬业奉献模范事迹材料思想汇报
2014/09/19 职场文书
JS不要再到处使用绝对等于运算符了
2021/04/30 Javascript
【海涛解说】pis亲自推荐,其实你从来不会玩NW
2022/04/01 DOTA