零基础入门大数据之spark中的几种key-value操作

今天记录一下spark里面的一些key-value对的相关算子。

key-value对可以简单理解为是一种认为构造的数据结构方式,比如一个字符串"hello",单看"hello"的话,它是一个字符串类型,现在假设我想把它在一个文本中出现的次数n作为一个值和"hello"一起操作,那么可以构造一种键值对(key-value)的结构来表示,也就是(hello,n)这样的结构,那么可能会问为什么要这么构造,答案是操作方便,便于计算。这种结构上和map基本是一个意思。

那么spark里面对key-value对操作的相关算子有哪些呢?列举几个重要的:

  • reduceByKey
  • groupByKey
  • combineByKey
  • sortByKey
reduceByKey

首当其冲也是用的比较多的就是reduceByKey。reduce我们知道就是map-reduce的reduce,聚合操作,那么reduceByKey就是按照key进行聚合操作,这个时候就是key不变,对具有相同key的所有key-value对中的value进行操作。这么一看,reduceByKey的操作过程就是:

(1)按key进行分组,把所有具有相同key的数据对划分为不同的大组;
(2)对每个大组,对该组中的所有元素的value进行某个函数处理;
(3)reduceByKey的处理输出也是一个key-value对,注意只是一个key,而不是一系列相同的key。至于value是什么,value是一个函数输出值:function(value1, value2, value3,…)

reduceByKey的经典例子就是统计文本中不同单词的个数,这个例子我在前面用非spark的reduceByKey方法举例过,那里比较复杂,这里用reduceByKey就简单多了。简单点,假设我的test.txt文本如下:

a b c d s a s d f v s a c b w a d f v s a v s c d

命令行下的代码如下:

scala>     val data = "a b c d s a s d f v s a c b w a d f v s a v s c d"
scala>     val s0 = data.split(" ")
scala>     val s1 = sc.parallelize(s0)
scala>     val s2 = s1.map(x => (x,1))
scala>     val s3 = s2.reduceByKey((x,y) => x+y)
scala>     s3.collect.foreach(println)

(a,5)
(b,2)
(s,5)
(c,3)
(d,4)
(f,2)
(v,3)
(w,1)

解释一下,首先我们需要构造key-value对的数据,也就是s2,可以看到,就是简单将每一个元素当key,value都是1,表示数量1。然后就是reduceByKey操作了,注意的是reduceByKey因为是聚合操作,操作的是两个key-value对生成一个key-value对,然后再重复这个过程,直到对相同的key只生成一个value,所以可以看到,输入是两个,输出是x+y,其实x+y就是操作函数,表示对value进行相加,这样才能达到统计数量的目的,当然这个函数是灵活的,不止于此,比如我把相同key的所有value串起来,如下:

scala>     val data = "a b c d s a s d f v s a c b w a d f v s a v s c d"
scala>     val s0 = data.split(" ")
scala>     val s1 = sc.parallelize(s0)
scala>     val s2 = s1.map(x => (x,"1"))
scala>     val s3 = s2.reduceByKey((x,y) => x+":"+y)
scala>     s3.collect.foreach(println)

(a,1:1:1:1:1)
(b,1:1)
(s,1:1:1:1:1)
(c,1:1:1)
(d,1:1:1:1)
(f,1:1)
(v,1:1:1)
(w,1)

可以看到,reduceByKey里面对key执行操作的函数是非常灵活的,总结就是对相同key进行任意的可聚合的操作。

groupByKey

groupByKey其实和reduceByKey非常像,功能都是一样的,性能甚至没有reduceByKey好。看官网解释: groupByKey是对每个key进行合并操作,但只生成一个sequence,groupByKey本身不能自定义操作函数。所以该函数比较简单了,就是一个简单的按照key进行搬运的操作,本省都不能自定义处理函数,所以用起来也无需输入什么,直接.groupByKey()生成一个key相同的序列。

注意:当采用groupByKey时,由于它不接收函数,spark只能先将所有的键值对都移动,这样的后果是集群节点之间的开销很大,导致传输延时。

combineByKey

根据字面意思可以知道,combineByKey就是按照key进行联合起来。乍一看和reduceByKey有什么区别?其实combineByKey是更底层的联合方式,很多函数包括reduceByKey都是由combineByKey实现的。

