一、SPARK常见RDD操作方法
操作类型 | 函数名 | 作用 |
---|---|---|
转化操作 | map() | 参数是函数,函数应用于RDD每一个元素,返回值是新的RDD |
flatMap() | 参数是函数,函数应用于RDD每一个元素,将元素数据进行拆分,变成迭代器,返回值是新的RDD | |
filter() | 参数是函数,函数会过滤掉不符合条件的元素,返回值是新的RDD | |
distinct() | 没有参数,将RDD里的元素进行去重操作 | |
union(RDD) | 参数是RDD,生成包含两个RDD所有元素的新RDD | |
intersection(RDD) | 参数是RDD,求出两个RDD的共同元素 | |
subtract(RDD) | 参数是RDD,将原RDD里和参数RDD里相同的元素去掉 | |
cartesian() | 参数是RDD,求两个RDD的笛卡儿积 | |
行动操作 | collect() | 返回RDD所有元素 |
count() | RDD里元素个数 | |
countByValue() | 各元素在RDD中出现次数 | |
take(num) | 从 RDD 中返回 num 个元素 | |
top(num) | 从 RDD 中返回最前面的 num个元素 | |
takeOrdered(num)(ordering) | 从 RDD 中按照提供的顺序返回最前面的 num 个元素 | |
takeSample(withReplacement, num, [seed]) | 从 RDD 中返回任意一些元素 | |
reduce() | 并行整合所有RDD数据,例如求和操作 | |
fold(0)(func) | 和reduce功能一样,不过fold带有初始值 | |
aggregate(0)(seqOp,combop) | 和reduce功能一样,但是返回的RDD数据类型和原RDD不一样 | |
foreach(func) | 对RDD每个元素都是使用特定函数 |
二、Java中针对专门类型的函数接口
函数名 | 等价函数 | 用途 |
---|---|---|
DoubleFlatMapFunction | Function> | 用于 flatMapToDouble,以生成 DoubleRDD |
DoubleFunction | Function | 用于 mapToDouble,以生成DoubleRDD |
PairFlatMapFunction | Function>> | 用于 flatMapToPair,以生成 PairRDD |
PairFunction | Function> | 用 于 mapToPair, 以 生 成PairRDD |
生成一个 JavaDoubleRDD、计算 RDD 中每个元素的平方值的示例,这样就可以调用 DoubleRDD 独有的函数了,比如 mean() 和 variance()。
JavaDoubleRDD result = rdd.mapToDouble(
new DoubleFunction<Integer>() {
public double call(Integer x) {
return (double) x * x;
}
});
System.out.println(result.mean());
三、Spark 对数据持久化
有时我们希望能多次使用同一个 RDD。如果简单地对 RDD 调用行动操作, Spark 每次都会重算 RDD 以及它的所有依赖。这在迭代算法中消耗格外大,因为迭代算法常常会多次使用同一组数据。
为了避免多次计算同一个 RDD,可以让 Spark 对数据进行持久化。对这个 RDD 调用行动操作前就调用了 persist() 方法。出于不同的目的,我们可以为 RDD 选择不同的持久化级别
级别 | 使用的空间 | CPU时间 | 是否在内存中 | 是否在磁盘上 | 备注 |
---|---|---|---|---|---|
MEMORY_ONLY | 高 | 低 | 是 | 否 | |
MEMORY_ONLY_SER | 低 | 高 | 是 | 否 | |
MEMORY_AND_DISK | 高 | 中等 | 部分 | 部分 | 如果数据在内存中放不下,则溢写到磁盘上 |
MEMORY_AND_DISK_SER | 低 | 高 | 部分 | 部分 | 如果数据在内存中放不下,则溢写到磁盘上。在内存中存放序列化后的数据 |
DISK_ONLY | 低 | 高 | 否 | 是 |
RDD 还有一个方法叫作 unpersist(),调用该方法可以手动把持久化的 RDD 从缓存中移除
四、键值对 RDD
键值对 RDD 是 Spark 中许多操作所需要的常见数据类型。
在Java中,键值对使用的是Scala标准库里面的scala.Tuple2类,可以通过调用new Tuple2(a, b)创建,然后通过tuple._1()和tuple._2()方法访问它的属性。键值对RDD使用的是JavaPairRDD类,可以使用特定的map操作将JavaRDDs转换为JavaPairRDDs,例如mapToPair和flatMapToPair。JavaPairRDD拥有标准RDD和特殊键值对的方法,例如,在下面的代码中使用了reduceByKey对键值对操作,计算每行的文本出现的次数:
JavaRDD<String> lines = sc.textFile("data.txt");
JavaPairRDD<String, Integer> pairs = lines.mapToPair(s -> new Tuple2(s, 1));
JavaPairRDD<String, Integer> counts = pairs.reduceByKey((a, b) -> a + b);
函数名 | 作用 |
---|---|
reduceByKey(func) | 合并具有相同键的值 |
groupByKey() | 对具有相同键的值进行分组 |
combineByKey( createCombiner,mergeValue,mergeCombiners,partitioner) | 使用不同的返回类型合并具有相同键的值 |
mapValues(func) | 对 pair RDD 中的每个值应用一个函数而不改变键 |
flatMapValues(func) | 一个返回迭代器的函数, 然后对返回的每个元素都生成一个对应原键的键值对记录。 通常用于符号化 |
keys() | 返回一个仅包含键的 RDD |
values() | 返回一个仅包含值的 RDD |
sortByKey() | 返回一个根据键排序的 RDD |
subtractByKey | 删掉 RDD 中键与 other RDD 中的键相同的元素 |
join(RDD) | 对两个 RDD 进行内连接 |
rightOuterJoin(RDD) | 对两个 RDD 进行连接操作,确保第一个 RDD 的键必须存在(右外连接) |
leftOuterJoin(RDD) | 对两个 RDD 进行连接操作,确保第二个 RDD 的键必须存在(左外连接) |
cogroup(RDD) | 将两个 RDD 中拥有相同键的数据分组到一起 |
Java 没有自带的二元组类型,因此 Spark 的 Java API 让用户使用 scala.Tuple2 类来创建二元组。这个类很简单: Java 用户可以通过 new Tuple2(elem1, elem2) 来创建一个新的二元组,并且可以通过 ._1() 和 ._2() 方法访问其中的元素。
JavaRDD<String> input = sc.textFile("D:\\spark-2.3.1-bin-hadoop2.7\\README.md");
// 筛选掉长度超过20 个字符的行
Function<scala.Tuple2<String, String>, Boolean> longWordFilter =
new Function<scala.Tuple2<String, String>, Boolean>() {
public Boolean call(scala.Tuple2<String, String> keyValue) {
return (keyValue._2().length() < 20);
}
};
JavaPairRDD<String, String> resultPair = input.mapToPair(new PairFunction<String, String, String>() {
@Override
public scala.Tuple2<String, String> call(String s) throws Exception {
return new scala.Tuple2(s, 1);
}
}).filter(longWordFilter);
五、Spark支持的一些常见格式
格式名称 | 结构化 | 备注 |
---|---|---|
文本文件 | 否 | 普通的文本文件,每行一条记录 |
JSON | 半结构化 | 常见的基于文本的格式,半结构化;大多数库都要求每 |
CSV | 是 | 非常常见的基于文本的格式,通常在电子表格应用中使用 |
SequenceFiles | 是 | 一种用于键值对数据的常见 Hadoop 文件格式 |
Protocol buffers | 是 | 一种快速、节约空间的跨语言格式 |
对象文件 | 是 | 用来将 Spark 作业中的数据存储下来以让共享的代码读取。改变类的时候它会失效,因为它依赖于 Java 序列化 |
①读取一个文本文件
JavaRDD<String> input = sc.textFile("file:///home/holden/repos/spark/README.md")
JavaPairRDD<String, String> inputRDD = sc.wholeTextFiles("file:///home/holden/repos/spark/README.md");
①保存文本文件
result.saveAsTextFile(outputFile)
②读取JSON
class ParseJson implements FlatMapFunction<Iterator<String>, Person> {
public Iterable<Person> call(Iterator<String> lines) throws Exception {
ArrayList<Person> people = new ArrayList<Person>();
ObjectMapper mapper = new ObjectMapper();
while (lines.hasNext()) {
String line = lines.next();
try {
people.add(mapper.readValue(line, Person.class));
} catch (Exception e) {
// 跳过失败的数据
}
}
return people;
}
}
JavaRDD<String> input = sc.textFile("file.json");
JavaRDD<Person> result = input.mapPartitions(new ParseJson());
②保存JSON
class WriteJson implements FlatMapFunction<Iterator<Person>, String> {
public Iterable<String> call(Iterator<Person> people) throws Exception {
ArrayList<String> text = new ArrayList<String>();
ObjectMapper mapper = new ObjectMapper();
while (people.hasNext()) {
Person person = people.next();
text.add(mapper.writeValueAsString(person));
}
return text;
}
}
JavaRDD<Person> result = input.mapPartitions(new ParseJson()).filter(
new LikesPandas());
JavaRDD<String> formatted = result.mapPartitions(new WriteJson());
formatted.saveAsTextFile(outfile);
③读取CSV
使用 textFile() 读取 CSV
public static class ParseLine implements Function<String, String[]> {
public String[] call(String line) throws Exception {
CSVReader reader = new CSVReader(new StringReader(line));
return reader.readNext();
}
}
JavaRDD<String> csvFile1 = sc.textFile(inputFile);
JavaPairRDD<String[]> csvData = csvFile1.map(new ParseLine());
③完整读取 CSV
public static class ParseLine
implements FlatMapFunction<Tuple2<String, String>, String[]> {
public Iterable<String[]> call(Tuple2<String, String> file) throws Exception {
CSVReader reader = new CSVReader(new StringReader(file._2()));
return reader.readAll();
}
}
JavaPairRDD<String, String> csvData = sc.wholeTextFiles(inputFile);
JavaRDD<String[]> keyedRDD = csvData.flatMap(new ParseLine());
④读取 SequenceFile
public static class ConvertToNativeTypes implements
PairFunction<Tuple2<Text, IntWritable>, String, Integer> {
public Tuple2<String, Integer> call(Tuple2<Text, IntWritable> record) {
return new Tuple2(record._1.toString(), record._2.get());
}
}
JavaPairRDD<Text, IntWritable> input = sc.sequenceFile(fileName, Text.class,
IntWritable.class);
JavaPairRDD<String, Integer> result = input.mapToPair(
new ConvertToNativeTypes());
⑤操作对象文件
sc.objectFile()
saveAsObjectFile
六、SpringBoot+Spark
1、POM
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.11</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.3.1</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>14.0.1</version>
</dependency>
2、demo代码
package cn.pzh.spark.demo.A01SparkDemo;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
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.FlatMapFunction;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
public class WordsCountDemo {
public static void main(String[] args) {
// 1、创建一个Java版本的Spark Context
// setAppName:设置应用名字,此名字会在Spark web UI显示
// setMaster:设置主节点URL,本例使用“local”是指本机单线程
// local[K]:本机K线程
// local[*]:本机多线程,线程数与服务器核数相同
// spark://HOST:PORT:Spark集群地址和端口,默认端口为7077
// mesos://HOST:PORT:Mesos集群地址和端口,默认端口为5050
// yarn:YARN集群
SparkConf conf = new SparkConf().setAppName("WordCount").setMaster("local");
// 创建JavaSparkContext对象
JavaSparkContext sc = new JavaSparkContext(conf);
// 2、读取数据
// ①并行集合Parallelized Collections
List<Integer> data = Arrays.asList(1, 2, 3, 4, 5);
// 创建 RDD 最简单的方式就是把程序中一个已有的集合传给 SparkContext 的 parallelize()方法
JavaRDD<Integer> distData = sc.parallelize(data);
// 调用reduce方法计算集合里面所有元素的总和
int count= distData.reduce((a, b) -> a + b);
System.out.println("总和为:" + count);
// ②外部存储中读取数据来创建 RDD。读取我们的输入数据
JavaRDD<String> input = sc.textFile("D:\\spark-2.3.1-bin-hadoop2.7\\README.md");
// 3、操作数据
// ①转化操作filter、union、map、flatMap:RDD 的转化操作是返回新 RDD 的操作。
// filter()接收一个函数,并将 RDD 中满足该函数的元素放入新的 RDD 中返回
JavaRDD<String> inputRDD = input.filter(x -> x.contains("Spark"));
// map()接收一个函数,把这个函数用于 RDD 中的每个元素,将函数的返回结果作为结果存入RDD中对应元素的值
// 计算 RDD 中各值的平方
JavaRDD<Integer> result = distData.map(new Function<Integer, Integer>() {
public Integer call(Integer x) {
return x * x;
}
});
System.out.println(StringUtils.join(result.collect(), ","));
// flatMap切分为单词
JavaRDD<String> words = input.flatMap(new FlatMapFunction<String, String>() {
public Iterator<String> call(String x) {
return Arrays.asList(x.split(" ")).iterator();
}
});
// ②行动操作:会把最终求得的结果返回到驱动器程序, 或者写入外部存储系统中
System.out.println("Spark在inputRDD有 " + inputRDD.count() + " 行");
for (String line : inputRDD.take(10)) {
System.out.println(line);
}
// 转换为键值对并计数
JavaPairRDD<String, Integer> counts = words.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public scala.Tuple2<String, Integer> call(String s) throws Exception {
return new scala.Tuple2(s, 1);
}
}).reduceByKey(new Function2<Integer, Integer, Integer>() {
public Integer call(Integer x, Integer y) {
return x + y;
}
});
// 筛选掉长度超过20 个字符的行
Function<scala.Tuple2<String, String>, Boolean> longWordFilter =
new Function<scala.Tuple2<String, String>, Boolean>() {
public Boolean call(scala.Tuple2<String, String> keyValue) {
return (keyValue._2().length() < 20);
}
};
JavaPairRDD<String, String> resultPair = input.mapToPair(new PairFunction<String, String, String>() {
@Override
public scala.Tuple2<String, String> call(String s) throws Exception {
return new scala.Tuple2(s, 1);
}
}).filter(longWordFilter);
// 将统计出来的单词总数存入一个文本文件,引发求值
counts.saveAsTextFile("D:\\spark-2.3.1-bin-hadoop2.7\\README1.md");
sc.close();
}
}