============SparkSQL的前身Shark概述=================================
在三四年前,Hive可以说是SQL on Hadoop的唯一选择,负责将SQL编译成可扩展的MapReduce作业。鉴于Hive的性能以及与Spark的兼容,Shark项目由此而生。
Shark即Hive on Spark,本质上是通过Hive的HQL解析,把HQL翻译成Spark上的RDD操作,然后通过Hive的metadata获取数据库里的表信息,实际HDFS上的数据和文件,会由Shark获取并放到Spark上运算。
Shark的最大特性就是快和与Hive的完全兼容,且可以在shell模式下使用rdd2sql()这样的API,把HQL得到的结果集,继续在scala环境下运算,支持自己编写简单的机器学习或简单分析处理函数,对HQL结果进一步分析计算。
=====================SparkSQL==========================
Spark为结构化数据处理引入了一个称为Spark SQL的编程模块。它提供了⼀个称为DataFrame的编程抽象,并且可以充当分布式SQL查询引擎
Spark SQL的特点
1、支持多种数据源:Hive、RDD、Parquet、JSON、JDBC等。
2、多种性能优化技术:in-memory columnar storage、byte-code generation、cost model动态评估等。
3、组件扩展性:对于SQL的语法解析器、分析器以及优化器,用户都可以自己重新开发,并且动态扩展。
====================SparkSQL的应用和Hive集成================
基于SparkSQL的应用和Hive集成(基于SparkSQL的应用需要Hive表的元数据)
-1. 将hive的hive-site.xml文件复制或者软连接到spark的conf文件夹中
=======> 将hive-site.xml文件放到spark应用的classpath环境变量中
cd /opt/cdh-5.3.6/spark/conf
ln -s /opt/cdh-5.3.6/hive/conf/hive-site.xml
-2. 根据hive-site.xml的配置文件内容,选择不同的操作方式
根据hive.metastore.uris参数的配置项,默认为空
-a. 如果没有给定参数值(默认情况)
将hive元数据库的连接驱动包添加到Spark应用的classpath环境变量中即可完成sparksql应用和hive的集成
-b. 如果给定了具体的metastore的服务所在节点的信息(非空)
--1. 启动hive的metastore服务 &表示在后台运行
cd /opt/cdh-5.3.6/hive/
bin/hive --service metastore &
--2. 完成spark和hive的集成
注意:Spark应用是讲很多HQL语句放在一个SPARK应用里面执行,Hive ON Spark 是讲一条HQL语句放一个应用去执行。
SparkSQL和Hive集成测试
cd /opt/cdh-5.3.6/spark
bin/spark-shell
scala> sqlContext.sql("select * from emp a join dept b on a.deptno=b.deptno").show
scala> sqlContext.sql("select * from emp").show
bin/spark-sql
use common;
select * from emp a join dept b on a.deptno=b.deptno;
=================Spark应用依赖第三方jar文件解决方案================
-1. 使用参数--jars添加本地的第三方jar文件,可以给定多个,使用逗号分隔
注意:要求driver运行的机器上必须存在该jar文件
bin/spark-shell --jars /opt/cdh-5.3.6/hive/lib/mysql-connector-java-5.1.27-bin.jar,/opt/cdh-5.3.6/hive/lib/derby-10.10.1.1.jar
-2. 使用--packages添加maven中央库的第三方jar文件,可以给定多个,使用逗号分隔(利用maven的conf/settings.xml配置文件)
注意:将第三方的jar文件下载后保存到本地的~/.ivy2/jars文件夹中(如果文件夹夹中存在,那么就不会下载直接使用)
bin/spark-shell --packages mysql:mysql-connector-java:5.1.27
-3. 在应用中设置spark.jars参数,参数值为本地的jar文件路径(运行过程中会自动的进行提交操作)
-4. 使用SPARK_CLASSPATH环境变量给定jar文件所在的路径
内部添加的是spark应用的两个参数:spark.driver.extraClassPath和spark.executor.extraClassPath
注意:要求所有执行节点上均存在对应文件夹及对应的jar文件
cd /opt/cdh-5.3.6/spark
mkdir -p external_jars
vim conf/spark-env.sh
SPARK_CLASSPATH=/opt/cdh-5.3.6/spark/external_jars/*
cd external_jars
cp /opt/cdh-5.3.6/hive/lib/mysql-connector-java-5.1.27-bin.jar .
cp /opt/cdh-5.3.6/hive/lib/derby-10.10.1.1.jar .
-5. 将依赖的jar文件打包到最终的jar文件中
注意:Spark On Yarn Cluster运行模式的情况下,第三方jar文件依赖解决访问(推荐):
将第三方的jar文件copy到${HADOOP_HOME}/share/hadoop/common/lib或者${HADOOP_HOME}/share/hadoop/yarn/lib文件夹中
=======================================================
SparkCore
SparkContext
RDD
SparkSQL
SQLContext: SparkSQL的入口,依赖SparkContext
HiveContext:
SQLContext的子类,当SparkSQL和Hive集成的时候(访问hive元数据、使用HQL语法),必须使用HiveContext作为SparkSQL的入口
DataFrame:SparkSQL中的核心抽象,类似RDD
=================SparkSQL的ThriftServer服务==============================
SparkSQL的ThriftServer服务
也就是Hive的hiveserver2服务
http://spark.apache.org/docs/1.6.1/sql-programming-guide.html#distributed-sql-engine
https://cwiki.apache.org/confluence/display/Hive/Setting+Up+HiveServer2
https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Overview
配置:
-1. ThriftServer服务所在的机器已经完成了SparkSQL和Hive集成的操作
-2. 根据自己的需要修改hive-site.xml中hiveserver2服务的相关参数
hive.server2.thrift.port=10000
hive.server2.thrift.bind.host=localhost
-3. 启动thriftserver的服务
sbin/start-thriftserver.sh
-4. 关闭thriftserver服务
sbin/stop-thriftserver.sh
测试:
用户名要求具有操作HDFS文件的权限,如果hdfs权限没有开启,用户名任意
-1. Web UI
ThriftServer服务还是一个Spark应用,所以Spark应用所有的东西在thriftserver中都可以使用(优化、监控....)
-2. 通过Spark自带的beeline命令
bin/beeline
beeline> !connect jdbc:hive2://bigdata-01.yushu.com:10000
Connecting to jdbc:hive2://bigdata-01.yushu.com:10000
Enter username for jdbc:hive2://bigdata-01.yushu.com: hadoop
Enter password for jdbc:hive2://bigdata-01.yushu.com:10000: ******
17/07/09 12:00:23 INFO jdbc.Utils: Supplied authorities: bigdata-01.yushu.com
17/07/09 12:00:23 INFO jdbc.Utils: Resolved authority: bigdata-01.yushu.com
17/07/09 12:00:24 INFO jdbc.HiveConnection: Will try to open client transport with JDBC Uri: jdbc:hive2://bigdata-01.yushu.com:10000
Connected to: Spark SQL (version 1.6.1)
Driver: Spark Project Core (version 1.6.1)
Transaction isolation: TRANSACTION_REPEATABLE_READ
0: jdbc:hive2://hadoop-senior01:10000>
0: jdbc:hive2://hadoop-senior01:10000> use common;
0: jdbc:hive2://hadoop-senior01:10000> select * from dept a join emp b on a.deptno = b.deptno;
退出命令: !quit
-3. 通过jdbc的代码来测试
http://maven.aliyun.com/nexus/content/groups/public/
http://mvnrepository.com/search?q=hive+jdbc
================SparkSQL读取JSON格式的HDFS文件===========================
-1. 将测试文件上传HDFS
hdfs dfs -mkdir -p /user/yushu/spark/sql/data
hdfs dfs -put /opt/cdh-5.3.6/spark/examples/src/main/resources/* /user/yushu/spark/sql/data
-2. 编写SparkSQL程序
val path = "/user/yushu/spark/data/people.json"
val df = sqlContext.jsonFile(path)
df.registerTempTable("tmp_people")
sqlContext.sql("select age,name from tmp_people where age is not null").show
sqlContext.sql("select age,name from json.`/user/yushu/spark/data/people.json` where age is not null").show
===================DataFrame====================================
内部是以RDD为基础的分布式数据集,区别是:DataFrame包含了一个schema的信息
RDD: 数据,DataFrame中的RDD类型其实就是RDD[Row],eg: df.rdd
Schema: 数据对应的结构,也就是RDD中Row对应的数据结构,eg: df.schema
创建的方式:
Spark1.3之后才出现DataFrame概念
val df = sqlContext.##
SparkSQL操作:
-1. HQL/SQL开发
将DataFrame注册成为临时表
然后通过sqlContext.sql("sql语句")来进行操作
-2. DSL开发
直接通过操作DataFrame的API进行业务开发,类似RDD的操作
DataFrame数据输出:
-1. 将DataFrame转换为RDD然后进行数据输出 1、saveAS... 2、foreach.....
-2. 直接调用DataFrame的相关API进行数据输出
df.show()
df.collect()
df.write.#
df.#
SparkSQL应用的处理过程
-1. 数据读入形成DataFrame
-2. DataFrame数据操作,得到最终的DataFrame结果
-3. 最终的DataFrame结果数据输出
DataFrame内部实际上是一个逻辑计划
所有的数据执行都是一个lazy的操作
调用相关的API的时候,实质是在内部构建一个查询的逻辑计划,类似RDD的构建过程;只有当DataFrame被触发调用的时候(获取数据的操作),才真正的执行
=================DataFrame的read和write编程模式=================
https://spark-packages.org/
https://github.com/databricks
功能:通过SparkSQL内部定义的read和write数据读写入口进行数据的加载和保存操作
读取数据:
val df = sqlContext.read.####.load()
写出数据:
df.write.####.save()
Read:
功能:读取外部数据源的数据,并形成DataFrame
相关函数说明:
根据一个已经存在的RDD来创建一个DataFrame对象
http://spark.apache.org/docs/1.6.1/sql-programming-guide.html#interoperating-with-rdds
-1. Inferring the Schema Using Reflection
利用schema的反射机制来进行创建操作
要求:RDD中的数据类型必须是case class数据类型,而且必须引入隐式转换
import sqlContext.implicits._
-2. Programmatically Specifying the Schema
调用SQLContext的相关API,根据给定的RDD和schema来进行DataFrame的创建
要求:RDD中的数据类型必须是Row类型的 valdf = rdd.toDF()
====================SparkSQL函数=================================
对于Hive中的窗口分析函数,必须使用HiveContext作为入口
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+WindowingAndAnalytics
ROW_NUMBER ===> 对每组数据按照给定的字段进行排序后,赋予从1开始的序列号
===> 解决分组排序TopN程序最优方案
sqlContext.sql("select deptno,sal,row_number() over (partition by deptno order by sal desc) as rnk from common.emp").show()
+------+------+---+
|deptno| sal|rnk|
+------+------+---+
| 10|5000.0| 1|
| 10|2450.0| 2|
| 10|1300.0| 3|
| 20|3000.0| 1|
| 20|3000.0| 2|
| 20|2975.0| 3|
| 20|1100.0| 4|
| 20| 800.0| 5|
| 30|2850.0| 1|
| 30|1600.0| 2|
| 30|1500.0| 3|
| 30|1250.0| 4|
| 30|1250.0| 5|
| 30| 950.0| 6|
+------+------+---+
sqlContext.sql("select * from (select deptno,sal, row_number() over (partition by deptno order by sal desc) as rnk from common.emp) t where t.rnk <=3").show()
+------+------+---+
|deptno| sal|rnk|
+------+------+---+
| 10|5000.0| 1|
| 10|2450.0| 2|
| 10|1300.0| 3|
| 20|3000.0| 1|
| 20|3000.0| 2|
| 20|2975.0| 3|
| 30|2850.0| 1|
| 30|1600.0| 2|
| 30|1500.0| 3|
+------+------+---+
====================================================================
Dataset
创建方式
http://spark.apache.org/docs/1.6.1/sql-programming-guide.html#creating-datasets
Dataset、DataFrame、RDD的区别与联系:
相同点:
都是分布式数据集
数据是分区的不可变的
DataFrame和Dataset的相同点还有:都具有数据特征/数据类型(schema)
不同点:
RDD中的数据没有数据类型
DataFrame中的数据是弱数据类型的,在运行之前不会做类型检查
Dataset中的数据类型是强数据类型的
Dataset中序列化机制是Kryo,RDD和DataFrame中是java的序列化机制
Dataset的运行速度比DataFrame/RDD快
==================SparkSQL优化===========================
-1. SparkCore中用到的优化相关的东西,SparkSQL经常使用
-2. SparkSQL的专门的优化
-a. hql语句的优化
-b. 参数优化
http://spark.apache.org/docs/1.6.1/sql-programming-guide.html#performance-tuning
spark.sql.shuffle.partitions:200, 给定hql语句中如果存在join\xxByKey的操作的时候(也就是存在shuffle的时候),DataFrame的分区数量
总结:
SparkSQL应用和Hive集成的方式
DataFrame是啥
DataFrame、Dataset、RDD的区别与联系
SparkSQL的write和read编程模型
SparkSQL使用DSL语言开发
案例:
不同数据源数据的操作
分区排序TopN的实现
========================= 不同数据源数据的JOIN操作=================================
object HiveJoinMysqlDemo { def main(args: Array[String]) { //构建上下文 val conf=new SparkConf() .setAppName("HiveJoinMysql") .setMaster("local[*]") val sc=SparkContext.getOrCreate(conf) val sqlContext=new HiveContext(sc) //连接mysql数据库 val url="jdbc:mysql://localhost:3306/yushu" val table="tb_dept" val props=new Properties() props.put("user","root") props.put("password","123456") // 2. 将dept数据同步到mysql中 sqlContext .read .table("db_emp.dept") .write .mode(SaveMode.Overwrite) .jdbc(url,table,props) // 3. mysql数据和hive数据join // 将mysql的数据读取形成DataFrame,然后注册成为临时表 val df = sqlContext .read .jdbc(url, table, Array("loc <= 'CHICAGO'", "loc > 'CHICAGO'"), props) df.registerTempTable("mysql_dept") sqlContext .sql("select a.*, b.loc,b.dname from db_emp.emp a join mysql_dept b on a.deptno=b.deptno") .registerTempTable("result_tmp01") // 3. 将数据写到HDFS上形成parquet格式的数据 sqlContext .table("result_tmp01") .write .format("parquet") .mode(SaveMode.Overwrite) .save("/user/yushu/spark/data")
//按部门分区保存在Hive表中
sqlContext .table(
"result_tmp01") .write .format(
"parquet") .partitionBy(
"deptno") .mode(SaveMode.
Overwrite) .saveAsTable(
"test01") }} ======================
SparkSQLThriftServerJDBCDemo=========================
def main(args: Array[String]): Unit = { // 1. 添加driver val driver = "org.apache.hive.jdbc.HiveDriver" Class.forName(driver) // 创建连接 val url = "jdbc:hive2://bigdata-01.yushu.com:10000" val conn = DriverManager.getConnection(url, "gerry", "123456") // 执行sql conn.prepareStatement("use db_emp").execute() val pstmt = conn.prepareStatement("select * from dept a join emp b on a.deptno = b.deptno where b.sal > ?") pstmt.setInt(1, 2500) val rs = pstmt.executeQuery() while (rs.next()) { println(rs.getString("ename") + ":" + rs.getDouble("sal")) } // 关闭连接 rs.close() pstmt.close() conn.close() }========================= ReadCSVSparkSQL ============================
import org.apache.spark.sql.hive.HiveContext import org.apache.spark.sql.types.{StringType, IntegerType, StructField, StructType} import org.apache.spark.sql.{SaveMode, SQLContext} import org.apache.spark.{SparkContext, SparkConf}
object ReadCSVSparkSQL { def main(args: Array[String]): Unit = { val conf = new SparkConf() .setAppName("ReadCSVSparkSQL") .setMaster("local[*]") .set("spark.sql.shuffle.partitions", "4") val sc = SparkContext.getOrCreate(conf) val sqlContext = new HiveContext(sc) val time = System.currentTimeMillis() // 2. 读取csv文件然后形成DataFrame,然后将DataFrame注册成为临时表tmp_taxi // 列名称:id,lat, lon, time val schema = StructType(Array( StructField("id", IntegerType), StructField("lat", StringType), StructField("lon", StringType), StructField("time", StringType) )) val path = "datas/taxi.csv" val df = sqlContext .read .format("com.databricks.spark.csv") .schema(schema) .load(path) df.registerTempTable("tmp_taxi") // 3.1 获取需要进行分析的列字段(id、time),并将time转换为小时 sqlContext.sql( """ |select id,substring(time,0,2) as hour |from tmp_taxi """.stripMargin ).registerTempTable("tmp01") // 3.2 统计各个出租车在各个时间段的载客次数 sqlContext.sql( """ |select id,hour,COUNT(1) as cnt |from tmp01 |group by id,hour """.stripMargin).registerTempTable("tmp02") // 3.3 获取每个小时段的载客次数前10的出租车数据(先按照小时分组,然后对每组数据获取前10条出租车载客数据<最多的>) ---> 分组排序TopN --> row_number函数 sqlContext.sql( """ |select id,hour,cnt, |ROW_NUMBER() OVER(PARTITION BY hour ORDER BY cnt DESC) as rnk |FROM tmp02 """.stripMargin).registerTempTable("tmp03") sqlContext.sql( """ |select id,hour,cnt |from tmp03 |where rnk<=10 """.stripMargin).registerTempTable("tmp04") //4.将结果保存 sqlContext.cacheTable("tmp04") sqlContext .table("tmp04") .map(row => { val id = row.getAs[Int]("id") val hour = row.getAs[String]("hour") val cnt = row.getAs[Long]("cnt") (id, hour, cnt) }).saveAsTextFile(s"result/csv/${time}/1") sqlContext .table("tmp04") .write .mode(SaveMode.Overwrite) .format("com.databricks.spark.csv") .save(s"result/csv/${time}/2") Thread.sleep(100000) } }
=====================RDD2DataFrameSparkSQ============================
object RDD2DataFrameSparkSQL { def main(args: Array[String]): Unit = { // 1. 构建上下文 val conf = new SparkConf() .setAppName("RDD2DataFrameSparkSQL") .setMaster("local[*]") // 由于SparkContext在JVM中只能存在一份,所以尽量不要使用new方式来创建 val sc = SparkContext.getOrCreate(conf) val sqlContext = new SQLContext(sc) import sqlContext.implicits._ // 2. 方式一:反射的机制 // alt + enter 快速导入变量的数据类型(鼠标放在变量后面) val rdd: RDD[Person] = sc.parallelize(Array( Person("gerry", 12), Person("小刘", 15), Person("小明", 18) )) val df = rdd.toDF() // 默认以case class的属性名称作为DataFrame的列名称 df.show() // 也可以通过toDF的参数来给定列名称,但是要求给定的数组集合中的元素和原始的列数量一致 rdd.toDF("c1", "c2").show() sc.parallelize(Array( ("gerry", 12), ("小刘", 15), ("小明", 18) )).toDF().show() // 方式二:利用明确给定的schema和rdd进行构建 val rowRDD = rdd.map(p => { val name = p.name val age = p.age Row(name, age)//构建Row类型的RDD }) val schema = StructType(Array( StructField("name", StringType), StructField("age", IntegerType)//构建Schema )) val df2 = sqlContext.createDataFrame(rowRDD, schema) df2.show() // DataFrame -> RDD val rdd1: RDD[Row] = df2.rdd val rdd2: RDD[(String, Int)] = df2.map(row => { // Row中的数据是没有数据类型的 val name = row.getAs[String]("name") val age = row.getAs[Int]("age") (name, age) }) rdd2.foreachPartition(iter => iter.foreach(println)) } } case class Person(name: String, age: Int)=======================UDFAndUDAFSparkSQL ================================
import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.sql.SQLContext
object UDFAndUDAFSparkSQL { def main(args: Array[String]): Unit = { // 1. 构建上下文 val conf = new SparkConf() .setAppName("UDFAndUDAFSparkSQL") .setMaster("local[*]") // 由于SparkContext在JVM中只能存在一份,所以尽量不要使用new方式来创建 val sc = SparkContext.getOrCreate(conf) val sqlContext = new SQLContext(sc) import sqlContext.implicits._ // 自定义UDF sqlContext.udf.register("format_double", (v:Double, i:Int) => { import java.math.BigDecimal val bd = new BigDecimal(v) bd.setScale(i, BigDecimal.ROUND_HALF_UP).doubleValue() }) // udaf注册 sqlContext.udf.register("self_avg", SelfAvgUDAF) // 创建一个DataFrame并注册成为临时表 sc.parallelize(Array( (1, 1234.123), (1, 4521.125), (1, 4523.0), (1, 562.0), (2, 1235.0), (2, 1255.235), (2, 1412.253), (2, 124.0), (2, 143.235), (3, 4562.0), (3, 1252.0), (3, 125.0), (3, 1154.0), (4, 1263.235), (4, 1246.0), (4, 1252.253), (4, 1451.0), (4, 1223.0), (5, 1263.235), (5, 1253.0), (6, 1235.0) )) .toDF("id", "sal") .registerTempTable("tmp01") sqlContext.sql( """ |SELECT | id, | AVG(sal) as sal1, | format_double(AVG(sal), 2) as sal2, | format_double(self_avg(sal), 2) as sal3 |FROM | tmp01 |GROUP BY id """.stripMargin) .show() } }
===============SelfAvgUDAF对象================ import org.apache.spark.sql.Row import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction} import org.apache.spark.sql.types._ object SelfAvgUDAF extends UserDefinedAggregateFunction{ override def inputSchema: StructType = { // 给定输入的数据类型 StructType( Array( StructField("v1", DoubleType) ) ) } override def bufferSchema: StructType = { // 给定缓存过程中的数据类型 StructType( Array( StructField("b1", DoubleType), StructField("b2", IntegerType) ) ) } override def dataType: DataType = { // 给定函数的返回数据类型 DoubleType } override def deterministic: Boolean = { // 指定多次运行的结果是否必须一致 true } override def initialize(buffer: MutableAggregationBuffer): Unit = { // 初始化缓存区中的 buffer.update(0, 0.0) buffer.update(1, 0) } override def update(buffer: MutableAggregationBuffer, input: Row): Unit = { // 对于输入的数据input更新buffer的值 val iv = input.getDouble(0) val bsum = buffer.getDouble(0) val btotal = buffer.getInt(1) buffer.update(0, bsum + iv) buffer.update(1, btotal + 1) } /** * 该方法只有当多个分区存在的时候才会被调用(对多个分区的聚合结果做几个全局的聚合操作) * @param buffer1 * @param buffer2 */ override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = { // 将buffer2中的值合并到buffer1中 val sum1 = buffer1.getDouble(0) val total1 = buffer1.getInt(1) val sum2 = buffer2.getDouble(0) val total2 = buffer2.getInt(1) buffer1.update(0, sum1 + sum2) buffer1.update(1, total1 + total2) } override def evaluate(buffer: Row): Any = { // 计算最终结果 buffer.getDouble(0) / buffer.getInt(1) } }