【Spark】SparkSQL编程实践

1 知识准备

1.1 Shark

Spark SQL是Spark生态系统中非常重要的组件,其前身为Shark。

Shark的设计导致了两个问题:

  • 一是执行计划优化完全依赖于Hive,不方便添加新的优化策略;
  • 二是因为Spark是线程级并行,而MapReduce是进程级并行。

因此,Spark在兼容Hive的实现上存在线程安全问题,导致Shark不得不使用另外一套独立维护的打了补丁的Hive源码分支。Shark的实现继承了大量的Hive代码,因而给优化和维护带来了大量的麻烦,特别是基于MapReduce设计的部分,成为整个项目的瓶颈。因此,在2014年的时候,Shark项目中止,并转向Spark SQL的开发。

1.2 Spark SQL设计

1、Spark-SQL架构
在这里插入图片描述
2、Spark-SQL支持的数据格式和编程语言
在这里插入图片描述

1.3 DataFrame与RDD的区别

在这里插入图片描述

2 DataFrame的创建

1、从Spark2.0以上版本开始,Spark使用全新的SparkSession接口替代Spark1.6中的SQLContext及HiveContext接口来实现其对数据加载、转换、处理等功能。SparkSession实现了SQLContext及HiveContext所有功能。

2、SparkSession支持从不同的数据源加载数据,并把数据转换成DataFrame,并且支持把DataFrame转换成SQLContext自身中的表,然后使用SQL语句来操作数据。SparkSession亦提供了HiveQL以及其他依赖于Hive的功能的支持。

2.1 从JSON文件中读取数据生成DataFrame

1、文件

{"name":"Michael"}
{"name":"Andy", "age":30}
{"name":"Justin", "age":19}

2、操作

scala> import org.apache.spark.sql.SparkSession

scala> val spark=SparkSession.builder().getOrCreate()

//使支持RDDs转换为DataFrames及后续sql操作
scala> import spark.implicits._

scala> val df = spark.read.json("file:///usr/local/spark/examples/src/main/resources/people.json")

scala> df.show()
+----+-------+
| age|   name|
+----+-------+
|null|Michael|
|  30|   Andy|
|  19| Justin|
+----+-------+

2、常用DataFrame操作

// 打印模式信息
scala> df.printSchema()
root
 |-- age: long (nullable = true)
 |-- name: string (nullable = true)
 
// 选择多列
scala> df.select(df("name"),df("age")+1).show()
+-------+---------+
|   name|(age + 1)|
+-------+---------+
|Michael|     null|
|   Andy|       31|
| Justin|       20|
+-------+---------+
 
// 条件过滤
scala> df.filter(df("age") > 20 ).show()
+---+----+
|age|name|
+---+----+
| 30|Andy|
+---+----+
 
// 分组聚合
scala> df.groupBy("age").count().show()
+----+-----+
| age|count|
+----+-----+
|  19|    1|
|null|    1|
|  30|    1|
+----+-----+
 
// 排序
scala> df.sort(df("age").desc).show()
+----+-------+
| age|   name|
+----+-------+
|  30|   Andy|
|  19| Justin|
|null|Michael|
+----+-------+
 
//多列排序
scala> df.sort(df("age").desc, df("name").asc).show()
+----+-------+
| age|   name|
+----+-------+
|  30|   Andy|
|  19|   Justin|
| null| Michael|
+----+-------+
 
//对列进行重命名
scala> df.select(df("name").as("username"),df("age")).show()
+--------+----+
|username| age|
+--------+----+
| Michael|null|
|  Andy|  30|
|  Justin|  19|
+--------+----+

2.1 从RDD转换创建DataFrame(txt文件)

方法:

  • 第一种方法是利用反射来推断包含特定类型对象的RDD的schema,适用对已知数据结构的RDD转换;
  • 第二种方法是使用编程接口,构造一个schema并将其应用在已知的RDD上。

