1. Spark SQL的Java开发包
Spark SQL提供Java的开发包,当前最新版本是2.1.0版本:spark-sql_2.11-2.1.0.jar,可以从下面链接下载:
http://central.maven.org/maven2/org/apache/spark/spark-sql_2.11/2.1.0/spark-sql_2.11-2.1.0.jar
或者通过Maven配置:
org.apache.spark
spark-sql_2.11
2.1.0
* Spark 2.1.0版本需要Java 7或以上,本文使用Java 1.8.0_72版本
2. SparkSession
创建一个基本的SparkSession,只要使用SparkSession.builder(),它是使用所有Spark SQL功能的入口点:
import org.apache.spark.sql.SparkSession;
SparkSession spark = SparkSession
.builder()
.appName("Java Spark SQL basic example")
.master("local[*]")
.config("spark.some.config.option", "some-value")
.getOrCreate();
* 必须调用master(String master)方法指定主节点URL,本例使用local[*],表示本机多线程,线程数与服务器核数相同,具体请参考以下链接:
http://spark.apache.org/docs/latest/submitting-applications.html#master-urls
* 如果没有指定主节点URL的话,运行时会遇到以下错误:
17/02/15 14:08:12 ERROR SparkContext: Error initializing SparkContext.
org.apache.spark.SparkException: A master URL must be set in your configuration
at org.apache.spark.SparkContext.(SparkContext.scala:379)
at org.apache.spark.SparkContext$.getOrCreate(SparkContext.scala:2313)
at org.apache.spark.sql.SparkSession$Builder$$anonfun$6.apply(SparkSession.scala:868)
at org.apache.spark.sql.SparkSession$Builder$$anonfun$6.apply(SparkSession.scala:860)
at scala.Option.getOrElse(Option.scala:121)
at org.apache.spark.sql.SparkSession$Builder.getOrCreate(SparkSession.scala:860)
Spark 2.0版本开始,SparkSession内置支持Hive的特性,包括使用HiveQL进行写查询,访问Hive的UDFs和从Hive表读取数据,不需要部署一个Hive的环境。
3. 创建DataFrames
使用SparkSession可以通过一个RDD、Hive表或者Spark数据源创建DataFrames,例如以下代码通过读取一个JSON文件创建一个DataFrame,然后调用show()方法显示内容:
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
Dataset df = spark.read().json("examples/src/main/resources/people.json");
// 显示DataFrame的内容
df.show();
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
4. 非类型化数据集操作(DataFrame操作)
DataFrames提供了Scala、Java、Python和R语言对结构化数据的操作。以下是一些使用Datasets对结构化数据进行处理的基本例子:
// col("...") is preferable to df.col("...")
import static org.apache.spark.sql.functions.col;
// 以树形格式打印schema
df.printSchema();
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)
// 选择“name”列
df.select("name").show();
// +-------+
// | name|
// +-------+
// |Michael|
// | Andy|
// | Justin|
// +-------+
// 选择所有数据, 但对“age”列执行+1操作
df.select(col("name"), col("age").plus(1)).show();
// +-------+---------+
// | name|(age + 1)|
// +-------+---------+
// |Michael| null|
// | Andy| 31|
// | Justin| 20|
// +-------+---------+
// 选择年龄“age”大于21的people
df.filter(col("age").gt(21)).show();
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+
// 根据年龄“age”分组并计数
df.groupBy("age").count().show();
// +----+-----+
// | age|count|
// +----+-----+
// | 19| 1|
// |null| 1|
// | 30| 1|
// +----+-----+
5. 以编程方式运行SQL查询
SparkSession的sql方法能够以编程方式运行SQL查询并返回Dataset
。
例如以下例子:
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
// 注册DataFrame为一个SQL的临时视图
df.createOrReplaceTempView("people");
Dataset sqlDF = spark.sql("SELECT * FROM people");
sqlDF.show();
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
6. 全局临时视图
Spark SQL的临时视图是当前session有效的,也就是视图会与创建该视图的session终止而失效。如果需要一个跨session而且一直有效的直到Spark应用终止才失效的临时视图,可以使用全局临时视图。全局临时视图是与系统保留数据库global_temp绑定,所以使用的时候必须使用该名字去引用,例如,SELECT * FROM global_temp.view1。
// 注册DataFrame为一个全局的SQL临时视图
df.createGlobalTempView("people");
// 全局临时视图与系统保留数据库global_temp绑定
spark.sql("SELECT * FROM global_temp.people").show();
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
// 全局临时视图是跨session的
spark.newSession().sql("SELECT * FROM global_temp.people").show();
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
7. 创建Datasets
Datasets类似于RDDs,然而,Datasets使用了一个专门的编码器Encoder来序列化对象而不是使用Java的序列化或Kryo。这些专门的编码器使用的格式允许Spark执行像过滤filtering、排序sorting和哈希hashing等操作而不需要把对象反序列化成字节。
import java.util.Arrays;
import java.util.Collections;
import java.io.Serializable;
import org.apache.spark.api.java.function.MapFunction;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.Encoder;
import org.apache.spark.sql.Encoders;
public static class Person implements Serializable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 创建一个Person对象
Person person = new Person();
person.setName("Andy");
person.setAge(32);
// 创建Java beans的Encoders
Encoder personEncoder = Encoders.bean(Person.class);
Dataset javaBeanDS = spark.createDataset(
Collections.singletonList(person),
personEncoder
);
javaBeanDS.show();
// +---+----+
// |age|name|
// +---+----+
// | 32|Andy|
// +---+----+
// Encoders类提供了常见类型的Encoders
Encoder integerEncoder = Encoders.INT();
Dataset primitiveDS = spark.createDataset(Arrays.asList(1, 2, 3), integerEncoder);
Dataset transformedDS = primitiveDS.map(value -> value + 1, integerEncoder);
transformedDS.collect(); // 返回 [2, 3, 4]
// 通过指定Encoder转换DataFrames为Dataset,基于名字匹配
String path = "examples/src/main/resources/people.json";
Dataset peopleDS = spark.read().json(path).as(personEncoder);
peopleDS.show();
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
8. Datasets与RDDs的相互转换
Spark SQL支持2种不同的方法把RDDs转换为Datasets。第一种是使用反射获取RDD的Schema,当知道schema的时候,使用基于反射的方法会让代码更加简明而且效果也更好。第二种是通过编程接口指定schema,这种方法会使代码冗长,但是可以在运行时才知道数据列以及其类型的情况下事先构造Datasets。
8.1 使用反射获取Schema
Spark SQL支持将JavaBean的RDD自动转换成DataFrame。目前的Spark SQL版本不支持包含Map field(s)的JavaBeans,但嵌套的JavaBeans和List或者Array fields是支持的。可以通过创建一个实现Serializable接口和包含所有fields的getters和setters方法的类来创建一个JavaBean。
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.MapFunction;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.Encoder;
import org.apache.spark.sql.Encoders;
// 通过一个文本文件创建Person对象的RDD
JavaRDD peopleRDD = spark.read()
.textFile("examples/src/main/resources/people.txt")
.javaRDD()
.map(line -> {
String[] parts = line.split(",");
Person person = new Person();
person.setName(parts[0]);
person.setAge(Integer.parseInt(parts[1].trim()));
return person;
});
// 对JavaBeans的RDD指定schema得到DataFrame
Dataset peopleDF = spark.createDataFrame(peopleRDD, Person.class);
// 注册该DataFrame为临时视图
peopleDF.createOrReplaceTempView("people");
// 执行SQL语句
Dataset teenagersDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19");
// The columns of a row in the result can be accessed by field index
// 结果中的列可以通过属性的下标获取
Encoder stringEncoder = Encoders.STRING();
Dataset teenagerNamesByIndexDF = teenagersDF.map(
row -> "Name: " + row.getString(0),
stringEncoder);
teenagerNamesByIndexDF.show();
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
// 或者通过属性的名字获取
Dataset teenagerNamesByFieldDF = teenagersDF.map(
row -> "Name: " + row.getAs("name"),
stringEncoder);
teenagerNamesByFieldDF.show();
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
8.2 通过编程接口指定Schema
当JavaBean不能被事先定义的时候,通过编程创建Dataset需要三个步骤:
- 通过原来的RDD创建一个Rows格式的RDD
- 创建以StructType表现的schema,该StructType与步骤1创建的Rows结构RDD相匹配
- 通过SparkSession的createDataFrame方法对Rows格式的RDD指定schema
例子:
import java.util.ArrayList;
import java.util.List;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;
// 创建一个RDD
JavaRDD peopleRDD = spark.sparkContext()
.textFile("examples/src/main/resources/people.txt", 1)
.toJavaRDD();
// 使用string定义schema
String schemaString = "name age";
// 基于用字符串定义的schema生成StructType
List fields = new ArrayList<>();
for (String fieldName : schemaString.split(" ")) {
StructField field = DataTypes.createStructField(fieldName, DataTypes.StringType, true);
fields.add(field);
}
StructType schema = DataTypes.createStructType(fields);
// 把RDD (people)转换为Rows
JavaRDD rowRDD = peopleRDD.map(record -> {
String[] attributes = record.split(",");
return RowFactory.create(attributes[0], attributes[1].trim());
});
// 对RDD应用schema
Dataset peopleDataFrame = spark.createDataFrame(rowRDD, schema);
// 使用DataFrame创建临时视图
peopleDataFrame.createOrReplaceTempView("people");
// 运行SQL查询
Dataset results = spark.sql("SELECT name FROM people");
// SQL查询的结果是DataFrames类型,支持所有一般的RDD操作
// 结果的列可以通过属性的下标或者名字获取
Dataset namesDS = results.map(row -> "Name: " + row.getString(0), Encoders.STRING());
namesDS.show();
// +-------------+
// | value|
// +-------------+
// |Name: Michael|
// | Name: Andy|
// | Name: Justin|
// +-------------+
* 参考Spark SQL官方链接:http://spark.apache.org/docs/latest/sql-programming-guide.html#getting-started
TO BE CONTINUED...O(∩_∩)O