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

来源:互联网 发布:mac python版本 编辑:程序博客网 时间:2024/06/05 16:55
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配置:

[xml] view plain copy
  1. <dependency>  
  2.     <groupId>org.apache.spark</groupId>  
  3.     <artifactId>spark-sql_2.11</artifactId>  
  4.     <version>2.1.0</version>  
  5. </dependency>  

* Spark 2.1.0版本需要Java 7或以上,本文使用Java 1.8.0_72版本

2. SparkSession

创建一个基本的SparkSession,只要使用SparkSession.builder(),它是使用所有Spark SQL功能的入口点:

[java] view plain copy
  1. import org.apache.spark.sql.SparkSession;  
  2.   
  3. SparkSession spark = SparkSession  
  4.     .builder()  
  5.     .appName("Java Spark SQL basic example")  
  6.     .master("local[*]")  
  7.     .config("spark.some.config.option""some-value")  
  8.     .getOrCreate();  

* 必须调用master(String master)方法指定主节点URL,本例使用local[*],表示本机多线程,线程数与服务器核数相同,具体请参考以下链接:

http://spark.apache.org/docs/latest/submitting-applications.html#master-urls

* 如果没有指定主节点URL的话,运行时会遇到以下错误:

[plain] view plain copy
  1. 17/02/15 14:08:12 ERROR SparkContext: Error initializing SparkContext.  
  2. org.apache.spark.SparkException: A master URL must be set in your configuration  
  3.     at org.apache.spark.SparkContext.<init>(SparkContext.scala:379)  
  4.     at org.apache.spark.SparkContext$.getOrCreate(SparkContext.scala:2313)  
  5.     at org.apache.spark.sql.SparkSession$Builder$$anonfun$6.apply(SparkSession.scala:868)  
  6.     at org.apache.spark.sql.SparkSession$Builder$$anonfun$6.apply(SparkSession.scala:860)  
  7.     at scala.Option.getOrElse(Option.scala:121)  
  8.     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()方法显示内容:

[java] view plain copy
  1. import org.apache.spark.sql.Dataset;  
  2. import org.apache.spark.sql.Row;  
  3.   
  4. Dataset<Row> df = spark.read().json("examples/src/main/resources/people.json");  
  5.   
  6. // 显示DataFrame的内容  
  7. df.show();  
  8. // +----+-------+  
  9. // | age|   name|  
  10. // +----+-------+  
  11. // |null|Michael|  
  12. // |  30|   Andy|  
  13. // |  19| Justin|  
  14. // +----+-------+  

4. 非类型化数据集操作(DataFrame操作)


DataFrames提供了Scala、Java、Python和R语言对结构化数据的操作。以下是一些使用Datasets对结构化数据进行处理的基本例子:

[java] view plain copy
  1. // col("...") is preferable to df.col("...")  
  2. import static org.apache.spark.sql.functions.col;  
  3.   
  4. // 以树形格式打印schema  
  5. df.printSchema();  
  6. // root  
  7. // |-- age: long (nullable = true)  
  8. // |-- name: string (nullable = true)  
  9.   
  10. // 选择“name”列  
  11. df.select("name").show();  
  12. // +-------+  
  13. // |   name|  
  14. // +-------+  
  15. // |Michael|  
  16. // |   Andy|  
  17. // | Justin|  
  18. // +-------+  
  19.   
  20. // 选择所有数据, 但对“age”列执行+1操作  
  21. df.select(col("name"), col("age").plus(1)).show();  
  22. // +-------+---------+  
  23. // |   name|(age + 1)|  
  24. // +-------+---------+  
  25. // |Michael|     null|  
  26. // |   Andy|       31|  
  27. // | Justin|       20|  
  28. // +-------+---------+  
  29.   
  30. // 选择年龄“age”大于21的people  
  31. df.filter(col("age").gt(21)).show();  
  32. // +---+----+  
  33. // |age|name|  
  34. // +---+----+  
  35. // | 30|Andy|  
  36. // +---+----+  
  37.   
  38. // 根据年龄“age”分组并计数  
  39. df.groupBy("age").count().show();  
  40. // +----+-----+  
  41. // | age|count|  
  42. // +----+-----+  
  43. // |  19|    1|  
  44. // |null|    1|  
  45. // |  30|    1|  
  46. // +----+-----+  

5. 以编程方式运行SQL查询

SparkSession的sql方法能够以编程方式运行SQL查询并返回Dataset<Row>。

例如以下例子:

[java] view plain copy
  1. import org.apache.spark.sql.Dataset;  
  2. import org.apache.spark.sql.Row;  
  3.   
  4. // 注册DataFrame为一个SQL的临时视图  
  5. df.createOrReplaceTempView("people");  
  6.   
  7. Dataset<Row> sqlDF = spark.sql("SELECT * FROM people");  
  8. sqlDF.show();  
  9. // +----+-------+  
  10. // | age|   name|  
  11. // +----+-------+  
  12. // |null|Michael|  
  13. // |  30|   Andy|  
  14. // |  19| Justin|  
  15. // +----+-------+  