2.1.1 反射机制

在利用反射机制推断RDD模式时,需要首先定义一个case class,因为只有case class才能被Spark隐式地转换为DataFrame。

1、导包

import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder
import org.apache.spark.sql.Encoder
import org.apache.spark.sql.Encoder
import spark.implicits._  //导入包支持把一个RDD隐式转换为一个DataFrame

2、创建Person类

case class Person(name: String, age: Long)  //定义一个case class

val peopleDF = spark.sparkContext.textFile("file:///usr/local/spark/examples/src/main/resources/people.txt").
|map(_.split(",")).
|map(attributes => Person(attributes(0), attributes(1).trim.toInt)).
|toDF()
 
//必须注册为临时表才能供下面的sql查询使用 
peopleDF.createOrReplaceTempView("people")  

//sql语句查询,最终生成一个DataFrame 
val personsDF = spark.sql("select name,age from people where age > 20")

//DataFrame中的每个元素都是一行记录,包含name和age两个字段,分别用t(0)和t(1)来获取值
personsDF.map(t => "Name:"+t(0)+","+"Age:"+t(1)).show()  
+------------------+
|             value|
+------------------+
|Name:Michael,Age:29|
|   Name:Andy,Age:30|
+------------------+

2.1.2 使用编程方式定义RDD模式

当无法提前定义case class时,就需要采用编程方式定义RDD模式

import org.apache.spark.sql.types._
import org.apache.spark.sql.Row

//生成 RDD
val peopleRDD = spark.sparkContext.textFile("file:///usr/local/spark/examples/src/main/resources/people.txt")

//制作“表头”
val schemaString = "name age"

val fields = schemaString.split(" ").map(fieldName => StructField(fieldName, StringType, nullable = true))

val schema = StructType(fields)

//制作“记录” 
val rowRDD = peopleRDD.map(_.split(",")).map(attributes => Row(attributes(0), attributes(1).trim))

//拼装记录和表头 
val peopleDF = spark.createDataFrame(rowRDD, schema)

//必须注册为临时表才能供下面查询使用
peopleDF.createOrReplaceTempView("people")

val results = spark.sql("SELECT name,age FROM people")
  
results.map(attributes => "name: " + attributes(0)+","+"age:"+attributes(1)).show()
+--------------------+
|               value|
+--------------------+
|name: Michael,age:29|
|   name: Andy,age:30|
| name: Justin,age:19|
+--------------------+

3 DataFrame数据保存

3.1 第一种保存方法

val peopleDF = spark.read.format("json").load("file:///usr/local/spark/examples/src/main/resources/people.json")

//select(“name”, “age”)确定要把哪些列进行保存,调用write.format(“csv”).save ()保存成csv文件 
peopleDF.select("name", "age").write.format("csv").save("file:///usr/local/spark/mycode/newpeople.csv")

另外,write.format()支持输出 json,parquet, jdbc, orc, libsvm, csv, text等格式文件,如果要输出文本文件,可以采用write.format(“text”),但是,需要注意,只有select()中只存在一个列时,才允许保存成文本文件,如果存在两个列,比如select(“name”, “age”),就不能保存成文本文件。

3.2 第二种保存方法

val peopleDF = spark.read.format("json").load("file:///usr/local/spark/examples/src/main/resources/people.json")

peopleDF.rdd.saveAsTextFile("file:///usr/local/spark/mycode/newpeople.txt")

把DataFrame转换成RDD,然后调用saveAsTextFile()保存成文本文件

4 Parquet文件与JDBC数据库读写

4.1 Parquet文件读写操作

scala> import spark.implicits._

scala> val parquetFileDF = spark.read.parquet("file:///usr/local/spark/examples/src/main/resources/users.parquet")
 
scala> parquetFileDF.createOrReplaceTempView("parquetFile")

scala> val namesDF = spark.sql("SELECT * FROM parquetFile")
 
