- 最常见单词计数问题
val input = sc.textFile(file_path)
val wordsRDD = input.map(x => x.split(" "))
# method 1
val result = wordsRDD.map(x => (x, 1)).reduceByKey((x, y) => x + y)
# method 2
val result = wordsRDD.countByValue()
-
combineByKey的工作原理
当第一次在分区内遇到某个键,使用createCombiner()函数创建该键对应的累加器的初始值;当之后再在该区间内遇到此键,使用mergeValue()方法将累加器对应的当前值与新的值进行合并。 -
对每个键规约数据的等价做法
rdd.groupByKey().map(value => value.reduce(func)) # 需要为每个键创建存放值的列表,占用内存
等价于
rdd.reduceByKey(func) # 比前者更高效
-
连接操作
左连接(leftOuterJoin),右连接(rightOuterJoin); 前者保留源RDD的所有键去匹配第二个RDD中与之相同的键,而后者则保留第二个RDD的所有键去匹配源RDD中与之相同的键。若同一个RDD中有多个相同键,则匹配后一一列出所有符合条件匹配对(不会压缩到一条记录中,注意)。倘若找不到相同的键,则在对应处记录None。 -
数据分区
必要性:对数据集在节点间的分布进行控制,以最少的网络传输极大提升整体性能;
非充分性:若RDD只需一次扫描,则无需分区;需要分区的场景包括大量基于键的连接计算。
例:
对于 rddA.join(rddB)
操作,该操作会先将rddA和rddB中的值全部求出来,将哈希值相同的记录通过网络传到同一台机器上,再在各个机器上对键相同的元素进行连接操作,即历经「rddA的hash值计算+跨数据混洗+相同键返回」与「rddB的hash值计算+跨数据混洗+相同键返回」两大步骤,再比较低效。
但若先对rddA执行自定义分区,即
rddA = rddA.partitionByKey(new HashPartitioner(N)).persist()
其中N为分区个数,persist为持久化操作(必须有,否则后续每次用到rddA时都会重复对数据做分区操作,相当于把partitionByKey的作用抵消了,和之前未自定义分区效果一样)。从而join操作时不会对rddA再进行混洗,只对rddA做本地引用,对rddB做混洗,网络传输仅存在于混洗rddB上。