Spark - 大规模数据去重

66 篇文章 2 订阅
29 篇文章 0 订阅

一.引言

场景 : 商品 product 每日总销售记录量级 亿 级别起,去重 product 量大概 万 级别。每个商品有一个 state 标识其状态,该状态共3个值,分别为 "A", "B","C"。

统计:

(1) 三个 state 下 product 的总量

(2) 对应 state 下 product 去重后的量

第一个统计很简单,构造 sc.longAccumulator 遍历统计数量即可,第二个统计每个 state 下有亿级别的 value ,去重时有严重的数据倾斜且数据去重规模很大,亿级别去重至万级别,不断优化代码最终达到快速大数据去重。

二. 尝试

1.GroupBy + ToSet

第一版按最基本的去重写法实现,构造 (state, product)  的 pairRDD,随后按 state groupBy 分组,将得到的全部 product toSet 达到去重的效果,最终获取 state + set.size 达到统计目的:

    val re = sc.textFile(input).map(line => {
      val info = line.split("\t")
      val state = info(0)
      val productId = info(1)
      // 全局计数
      countMap(state).add(1L)
      // 构建 state+product 的 PairRDD
      (state, productId)
    }).groupBy(_._1).map(info => {
      val state = info._1
      // toSet.size
      val productSet = info._2.map(kv => {
        val productId = kv._2
        productId
      }).toArray.toSet
      state + "_" + productSet.size
    }).collect()

该方法会在每个 State 下构造一个亿级别的 Product 的 iterator,toArray.toSet.size 方法不仅占用大量内存且执行效率低,执行时间 20 min + 未结束,kill 掉任务重新优化。

2.GroupBy + RandomIndex + ToSet

因为 state 只有 ABC 三种可能,所以最后全部压力分摊在 3 个节点上,构造 PairRDD 时可以给  state 加上随机索引,从而将任务分散,获得多个小的 Set 再将 Set 合并即可,相当于分治:

    val re = sc.textFile(input).map(line => {
      val info = line.split("\t")
      val state = info(0)
      val productId = info(1)
      // 全局计数
      countMap(state).add(1L)
      // 构建 state + randomIndex + product 的 PairRDD
      (state + "_" + random.nextInt(100) , productId)
    }).groupBy(_._1).map(info => {
      val state = info._1.split("_")(0)
      // 分治
      val productSet = info._2.map(kv => {
        val productId = kv._2
        productId
      }).toArray.toSet
      (state, productSet)
    }).groupBy(_._1).map(info => {
      val state = info._1
      val tmpSet = mutable.HashSet[String]()
      // 合并
      info._2.foreach(kv => {
        tmpSet ++= kv._2
      })
      state + ":" + tmpSet.size
    }).collect()

该方法会将原始数据分为 3 x 100 份,缩减了每个 key 要处理的 productId 的量,最后再去重随机索引再 groupBy 一次,汇总得到结果,执行时间 5 min,优化效果显著,继续优化。

3.Distinct + GroupBy (推荐👍 )

上一步方案通过 randomIndex 将数据量分治,减少的百分比和 random 的数值成正比,但是在数据量很大的情况下,分治的每个 key 对应的 value 量还是很大,所以简单的去重执行 5min +,这次将 groupBy 改为 distinct,先去重得到 万 级别数据量,再 GroupBy,此时的数据量本机也可轻松完成:

    val re = sc.textFile(input).map(line => {
      val info = line.split("\t")
      val state = info(0)
      val productId = info(1)
      sendMap(state).add(1L)
      state + "_" + mid
    }).distinct().map(info => {
      val state = info.split("_")(0)
      val productId = info.split("_")(1)
      (state, productId)
    }).groupBy(_._1).map(info => {
      val state = info._1
      val num = info._2.map(_._2).toSet.size
      state + ":" + num
    }).collect()

distinct 去重其实就是 reduceByKey ,reduceByKey 会先在自己的节点汇总再送到统一处理节点,所以思想也是分治,且速度优于 GroupByKey,因为 GroupByKey 是把所有数据拉到一个节点再开始处理,改为上述方法后,运行时间 1min 左右,达到优于预期。

    map(x => (x, null)).reduceByKey((x, y) => x, numPartitions).map(_._1)

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BIT_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值