scala> namesDF.foreach(attributes =>println("Name: " + attributes(0)+"  favorite color:"+attributes(1)))
import spark.implicits._

val peopleDF = spark.read.json("file:///usr/local/spark/examples/src/main/resources/people.json")
 
peopleDF.write.parquet("file:///usr/local/spark/mycode/newpeople.parquet")

4.2 JDBC数据库读写操作

4.2.1 准备

1、首先,请进入Linux系统(本教程统一使用hadoop用户名登录),打开火狐(FireFox)浏览器,下载一个MySQL的JDBC驱动(下载)。在火狐浏览器中下载时,一般默认保存在hadoop用户的当前工作目录的“Downloads”目录下,所以,可以打开一个终端界面,输入下面命令查看:

cd ~/Downloads

2、就可以看到刚才下载到的MySQL的JDBC驱动程序,文件名称为mysql-connector-java-5.1.49.tar.gz(你下载的版本可能和这个不同)。现在,使用下面命令,把该驱动程序拷贝到spark的安装目录下:

sudo tar -zxf ~/Downloads/mysql-connector-java-5.1.49.tar.gz -C /usr/local/spark/jars
cd /usr/local/spark/jars
ls

3、这时就可以在/usr/local/spark/jars目录下看到这个驱动程序文件所在的文件夹mysql-connector-java-5.1.40,进入这个文件夹,就可以看到驱动程序文件mysql-connector-java-5.1.40-bin.jar。请输入下面命令启动已经安装在Linux系统中的mysql数据库(如果前面已经启动了MySQL数据库,这里就不用重复启动了)

service mysql start

4、下面,我们要启动一个spark-shell,而且启动的时候,要附加一些参数。启动Spark Shell时,必须指定mysql连接驱动jar包

cd /usr/local/spark
./bin/spark-shell \
--jars /usr/local/spark/jars/mysql-connector-java-5.1.49/mysql-connector-java-5.1.49-bin.jar \
--driver-class-path /usr/local/spark/jars/mysql-connector-java-5.1.49/mysql-connector-java-5.1.49-bin.jar

上面的命令行中,在一行的末尾加入斜杠\,是为了告诉spark-shell,命令还没有结束。启动进入spark-shell以后,可以执行以下命令连接数据库,读取数据,并显示:

scala> val jdbcDF = spark.read.format("jdbc").option("url", "jdbc:mysql://mnode1:3306/spark").option("driver","com.mysql.jdbc.Driver").option("dbtable", "student").option("user", "root").option("password", "Hive@2020").load()

scala> jdbcDF.show()
| id|    name|gender|age|
+---+--------+------+---+
|  1| Zhangsan|     F| 23|
|  2| Lisi|     M| 24|
+---+--------+------+---+

4.2.2 写入数据

import java.util.Properties
import org.apache.spark.sql.types._
import org.apache.spark.sql.Row

//下面我们设置两条数据表示两个学生信息
val studentRDD = spark.sparkContext.parallelize(Array("3 Wangwu M 26","4 Zhaoliu M 27")).map(_.split(" "))

//下面要设置模式信息
val schema = StructType(List(StructField("id", IntegerType, true),StructField("name", StringType, true),StructField("gender", StringType, true),StructField("age", IntegerType, true)))

//下面创建Row对象,每个Row对象都是rowRDD中的一行
val rowRDD = studentRDD.map(p => Row(p(0).toInt, p(1).trim, p(2).trim, p(3).toInt))
 
//建立起Row对象和模式之间的对应关系,也就是把数据和模式对应起来
val studentDF = spark.createDataFrame(rowRDD, schema)

//下面创建一个prop变量用来保存JDBC连接参数
val prop = new Properties()
prop.put("user", "root") //表示用户名是root
prop.put("password", "Hive@2020") //表示密码是hadoop
prop.put("driver","com.mysql.jdbc.Driver") //表示驱动程序是com.mysql.jdbc.Driver

