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通过shutil实现快速文件复制的方法
Mar 14 Python
Pycharm 创建 Django admin 用户名和密码的实例
May 30 Python
Python实现查找字符串数组最长公共前缀示例
Mar 27 Python
Python何时应该使用Lambda函数
Jul 02 Python
Python画图高斯分布的示例
Jul 10 Python
tornado+celery的简单使用详解
Dec 21 Python
python通过matplotlib生成复合饼图
Feb 06 Python
解决tensorflow添加ptb库的问题
Feb 10 Python
python生成任意频率正弦波方式
Feb 25 Python
django创建超级用户时指定添加其它字段方式
May 14 Python
python各种excel写入方式的速度对比
Nov 10 Python
python实现文件分片上传的接口自动化
Nov 19 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仿ZOL分页类代码
2008/10/02 PHP
composer.lock文件的作用
2016/02/03 PHP
thinkPHP框架实现多表查询的方法
2018/06/14 PHP
PHP切割汉字的常用方法实例总结
2019/04/27 PHP
List Installed Hot Fixes
2007/06/12 Javascript
深入理解javascript动态插入技术
2013/11/12 Javascript
js 定时器setTimeout无法调用局部变量的解决办法
2013/11/28 Javascript
Nodejs极简入门教程(三):进程
2014/10/27 NodeJs
详细分析JavaScript变量类型
2015/07/08 Javascript
javascript实现网页子页面遍历回调的方法(涉及 window.frames、递归函数、函数上下文)
2015/07/27 Javascript
javascript中SetInterval与setTimeout的定时器用法
2015/08/24 Javascript
Flask中获取小程序Request数据的两种方法
2017/05/12 Javascript
Angular4.x通过路由守卫进行路由重定向实现根据条件跳转到相应的页面(推荐)
2018/05/10 Javascript
js嵌套的数组扁平化:将多维数组变成一维数组以及push()与concat()区别的讲解
2019/01/19 Javascript
如何使用Node.js爬取任意网页资源并输出PDF文件到本地
2019/06/17 Javascript
微信小程序用户授权、位置授权及获取微信绑定手机号
2019/07/18 Javascript
解决vue字符串换行问题(绝对管用)
2020/08/06 Javascript
vue-router重写push方法,解决相同路径跳转报错问题
2020/08/07 Javascript
Python中的字符串操作和编码Unicode详解
2017/01/18 Python
Python中的pathlib.Path为什么不继承str详解
2019/06/23 Python
使用pandas实现连续数据的离散化处理方式(分箱操作)
2019/11/22 Python
Python如何定义有默认参数的函数
2020/08/10 Python
Python基于Faker假数据构造库
2020/11/30 Python
不可轻视HTML5!App三年内将被html5顶替彻底消失
2015/11/18 HTML / CSS
美国购买体育赛事门票网站:TicketCity
2019/03/06 全球购物
全球异乡人的跨境社交电商平台:Kouhigh口嗨网
2020/07/24 全球购物
WEB控件及HTML服务端控件能否调用客户端方法?如果能,请解释如何调用?
2015/08/25 面试题
乡镇庆八一活动方案
2014/02/02 职场文书
物理研修随笔感言
2014/02/14 职场文书
《小小雨点》教学反思
2014/02/18 职场文书
机关节能减排实施方案
2014/03/17 职场文书
建设工地安全标语
2014/06/07 职场文书
校园文化艺术节宣传标语
2014/10/09 职场文书
休学证明范本
2015/06/19 职场文书
2019年健身俱乐部的创业计划书
2019/08/26 职场文书
tensorflow学习笔记之tfrecord文件的生成与读取
2021/03/31 Python