SparkSQL编程指南之Java篇一-入门

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

你可能感兴趣的:(Spark)