一个运行在Spark上执行sql的处理框架,可以用来处理结构化的数据【外部数据源(访问hive、json、parquet等文件的数据)】。
Spark SQL提供了SQL的API、DateFrame和DataSet的API
前端可以有不同种的访问方式,Hive AST传过来的就是一个字符串(解析成抽象语法树),在Catalyst会生成一个未解析完成的执行计划,这个执行计划会和元数据中的表信息进行整合生成逻辑执行计划,经过优化,生成优化执行计划,最后才会生成一个物理执行计划(Spark执行作业),提交到对应的集群上运行。
3.1.1 pom.xml依赖
2.11.8
2.1.0
org.scala-lang
scala-library
${scala.version}
provided
org.apache.spark
spark-sql_2.11
${spark.version}
provided
3.1.2 编写测试类 (这里用scala语言编写)
object SparkSessionApp {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName("SparkSessionApp").master("local[2]").getOrCreate()
val people = spark.read.json("file:///D:/people.json")
people.show()
spark.stop()
}
}
运行结果如下:
因为Hive的元数据信息是存放在MySQL中的,所以我们应该在spark的conf/目录下,复制一份hive-site.xml过来
javax.jdo.option.ConnectionURL
jdbc:mysql://localhost:3306/sparksql?createDatabaseIfNotExist=true
javax.jdo.option.ConnectionDriverName
com.mysql.jdbc.Driver
javax.jdo.option.ConnectionUserName
root
javax.jdo.option.ConnectionPassword
root
hive.metastore.schema.verification
false
A)启动 spark-shell --master local[2] --jars mysql.jar包
B)我们还可以用spark-sql指令去操作Hive数据库
启动 spark-sql --master local[2] --jars mysql.jar包
注意:如果要访问操作Hive中的数据,必须要先启动HDFS
C)使用JDBC访问Hive中的数据
依赖:
org.apache.spark
spark-hive_2.11
${spark.version}
D)使用JDBC操作hive
thriftserver启动 【修改默认占用端口】
./start-thriftserver.sh --master local[2] --jars /home/Kiku/mysql-connector-java-5.1.27-bin.jar --hiveconf hive.server2.thrift.port=14000
添加依赖
org.spark-project.hive
hive-jdbc
1.2.1.spark2
编写客户端使用JDBC连接代码
object SparkSQLThriftServerApp {
def main(args: Array[String]): Unit = {
Class.forName("org.apache.hive.jdbc.HiveDriver")
val conn = DriverManager.getConnection("jdbc:hive2://192.168.6.130:14000", "Kiku", "")
val pstmt = conn.prepareStatement("select empno, ename, sal from emp")
val rs = pstmt.executeQuery()
while (rs.next()) {
println("empno: " + rs.getInt("empno") + ", ename: " +
"" + rs.getString("ename") + ", sal: " + rs.getDouble("sal"))
}
rs.close()
pstmt.close()
conn.close()
}
}
DataSet:分布式的数据集
RDD:分布式的数据集
DataFrame:以列(列名、列的类型、列值)的形式构成的分布式数据集,按照列赋予不同的名称【更像是一张数据表】
RDD和DataFrame的区别?
【RDD如果是用Java/Scala语言开发,底层是跑在JVM上;用python也会跑在Python的运行环境;执行效率可能会不一样】
【DataFrame底层会转换为逻辑执行计划,执行效率相等】
/**
* DataFrame API基本操作
*/
object DataFrameApp {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("DataFrameApp").master("local[2]").getOrCreate()
//将json文件加载成一个dataframe
val peopleDF = spark.read.format("json").load("file:///D:/people.json")
//输出dataframe对应的schema信息
peopleDF.printSchema()
//输出数据集的前20条记录
peopleDF.show()
//查询某列所有的数据 select name from table
peopleDF.select("name").show()
//查询某几列所有的数据,并对列进行计算:select name, age + 10 as age2 from table
peopleDF.select(peopleDF.col("name"), (peopleDF.col("age") + 10).as("age2")).show()
//根据某一列的值进行过滤:select * from table where age > 19
peopleDF.filter(peopleDF.col("age") > 19).show()
//根据某一列进行分组,然后再进行聚合操作:select age, count(1) from table group by age
peopleDF.groupBy("age").count().show()
spark.stop()
}
}
1)反射方式:case class
2)编程方式:定义struct,和rdd绑定
/**
* DataFrame和RDD的互操作
*/
object DataFrameRDDApp {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("DataFrameRDDApp").master("local[2]").getOrCreate()
//inferReflection(spark)
program(spark)
spark.stop()
}
// 利用编程
private def program(spark: SparkSession): Unit = {
//RDD => DataFrame
val rdd = spark.sparkContext.textFile("file:///D:/infos.txt")
val infoRDD = rdd.map(_.split(",")).map(line => Row(line(0).toInt, line(1), line(2).toInt))
val structType = StructType(Array(StructField("id", IntegerType, true),
StructField("name", StringType, true),
StructField("age", IntegerType, true)))
val infoDF = spark.createDataFrame(infoRDD, structType)
infoDF.printSchema()
infoDF.show()
//通过df的api进行操作
infoDF.filter(infoDF.col("age") > 30).show()
//通过sql的方式进行操作
//注册为一张表 表名是infos
infoDF.createOrReplaceTempView("infos")
spark.sql("select * from infos where age > 30").show()
}
// 利用反射
private def inferReflection(spark: SparkSession) = {
//RDD => DataFrame
val rdd = spark.sparkContext.textFile("file:///D:/infos.txt")
//注意:需要导入隐式转换
import spark.implicits._
val infoDF = rdd.map(_.split(",")).map(line => Info(line(0).toInt, line(1), line(2).toInt)).toDF()
infoDF.show()
infoDF.filter(infoDF.col("age") > 30).show()
// 如果对于编程不熟悉 可以利用sql的方式进行处理,注册为一张表 表名是infos
infoDF.createOrReplaceTempView("infos")
spark.sql("select * from infos where age > 30").show()
}
case class Info(id: Int, name: String, age: Int)
}
/**
* DataFrame中的操作,综合实例
*/
object DataFrameCase {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("DataFrameCase").master("local[2]").getOrCreate()
//RDD => DataFrame
val rdd = spark.sparkContext.textFile("file:///D:/student.txt")
//注意:需要导入隐式转换
import spark.implicits._
val studentDF = rdd.map(_.split("\\|")).map(line => Student(line(0).toInt, line(1), line(2), line(3))).toDF()
//最多显示30行数据,false:表示长的数据不会被截取掉
studentDF.show(30, false)
//取出前10条记录
studentDF.take(10)
studentDF.first()
studentDF.head(3)
//截取其中的几列
studentDF.select("email").show(30, false)
studentDF.filter("name='' OR name='NULL'").show()
//找出name以M开头的人
studentDF.filter("SUBSTR(name, 0, 1)='M'").show()
//按照name进行排序
studentDF.sort(studentDF.col("name")).show()
studentDF.sort(studentDF.col("name").desc).show()
studentDF.sort("name", "id").show()
studentDF.sort(studentDF.col("name").asc, studentDF.col("id").desc).show()
studentDF.select(studentDF.col("name").as("student_name")).show()
//join
val studentDF2 = rdd.map(_.split("\\|")).map(line => Student(line(0).toInt, line(1), line(2), line(3))).toDF()
studentDF.join(studentDF2, studentDF.col("id") === studentDF2.col("id")).show()
spark.stop()
}
//模式类:实例化对象的时候可以不用new,默认public,有默认的toString方法,不能被继承
case class Student(id: Int, name: String, phone: String, email: String)
}
/**
* Parquet文件操作
*/
object ParquetApp {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("ParquetApp")
.master("local[2]").getOrCreate()
/**
* spark.read.format("parquet").load 这是标准写法
*/
val userDF = spark.read.format("parquet").load("file:///home/Kiku/app/spark-2.1.0-bin-2.6.0-cdh5.7.0/examples/src/main/resources/users.parquet")
userDF.printSchema()
userDF.show()
userDF.select("name", "favorite_color").show()
//把读出来的两列 转换成json写入新文件中
userDF.select("name", "favorite_color").write.format("json").save("file:///home/Kiku/data/jsonout")
spark.read.load("file:///home/Kiku/app/spark-2.1.0-bin-2.6.0-cdh5.7.0/examples/src/main/resources/users.parquet").show()
//会报错,因为sparksql默认处理的format就是parquet
spark.read.load("file:///home/Kiku/app/spark-2.1.0-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json").show()
spark.read.format("parquet").option("path", "file:///home/Kiku/app/spark-2.1.0-bin-2.6.0-cdh5.7.0/examples/src/main/resources/users.parquet").load().show()
//调整默认分区大小 默认是200
spark.sqlContext.setConf("spark.sql.shuffle.partitions", "10")
//操作mysql 的数据
spark.read.format("jdbc").option("url", "jdbc:mysql://localhost:3306/sparksql")
.option("dbtable", "sparksql.TBLS").option("user", "root")
.option("password", "MySQLKenan_07").option("driver", "com.mysql.jdbc.Driver").load()
spark.stop()
}
}
import org.apache.spark.sql.SparkSession
/**
* 使用外部数据源综合查询Hive和MySQL的表数据
*/
object HiveMySQLApp {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("HiveMySQLApp")
.master("local[2]").getOrCreate()
//加载Hive表的数据
val hiveDF = spark.table("emp")
//加载mysql的数据
val mysqlDF = spark.read.format("jdbc")
.option("url", "jdbc:mysql://localhost:3306/sparksql")
.option("dbtable", "sparksql.DEPT")
.option("user", "root").option("password", "MySQLKenan_07")
.option("driver", "com.mysql.jdbc.Driver").load()
//JOIN
val resultDF = hiveDF.join(mysqlDF, hiveDF.col("deptno") === mysqlDF.col("DEPTNO"))
resultDF.select(hiveDF.col("empno"), hiveDF.col("ename"),
mysqlDF.col("deptno"), mysqlDF.col("dname")).show()
resultDF.show()
spark.stop()
}
}
A) 比如{"age":[1, 2, 3]} 这样我们可以通过select age[0] from table; 去访问数组中的某一个元素
运用explode函数,select explode(age) from table; 我们可以获得age的三行记录
B) 比如{"address":{"province":"山东","city":"青岛"}} 这样我们可以通过select address.city from table;去访问address中的元素
列式存储(parquet文件)
分区裁剪
条件下压(where、join,数据分析的时候,我们可以在数据源端进行数据过滤,不加载某些不必要的列,可以边读数据边过滤)
parquet文件默认是snappy压缩格式,可以根据SparkSession.builder().config("spark.sql.parquet.compression.codec","gzip")更改压缩格式
Bzip2压缩效果明显是最好的,但是bzip2压缩速度最慢,可分割
Gzip压缩效果不如Bzip2,但是压缩解压速度快,不支持分隔
LZO压缩效果不如Bzip2和Gzip,但是压缩解压速度最快!并且支持分隔!
对每个分区进行批量插入,在插入的时候,用addBatch,批量插入数据
调整并行度
spark-submit \
--class com.xq.analysis.yarn.CategoryCommendAnalysisYARN \
--name CategoryCommendAnalysisYARN \
--master yarn \
--executor-memory 1G \
--num-executors 1 \
--conf spark.sql.shuffle.partitions=100
/home/Kiku/lib/project/sparksqlproject-1.0-SNAPSHOT-jar-with-dependencies.jar \
hdfs://hadoop000:8020/project/clean/category-commend
调整分区字段类型推测【所有分区类型都是string】
SparkSession.builder().config("spark.sql.sources.partitionColumnTypeInterface.enabled", "false");
7.1 去掉运行模式、作业名称等参数
val spark = SparkSession.builder().getOrCreate()
7.2 给环境中存在的依赖加入
maven-assembly-plugin
jar-with-dependencies
7.3 打包
mvn assembly:assembly
7.4 提交执行
spark-submit \
--class com.xq.analysis.yarn.CategoryCommendAnalysisYARN \
--name CategoryCommendAnalysisYARN \
--master yarn \
--executor-memory 1G \
--num-executors 1 \
/home/Kiku/lib/project/sparksqlproject-1.0-SNAPSHOT-jar-with-dependencies.jar \
hdfs://hadoop000:8020/project/clean/category-commend
如果有需要添加的文件,用--files添加