1 说明
本文档介绍Spark的二次排序解决方案。
本章知识
方法 | 返回类型/描述 |
---|---|
textFile | –> JavaRDD - JavaRDD<String> org.apache.spark.api.java.JavaSparkContext. textFile (String path)- JavaRDD<String> textFile (String path, int minPartitions) |
mapToPair | –> JavaPairRDD - static <K2,V2> JavaPairRDD<K2,V2> mapToPair (PairFunction<T,K2,V2> f) |
groupByKey | –>JavaPairRDD- - JavaPairRDD<K,Iterable<V>> org.apache.spark.api.java.JavaPairRDD.groupByKey() |
mapValues | 要复制一份values再操作 - <U> JavaPairRDD<K,U> mapValues(Function<V,U> f) 说明:Pass each value in the key-value pair RDD through a map function without changing the keys; this also retains the original RDD’s partitioning. |
Iterable 转 List | 可以使用第三方包Lists.newArrayList(e) com.google.common.collect.Lists.newArrayList (Iterable elements) |
take(n) 及 collect() | -static java.util.List<T> take(int num) - static java.util.List<T> collect() |
1.1 Chapter 01: Secondary Sorting With Spark
输入格式
每一条记录(行)格式如下
<key><,><time><,><value>
假设key
已经有序,对key
分组,对time
排序
输入数据
$ cat chap01-timeseries.txt
p,4,40
p,6,20
x,2,9
y,2,5
x,1,3
y,1,7
y,3,1
x,3,6
z,1,4
z,2,8
z,3,7
z,4,0
p,1,10
p,3,60
期望输出
(z,{1=>4, 2=>8, 3=>7, 4=>0})
(p,{1=>10, 3=>60, 4=>40, 6=>20})
(x,{1=>3, 2=>9, 3=>6})
(y,{1=>7, 2=>5, 3=>1})
环境
spark version 2.1.2-SNAPSHOT
Java 1.8.0_131
项目详细实现见如下过程,项目结构如下
1.1.1 新建maven工程
新建maven工程,坐标 <groupId>cn.whbing.spark</groupId>
<artifactId>dataalgorithms</artifactId>
并添加两个依赖,其中spark-core_2.11
,是必须的;guava
仅仅是为了将Iterable转化为List。
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>24.1-jre</version>
</dependency>
整个pom
文件如下
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.whbing.spark</groupId>
<artifactId>dataalgorithms</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>dataalgorithms</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>24.1-jre</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
1.1.2 编辑输入文件
输入文件每一行格式如下<key><,><time><,><value>
,其保存在chap01-timeseries.txt
中,内容如下。输入文件的位置可以保存在本地或HDFS
等。本测试在local
模式运行(集群类似),chap01-timeseries.txt
文件位置与src
同级。输入文件可以写死,运行时直接运行即可;也可以将输入文件作为参数输入,运行参数为chap01-timeseries.txt
。
chap01-timeseries.txt
p,4,40
p,6,20
x,2,9
y,2,5
x,1,3
y,1,7
y,3,1
x,3,6
z,1,4
z,2,8
z,3,7
z,4,0
p,1,10
p,3,60
1.1.3 二次排序
/ 1 / 说明
① textFile
将每一行读取成RDD可一参数或两参数;
② mapToPair(new PairFunction<T,K,V>)
对每一行RDD的处理。T
:原RDD类型,即String
;K
:转化后Key
类型;V
:转化后Value
类型;
注:new PairFunction
中返回Tuple2<K,V>
即可对应到PairRDD;
③ groupByKey
将相同key
的value
值组成Iterable
;
④ mapValues
排序上述Iterable
,属于java
中处理集合
范畴
/ 2 / 几个重要过程介绍
(1)处理输入文件并读取
将输入作为运行参数
//读取参数并验证
if(args.length<1){
System.err.println("Usage: SecondarySort <file>");
System.exit(1); //1表示非正常退出,0表示正常退出
}
//读取输入的参数,即输入文件
String inputPath = args[0];
System.out.println("args[0]:<file>="+args[0]);
创建sparkConf
及sparkContext
,并读取文件
SparkConf conf = new SparkConf().setAppName("SecondarySort by spark").setMaster("local");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile(inputPath);
运行时需要传入参数。eclipse
中Run Configurations
–>Arguments
填入timeseries.txt
(2)中间结果调试collect或take
想要打印中间过程的RDD
或PairRDD
,均使用collect
。(saveAsText保存为文本,后续介绍)
take(int num)
只打印前num个
spark2.1.2 API 如下
static java.util.List<T> take(int num)
static java.util.List<T> collect()
PairRDD<K,V>
collect
后变成List<Tuple2<K,V>>
如对分组后的PairRDD
调试
List<Tuple2<String,Iterable<Tuple2<Integer,Integer>>>> out2 = groups.collect();
for(Tuple2<String,Iterable<Tuple2<Integer,Integer>>> t:out2){
System.out.println(t._1);
Iterable<Tuple2<Integer,Integer>> list = t._2;
for(Tuple2<Integer,Integer> t2:list){
System.out.println(t2._1+","+t2._2);
}
System.out.println("----");
}
(3)对Iterable排序
RDD不可变,进行操作需要先复制一份。
不能直接将Iterable转ArrayList,需要转换。这里可以使用import com.google.common.collect.Lists;
JavaPairRDD<String,Iterable<Tuple2<Integer,Integer>>> sorted =
groups.mapValues(new Function<Iterable<Tuple2<Integer,Integer>>,
Iterable<Tuple2<Integer,Integer>>>() {
@Override
public Iterable<Tuple2<Integer, Integer>> call(Iterable<Tuple2<Integer, Integer>> v)
throws Exception {
//直接对v排序是错误的,因为RDD不可变,要复制一份
//Collections.sort(v, new TupleComparator());
//书中有错误,需要转化v成ArrayList,不能直接是iterable
//以下转化也有问题,引入google的包
//ArrayList<Tuple2<Integer, Integer>> listOut = new ArrayList<>((ArrayList)v);
ArrayList<Tuple2<Integer, Integer>> listOut =Lists.newArrayList(v);
Collections.sort(listOut, new TupleComparator());
return listOut; //listOut属于Iterable,可以返回
}
});
(4)定制比较器
可以使用单例模式
TupleComparator.java
package cn.whbing.spark.dataalgorithms.chap01.util;
import java.io.Serializable;
import java.util.Comparator;
import scala.Tuple2;
public class TupleComparator implements
Comparator<Tuple2<Integer,Integer>>,Serializable {
public static final TupleComparator INSTANCE = new TupleComparator();
@Override
public int compare(Tuple2<Integer, Integer> t1,
Tuple2<Integer, Integer> t2) {
if(t1._1 <t2._1){
return -1;
}else if(t1._1 >t2._1){
return 1;
}
return 0;
}
}
/ 3 / 完整代码
SecondarySort.java
package cn.whbing.spark.dataalgorithms.chap01;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.PairFunction;
import com.google.common.collect.Lists;
import cn.whbing.spark.dataalgorithms.chap01.util.TupleComparator;
import scala.Tuple2;
public class SecondarySort {
public static void main(String[] args) {
//读取参数并验证
if(args.length<1){
System.err.println("Usage: SecondarySort <file>");
System.exit(1); //1表示非正常退出,0表示正常退出
}
//读取输入的参数,即输入文件
String inputPath = args[0];
System.out.println("args[0]:<file>="+args[0]);
//创建sparkConf及sparkContext(集群模式下运行时配模式时sparkConf可以不要)
SparkConf conf = new SparkConf().setAppName("SecondarySort by spark").setMaster("local");
JavaSparkContext sc = new JavaSparkContext(conf);
//final JavaSparkContext sc = new JavaSparkContext();
//读取每一行即<name><,><time><,><value>
//JavaRDD<String> lines = sc.textFile("timeseries.txt");
JavaRDD<String> lines = sc.textFile(inputPath);
//将每一行的JavaRDD<String>包括<name><,><time><,><value>进行进一步的处理
//转化成键值对,键是name,值是Tuple2(time,value)
/*PairFunction<T,K,V>
T-->表示输入
K-->表示转化后的key
V-->转化后的value
*/
/*
public interface PairFunction<T, K, V> extends Serializable {
Tuple2<K, V> call(T t) throws Exception;
}
*/
JavaPairRDD<String, Tuple2<Integer,Integer>> pairs =
lines.mapToPair(new PairFunction<String, String, Tuple2<Integer,Integer>>() {
@Override
public Tuple2<String, Tuple2<Integer, Integer>> call(String s) throws Exception {
//对输入s进行转换
String[] tokens = s.split(",");//s即x,2,5 以逗号分割
System.out.println(tokens[0]+","+tokens[1]+","+tokens[2]);
Integer time = new Integer(tokens[1]);//parseInt,valueOf都可以
Integer value = new Integer(tokens[2]);
Tuple2<Integer,Integer> timevalue = new Tuple2<Integer,Integer>(time,value);
return new Tuple2<String,Tuple2<Integer, Integer>>(tokens[0],timevalue);
}
});
//验证上述中间RDD的结果
List<Tuple2<String, Tuple2<Integer,Integer>>> out1 =
pairs.take(5);//collect
System.out.println("==DEBUG1==");
System.out.println("JavaPairRDD collect as:");
for(Tuple2 t:out1){
System.out.println(t._1+","
+((Tuple2<Integer, Integer>)t._2)._1+","
+((Tuple2<Integer, Integer>)t._2)._2);
}
//对name进行分组groupByKey,分组之后相同key显示一个,value汇集在一起为Iterable
JavaPairRDD<String,Iterable<Tuple2<Integer,Integer>>> groups =
pairs.groupByKey();
//对groups进行验证,使用collect收集结果,存为List,里面PairRDD记为Tuple2型
System.out.println("==DEBUG2==");
System.out.println("groupByKey as:");
List<Tuple2<String,Iterable<Tuple2<Integer,Integer>>>> out2 =
groups.collect();
for(Tuple2<String,Iterable<Tuple2<Integer,Integer>>> t:out2){
System.out.println(t._1);
Iterable<Tuple2<Integer,Integer>> list = t._2;
for(Tuple2<Integer,Integer> t2:list){
System.out.println(t2._1+","+t2._2);
}
System.out.println("----");
}
//groups中的value是Iterable类型,每一条数据是Tuple2,需要对其排序
//比较器件util.TupleComparator
//new Function参数,前一个是输入,后一个是输出
JavaPairRDD<String,Iterable<Tuple2<Integer,Integer>>> sorted =
groups.mapValues(new Function<Iterable<Tuple2<Integer,Integer>>,
Iterable<Tuple2<Integer,Integer>>>() {
@Override
public Iterable<Tuple2<Integer, Integer>> call(Iterable<Tuple2<Integer, Integer>> v)
throws Exception {
//直接对v排序是错误的,因为RDD不可变,要复制一份
//Collections.sort(v, new TupleComparator());
//书中有错误,需要转化v成ArrayList,不能直接是iterable
//以下转化也有问题,引入google的包
//ArrayList<Tuple2<Integer, Integer>> listOut = new ArrayList<>((ArrayList)v);
ArrayList<Tuple2<Integer, Integer>> listOut =Lists.newArrayList(v);
Collections.sort(listOut,TupleComparator.INSTANCE);//new TupleComparator()
return listOut; //listOut属于Iterable,可以返回
}
});
//输出sorted
System.out.println("==DEBUG OUTPUT==");
List<Tuple2<String, Iterable<Tuple2<Integer, Integer>>>> out =
sorted.collect();
for(Tuple2<String, Iterable<Tuple2<Integer, Integer>>> t:out){
System.out.println(t._1);//key
for(Tuple2<Integer, Integer> t2:t._2){
System.out.println(t2._1+","+t2._2);
}
System.out.println("----");
}
}
}
1.1.4 运行结果
p,4,40
p,6,20
x,2,9
y,2,5
x,1,3
==DEBUG1==
JavaPairRDD collect as:
p,4,40
p,6,20
x,2,9
y,2,5
x,1,3
==DEBUG2==
groupByKey as:
p,4,40
p,6,20
x,2,9
y,2,5
x,1,3
y,1,7
y,3,1
x,3,6
z,1,4
z,2,8
z,3,7
z,4,0
p,1,10
p,3,60
z
1,4
2,8
3,7
4,0
----
p
4,40
6,20
1,10
3,60
----
x
2,9
1,3
3,6
----
y
2,5
1,7
3,1
----
==DEBUG OUTPUT==
z
1,4
2,8
3,7
4,0
----
p
1,10
3,60
4,40
6,20
----
x
1,3
2,9
3,6
----
y
1,7
2,5
3,1
----
1.1.5 小结
textFile
–> JavaRDDmapToPair
–> JavaPairRDDgroupByKey
–>JavaPairRDDmapValues
–>要复制一份values再操作- Iterable转List
take(n)
及collect()