找了一下官网对combineByKey的定义:

def combineByKey[C](
      createCombiner: V => C,
      mergeValue: (C, V) => C,
      mergeCombiners: (C, C) => C,
      partitioner: Partitioner,
      mapSideCombine: Boolean = true,
      serializer: Serializer = null)

关键点来了,combineByKey里面包括三个变换函数,不同情况下会出发不同的函数,这点需要好好理解。解释就是:

(1)createCombiner: V => C ,这个函数是初始化阶段,也就是数据输入的原始value格式,想初始化成某种形式都可以,比如给值加个东西,a => (a,1)。

(2)mergeValue: (C, V) => C,该函数把新的元素V合并到之前的元素C(createCombiner)上 (这个操作在每个分区内进行),也就是第一步的结果上。

(3)mergeCombiners: (C, C) => C,该函数把2个元素C合并 (这个操作在不同分区间进行)。

不理解不要紧,看个求平均数的例子:

scala> val initialScores = Array(("zhangsan", 88.0), ("zhangsan", 95.0), ("lisi", 91.0), ("lisi", 93.0), ("zhangsan", 95.0), ("lisi", 98.0))
scala> val d1 = sc.parallelize(initialScores)
scala> type MVType = (Int, Double) //定义一个元组类型(科目计数器,分数)
scala> val d2 = d1.combineByKey(
  score => (1, score),
  (c1: MVType, newScore) => (c1._1 + 1, c1._2 + newScore),
  (c1: MVType, c2: MVType) => (c1._1 + c2._1, c1._2 + c2._2)
).map { case (name, (num, socre)) => (name, socre / num) }.collect

scala> d2.foreach(println)

(lisi,94.0)
(zhangsan,92.66666666666667)

解释一下,输入的原始数据是(姓名,成绩)的key-value对,那么对这个数据进行combineByKey的一系列操作后,首先,这个函数会对key相同的进行分组。那么函数里面处理的就只是value了。如果是原始数据的value,可以看到,它会执行第一个操作,也就是score => (1, score),因为只有这个操作可以符合输入输出。那么原始数据处理过的第一步先变成了 (1, score),我们知道,聚合还没有完成,假设一个经过第一步的数据和原始数据相遇进行聚合了怎么办?这个时候可以看到,数据输入符合第二步的格式,从而会执行第二个操作,也将数目加1,分数相加。那么也可能碰到两个都进行第一步操作的两个数进行聚合,这个时候可以看到,符合步骤三的输入格式,执行步骤三。有人说,存不存在两个步骤二出来的数据进行聚合呢?当然存在,但是我们看一下,两个步骤2出来的数据格式是什么?很显然,就是步骤1出来的数据格式吧,所以两个步骤2的数据聚合也是进行步骤三的操作。最最后map一下求一下平均即可得到平均数。

总结一下可以看到,combineByKey更复杂,复杂的东西当然也就更灵活了。

sortByKey

最后再说一个重要的按key进行排序的函数。但凡对key-value对处理完又需要排序的,基本都会碰到这个函数。而且灵活运用这个函数可是实现很多功能。比如我以前就用到了将key-value对进行处理,然后调换key-value的顺序变成(value,key)组成新的key-value,然后排序,再调换位置处理,再排序,实现复杂的功能。

这里简化一下,假设我们像对reduceByKey里面那个例子进行输出字母统计进行降序排列输出,怎么做呢?

scala>    al data = "a b c d s a s d f v s a c b w a d f v s a v s c d"
    val s0 = data.split(" ")
    val s1 = sc.parallelize(s0)
    val s2 = s1.map(x => (x,1))
    val s3 = s2.reduceByKey((x,y) => x+y)
    s3.collect.foreach(println)
    val s4 = s3.map{case(key,value) => (value,key)}
    val s5 = s4.sortByKey(false).map{case(key,value) => (value,key)}.collect
    
scala> s5.foreach(println)
(a,5)
(s,5)
(d,4)
(c,3)
(v,3)
(b,2)
(f,2)
(w,1)

sortByKey函数不需要参数或者接受一个bool参数,指定是降序还是升序,默认true升序。这里我们给了false,可以看到输出变成了降序。这就是将(key,value)变成(value,key)进行排序再变成(key,value)的典型应用。


关注公号【AInewworld】,第一时间获取精彩内容
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值