//下面就可以连接数据库,采用append模式,表示追加记录到数据库spark的student表中
studentDF.write.mode("append").jdbc("jdbc:mysql://mnode1:3306/spark", "spark.student", prop)

5 Hive数据仓库读写操作

5.1 让Spark包含Hive支持

scala> import org.apache.spark.sql.hive.HiveContext

如果返回错误信息,也就是spark无法识别org.apache.spark.sql.hive.HiveContext,这就说明你当前电脑上的Spark版本不包含Hive支持。

为了让Spark能够访问Hive,需要把Hive的配置文件hive-site.xml拷贝到Spark的conf目录下,请在Shell命令提示符状态下操作:

cd /usr/local/spark/conf
cp /usr/local/hive/conf/hive-site.xml .
ls

5.2 连接Hive读写数据

1、在进行编程之前,我们需要做一些准备工作,我们需要修改“/usr/local/spark/conf/spark-env.sh”这个配置文件:

cd /usr/local/spark/conf/
vim spark-env.sh

这样就使用vim编辑器打开了spark-env.sh这个文件,这文件里面以前可能包含一些配置信息,全部删除,然后输入下面内容:

export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_271
export CLASSPATH=$CLASSPATH:/usr/local/hive/lib
export SCALA_HOME=/usr/local/scala
export HIVE_CONF_DIR=/usr/local/hive/conf

2、保存spark-env.sh这个文件,退出vim编辑器。之后我们配置spark-defaults.conf文件,具体命令如下:

cd /usr/local/spark/conf/
cp spark-defaults.conf.template spark-defaults.conf
vim spark-defaults.conf

这样就使用vim编辑器打开了spark-defaults.conf这个文件,然后输入下面内容:

spark.driver.extraClassPath=/usr/local/spark/jars/mysql-connector-java-5.1.49/*

5.2.1 从Hive中读取数据

import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession
 
case class Record(key: Int, value: String)
 
// warehouseLocation points to the default location for managed databases and tables
val warehouseLocation = "spark-warehouse"
 
val spark = SparkSession.builder().appName("Spark Hive Example").config("spark.sql.warehouse.dir", warehouseLocation).enableHiveSupport().getOrCreate()
 
import spark.implicits._
import spark.sql
//下面是运行结果
scala> sql("SELECT * FROM sparktest.student").show()
+---+--------+------+---+
| id|    name|gender|age|
+---+--------+------+---+
|  1| Zhangsan|     F| 23|
|  2| Lisi |     M| 24|
+---+--------+------+---+

5.2.2 向Hive中写入数据

import java.util.Properties
import org.apache.spark.sql.types._
import org.apache.spark.sql.Row

//下面我们设置两条数据表示两个学生信息
val studentRDD = spark.sparkContext.parallelize(Array("3 Wangwu M 26","4 Zhaoliu M 27")).map(_.split(" "))
 
//下面要设置模式信息
val schema = StructType(List(StructField("id", IntegerType, true),StructField("name", StringType, true),StructField("gender", StringType, true),StructField("age", IntegerType, true)))
 
//下面创建Row对象,每个Row对象都是rowRDD中的一行
val rowRDD = studentRDD.map(p => Row(p(0).toInt, p(1).trim, p(2).trim, p(3).toInt))

//建立起Row对象和模式之间的对应关系,也就是把数据和模式对应起来
val studentDF = spark.createDataFrame(rowRDD, schema)
 
//查看studentDF
studentDF.show()
+---+---------+------+---+
| id|     name|gender|age|
+---+---------+------+---+
|  3| Wangwu|     M| 26|
|  4|  Zhaoliu|     M| 27|
+---+---------+------+---+
//下面注册临时表
studentDF.registerTempTable("tempTable") 
 
sql("insert into sparktest.student select * from tempTable")
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的冲浪码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值