combineByKey是Spark中一个比较核心的高级函数, groupByKey,reduceByKey的底层都是使用combineByKey实现的,这是我要弄清楚它的原因
1.6.0版的函数名更新为combineByKeyWithClassTag
combineByKey会调用combineByKeyWithClassTag,源码
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)] = self.withScope {
combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners)(null)
}
所以弄清楚combineByKey就能弄懂后者了
2.首先我们来看一例子
我有三类书
数据结构 | 操作系统 | 算法 |
---|---|---|
大话数据结构 | 深入理解操作系统 | 算法导论 |
数据结构C++版本 | Linux操作系统 | 算法基础 |
数据结构Java版本 | Windows操作系统 | 算法心得 |
,每一本数都有一个价格,我们现在的任务是要把所有的书籍都卖出去,假设我们要为每一类书籍都买一个钱包来装这些书籍卖出去的价格
思路就是,当我们卖掉每一类书籍的第一本书的时候,将得到的钱去买一个钱包,然后其他同类书籍卖掉的钱直接装进钱包就好了,下面是代码实现
case class Book(val name: String, val price: Int) {
//卖书然后买一个十块钱的钱包来装钱
def sellBook_buyWallet: Wallet = Wallet(price - 10)
}
case class Wallet(var money: Int) {
//将不同的钱包的钱累加起来
def addMoney(that: Book): Wallet = Wallet(money + that.price)
}
object BookCombineByKey {
def main(args: Array[String]): Unit = {
val sparkconf = new SparkConf().setAppName("Book_Sell").setMaster("local")
val sc = new SparkContext(sparkconf)
val bookRDD = sc.makeRDD(Array(
("数据结构", Book("大话数据结构", 50)),
("数据结构", Book("数据结构C++版本", 80)),
("数据结构", Book("数据结构Java版本", 50)),
("算法", Book("算法导论", 50)),
("算法", Book("算法基础", 20)),
("算法", Book("算法心得", 50)),
("操作系统", Book("深入理解操作系统", 30)),
("操作系统", Book("Linux操作系统", 70)),
("操作系统", Book("Windows操作系统", 50))
))
println(bookRDD.partitions.size+"===================")
val aggresult = bookRDD.combineByKeyWithClassTag(
(book: Book) => book.sellBook_buyWallet,
(w: Wallet, b: Book) => w.addMoney(b),
(w1: Wallet, w2: Wallet) => {
println("================ call ===============")
Wallet(w1.money + w2.money)
}
).collectAsMap().foreach(println)
}
}
其余的代码相当简单我就不讲解了,主要讲解combineByKey的参数,
- 第一个参数是说
(book: Book) => book.sellBook_buyWallet
满足了我们的需求,同类第一本书籍卖掉的时候,将得到的钱去买一个钱包,然后将卖书得到的钱减掉买钱包的钱,然后装入钱包。 - 第二个参数
(w: Wallet, b: Book) => w.addMoney(b)
满足我们的第二个需求,同类其他书籍卖掉得到的钱直接装入本类对应的钱包
第三个参数我们姑且不看,先将程序跑起来
看看执行结果:
算法类对应的钱包的钱数怎么来的?:
第一本书50元,卖掉得到50块钱,然后再买一个钱包,花了十块钱,现在还有50-10=40元,装进钱包,算法类第二本书卖掉得到20块钱,装进钱包,第三本买到得到50装进钱包,现在这个钱包有多少钱?50-10+20+50
以此类推,操作系统的钱怎么来的?
30-10+70+50=140
数据结构的钱?
50-10+80+50=170
这是不是很像,reduceBykey?
3.接下来我们来讲解第三个参数
(w1: Wallet, w2: Wallet)
你可以看到我在这个函数,里打印了一句话,但是控制台并没有输出这句话?
println("================ call ===============")
日志过长,我就不粘贴进来了
这是为什么呢?原来只有当同类的事物在不同的分区的时候,这个函数才会被触发。这在我们这个例子上对应的是什么呢?
这个就是,不同的地区的都同时在卖这些书籍,每一个地区都有三个不同的钱包,当所有的书籍都卖完了之后,我们再将这些钱包依次合并为三个大的钱包。并入地区一的数据结构的钱包转了1000块,地区二的数据结构的钱包装了500块,最终数据结构这个大钱包装了1500块。
那我们怎么样才能在上面的例子中触发这个函数呢?
在上面的代码运行时候的控制台中,我打印出了这个RDD的分区数目,可以看到只有一个分区
现在对代码做一点点修改,我创建RDD的时候,指定分区的数量是2,这以为这这些Book对象会分布到不同的分区上
看一下控制台:
可以看到现在的第三个函数的结果也被调用了
对比一下结果,我们第一次得到的结果是:
(算法,Wallet(110))
(操作系统,Wallet(140))
(数据结构,Wallet(170))
修改代码之后得到的结果是:
(算法,Wallet(100))
(操作系统,Wallet(140))
(数据结构,Wallet(170))
这是为什么?因为分区将算法类的书籍都分到了两个分区,操作系统和数据结构的所有书籍都在同一个分区
类似这样,算法类在两个分区分别都买了一个钱包,所以最终算法类有
50-10+20-10+50=100
其余两类没有变化。