6. 全局临时视图

Spark SQL的临时视图是当前session有效的,也就是视图会与创建该视图的session终止而失效。如果需要一个跨session而且一直有效的直到Spark应用终止才失效的临时视图,可以使用全局临时视图。全局临时视图是与系统保留数据库global_temp绑定,所以使用的时候必须使用该名字去引用,例如,SELECT * FROM global_temp.view1。

[java] view plain copy
  1. // 注册DataFrame为一个全局的SQL临时视图  
  2. df.createGlobalTempView("people");  
  3.   
  4. // 全局临时视图与系统保留数据库global_temp绑定  
  5. spark.sql("SELECT * FROM global_temp.people").show();  
  6. // +----+-------+  
  7. // | age|   name|  
  8. // +----+-------+  
  9. // |null|Michael|  
  10. // |  30|   Andy|  
  11. // |  19| Justin|  
  12. // +----+-------+  
  13.   
  14. // 全局临时视图是跨session的  
  15. spark.newSession().sql("SELECT * FROM global_temp.people").show();  
  16. // +----+-------+  
  17. // | age|   name|  
  18. // +----+-------+  
  19. // |null|Michael|  
  20. // |  30|   Andy|  
  21. // |  19| Justin|  
  22. // +----+-------+  

7. 创建Datasets

Datasets类似于RDDs,然而,Datasets使用了一个专门的编码器Encoder来序列化对象而不是使用Java的序列化或Kryo。这些专门的编码器使用的格式允许Spark执行像过滤filtering、排序sorting和哈希hashing等操作而不需要把对象反序列化成字节。

[java] view plain copy
  1. import java.util.Arrays;  
  2. import java.util.Collections;  
  3. import java.io.Serializable;  
  4.   
  5. import org.apache.spark.api.java.function.MapFunction;  
  6. import org.apache.spark.sql.Dataset;  
  7. import org.apache.spark.sql.Row;  
  8. import org.apache.spark.sql.Encoder;  
  9. import org.apache.spark.sql.Encoders;  
  10.   
  11. public static class Person implements Serializable {  
  12.   private String name;  
  13.   private int age;  
  14.   
  15.   public String getName() {  
  16.     return name;  
  17.   }  
  18.   
  19.   public void setName(String name) {  
  20.     this.name = name;  
  21.   }  
  22.   
  23.   public int getAge() {  
  24.     return age;  
  25.   }  
  26.   
  27.   public void setAge(int age) {  
  28.     this.age = age;  
  29.   }  
  30. }  
  31.   
  32. // 创建一个Person对象  
  33. Person person = new Person();  
  34. person.setName("Andy");  
  35. person.setAge(32);  
  36.   
  37. // 创建Java beans的Encoders  
  38. Encoder<Person> personEncoder = Encoders.bean(Person.class);  
  39. Dataset<Person> javaBeanDS = spark.createDataset(  
  40.   Collections.singletonList(person),  
  41.   personEncoder  
  42. );  
  43. javaBeanDS.show();  
  44. // +---+----+  
  45. // |age|name|  
  46. // +---+----+  
  47. // | 32|Andy|  
  48. // +---+----+  
  49.   
  50. // Encoders类提供了常见类型的Encoders  
  51. Encoder<Integer> integerEncoder = Encoders.INT();  
  52. Dataset<Integer> primitiveDS = spark.createDataset(Arrays.asList(123), integerEncoder);  
  53. Dataset<Integer> transformedDS = primitiveDS.map(value -> value + 1, integerEncoder);  
  54. transformedDS.collect(); // 返回 [2, 3, 4]  
  55.   
  56. // 通过指定Encoder转换DataFrames为Dataset,基于名字匹配  
  57. String path = "examples/src/main/resources/people.json";  
  58. Dataset<Person> peopleDS = spark.read().json(path).as(personEncoder);  
  59. peopleDS.show();  
  60. // +----+-------+  
  61. // | age|   name|  
  62. // +----+-------+  
  63. // |null|Michael|  
  64. // |  30|   Andy|  
  65. // |  19| Justin|  
  66. // +----+-------+  

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。

[java] view plain copy
  1. import org.apache.spark.api.java.JavaRDD;  
  2. import org.apache.spark.api.java.function.Function;  
  3. import org.apache.spark.api.java.function.MapFunction;  
  4. import org.apache.spark.sql.Dataset;  
  5. import org.apache.spark.sql.Row;  
  6. import org.apache.spark.sql.Encoder;  
  7. import org.apache.spark.sql.Encoders;  
  8.   
  9. // 通过一个文本文件创建Person对象的RDD  
  10. JavaRDD<Person> peopleRDD = spark.read()  
  11.   .textFile("examples/src/main/resources/people.txt")  
  12.   .javaRDD()  
  13.   .map(line -> {  
  14.     String[] parts = line.split(",");  
  15.     Person person = new Person();  
  16.     person.setName(parts[0]);  
  17.     person.setAge(Integer.parseInt(parts[1].trim()));  
  18.     return person;  
  19.   });  
  20.   
  21. // 对JavaBeans的RDD指定schema得到DataFrame  
  22. Dataset<Row> peopleDF = spark.createDataFrame(peopleRDD, Person.class);  
  23. // 注册该DataFrame为临时视图  
  24. peopleDF.createOrReplaceTempView("people");  
  25.   
  26. // 执行SQL语句  
  27. Dataset<Row> teenagersDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19");  
  28.   
  29. // The columns of a row in the result can be accessed by field index  
  30. // 结果中的列可以通过属性的下标获取  
  31. Encoder<String> stringEncoder = Encoders.STRING();  
  32. Dataset<String> teenagerNamesByIndexDF = teenagersDF.map(  
  33.     row -> "Name: " + row.getString(0),  
  34.     stringEncoder);  
  35. teenagerNamesByIndexDF.show();  
  36. // +------------+  
  37. // |       value|  
  38. // +------------+  
  39. // |Name: Justin|  
  40. // +------------+  
  41.   
  42. // 或者通过属性的名字获取  
  43. Dataset<String> teenagerNamesByFieldDF = teenagersDF.map(  
  44.     row -> "Name: " + row.<String>getAs("name"),  
  45.     stringEncoder);  
  46. teenagerNamesByFieldDF.show();  
  47. // +------------+  
  48. // |       value|  
  49. // +------------+  
  50. // |Name: Justin|  
  51. // +------------+  

8.2 通过编程接口指定Schema

当JavaBean不能被事先定义的时候,通过编程创建Dataset<Row>需要三个步骤:

  • 通过原来的RDD创建一个Rows格式的RDD
  • 创建以StructType表现的schema,该StructType与步骤1创建的Rows结构RDD相匹配
  • 通过SparkSession的createDataFrame方法对Rows格式的RDD指定schema
例子:

[java] view plain copy
  1. import java.util.ArrayList;  
  2. import java.util.List;  
  3.   
  4. import org.apache.spark.api.java.JavaRDD;  
  5. import org.apache.spark.api.java.function.Function;  
  6.   
  7. import org.apache.spark.sql.Dataset;  
  8. import org.apache.spark.sql.Row;  
  9.   
  10. import org.apache.spark.sql.types.DataTypes;  
  11. import org.apache.spark.sql.types.StructField;  
  12. import org.apache.spark.sql.types.StructType;  
  13.   
  14. // 创建一个RDD  
  15. JavaRDD<String> peopleRDD = spark.sparkContext()  
  16.   .textFile("examples/src/main/resources/people.txt"1)  
  17.   .toJavaRDD();  
  18.   
  19. // 使用string定义schema  
  20. String schemaString = "name age";  
  21.   
  22. // 基于用字符串定义的schema生成StructType  
  23. List<StructField> fields = new ArrayList<>();  
  24. for (String fieldName : schemaString.split(" ")) {  
  25.   StructField field = DataTypes.createStructField(fieldName, DataTypes.StringType, true);  
  26.   fields.add(field);  
  27. }  
  28. StructType schema = DataTypes.createStructType(fields);  
  29.   
  30. // 把RDD (people)转换为Rows  
  31. JavaRDD<Row> rowRDD = peopleRDD.map(record -> {  
  32.   String[] attributes = record.split(",");  
  33.   return RowFactory.create(attributes[0], attributes[1].trim());  
  34. });  
  35.   
  36. // 对RDD应用schema  
  37. Dataset<Row> peopleDataFrame = spark.createDataFrame(rowRDD, schema);  
  38.   
  39. // 使用DataFrame创建临时视图  
  40. peopleDataFrame.createOrReplaceTempView("people");  
  41.   
  42. // 运行SQL查询  
  43. Dataset<Row> results = spark.sql("SELECT name FROM people");  
  44.   
  45. // SQL查询的结果是DataFrames类型,支持所有一般的RDD操作  
  46. // 结果的列可以通过属性的下标或者名字获取  
  47. Dataset<String> namesDS = results.map(row -> "Name: " + row.getString(0), Encoders.STRING());  
  48. namesDS.show();  
  49. // +-------------+  
  50. // |        value|  
  51. // +-------------+  
  52. // |Name: Michael|  
  53. // |   Name: Andy|  
  54. // | Name: Justin|  
  55. // +-------------+  

* 参考Spark SQL官方链接:http://spark.apache.org/docs/latest/sql-programming-guide.html#getting-started

TO BE CONTINUED...O(∩_∩)O
阅读全文
0 0
原创粉丝点击