Spark集成
Spark集成
一、Spark 架构与优化器
1.Spark架构 (重点)
- 能够直接访问现存的Hive数据(链接Hive的元数据寻址)
- 提供JDBC/ODBC接口供第三方工具借助Spark进行数据处理
- 提供了更高层级的接口方便地处理数据(sql算子,DataFrame类)
- 支持多种操作方式:SQL、API编程 (Spark-sql ,spark-shell,spark API)
- 支持多种外部数据源:Parquet、JSON、RDBMS、csv、text、hive、hbase等
2.Spark优化器
SQL语句首先通过Parser模块被解析为语法树,此棵树称为Unresolved Logical Plan; Unresolved Logical Plan通过Analyzer模块借助于数据元数据解析为Logical Plan; 此时再通过各种基于规则的优化策略进行深入优化,得到Optimized Logical Plan; 优化后的逻辑执行计划依然是逻辑的,并不能被Spark系统理解,此时需要将此逻辑执行计划转换为Physical Plan;
优化
1、在投影上面查询过滤器 2、检查过滤是否可下压
二、Spark+SQL的API (重点)
前言:Spark对具有Schema的数据的维护其实与Hive是相似的,自身维护了一个元数据库和一个数据仓库,DataFrame则是针对具有schema的数据所包装RDD类,提供了可以直接操作schema数据的sql接口,并且提供了sql函数直接解析原生sql语句。同时Spark也可以通过hive的metastore接口或者jdbc接口链接到外部的元数据库上,通过这个元数据库便实现了对hive的数据仓库和RDBMS上的数据的操作。
SparkContext
SQLContext
- Spark SQL的编程入口
HiveContext
- SQLContext的子集,包含更多功能
SparkSession(Spark 2.x推荐)
- SparkSession:合并了SQLContext与HiveContext
- 提供与Spark功能交互单一入口点,并允许使用DataFrame和Dataset API对Spark进行编程
———————————————————————————————————————————
1.DataSet简介
特定域对象(Seq、Array、RDD)中的强类型的集合,基于RDD,与RDD最大的区别在于:DS有schema数据结构信息。DataSet= RDD + Schema
- DataSet即有Schema数据结构信息(行列)的数据集合
scala> spark.createDataset(1 to 3).show
scala> spark.createDataset(List(("a",1),("b",2),("c",3))).show
scala> spark.createDataset(sc.parallelize(List(("a",1,1),("b",2,2)))).show
- createDataset()的参数可以是:Seq、Array、RDD
- 上面三行代码生成的Dataset分别是:Dataset[Int]、Dataset[(String,Int)]、Dataset[(String,Int,Int)]
- Dataset=RDD+Schema,所以Dataset与RDD有大部共同的函数,如map、filter等
———————————————————————————————————————————
2.DataFrame简介
基于DS,但是元素仅限为Row类,这种限定使得DF的操作可以针对性的优化,因此DF的操作往往比DS快3倍以上。Row就相当于SQL中的记录
DataFrame = RDD[ROW] + Schema
DataFrame=Dataset[Row]
类似传统数据的二维表格
在RDD基础上加入了Schema(数据结构信息)
DataFrame Schema支持嵌套数据类型
- 对应HBase的数据结构,资源消耗较大
- struct
- map
- array
提供更多类似SQL操作的API,比如直接使用sql算子以sql语句作为参数进行查询:
df.sql("select * from table_name")
———————————————————————————————————————————
3.RDD与DF/DS的创建
WHAT is the schema of this:
- DS/DF 转换到RDD
case class Point(label:String,x:Double,y:Double)
val points=Seq(Point("bar",3.0,5.6),Point("foo",-1.0,3.0)).toDF("label","x","y")
//转换
val s=points.rdd
将RDD组织为DS或DF
- toDF/toDS 算子
- 这种方法根据RDD内部数据类型的反射信息自动推断生成Schema
case class Person(name:String,age:Int)
//反射获取RDD内的样例类的Schema来构造DF
import spark.implicits._
val people=sc.textFile("file:///home/hadooop/data/people.txt")
.map(_.split(","))
.map(p => Person(p(0), p(1).trim.toInt)).toDF() ////map构造实例类,toDS或者toDF方法
people.show
people.registerTempTable("people") //将DF注册为临时表,以使用sql语句
val teenagers = spark.sql("SELECT name, age FROM people WHERE age >= 13 AND age <= 19")
teenagers.show()
//也可以不定义样例类直接使用Array、Seq等集合的toDF方法,参数给定为列名,类型会自动推断
通过Schema+DF来定义有结构的DF (重点)
sc.textfile读取有格式的文本时需要解决隐式的格式问题以及数据头问题
spark.read.格式 无法解决字段格式自动转换,全是String
因此我们通过预定义schema再读取的方式解决繁琐的数据类型转换问题
前提是所有字段都是所需的,不然需要额外定义不关心的字段
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.{StructType, StructField, StringType}
val myschema=new StructType().add("order_id",StringType).add("order_date",StringType).add("customer_id",StringType).add("status",StringType)
val orders=spark.read.schema(myschema).csv("file:///root/orders.csv")
通过ROW+Schema组织DF (适用于外部的原始数据源)
//构造ROW对象和指定Schema的方法组合
people=sc.textFile("file:///home/hadoop/data/people.txt")
val schemaString = "name age" // 以字符串的方式定义DataFrame的Schema信息
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.{StructType, StructField, StringType} // StructType类自定义Schema
val schema = StructType(schemaString.split(" ").map(fieldName =>StructField(fieldName,StringType, true))) //类型是Arrayp[StructField] //Row类包装数据内容
val rowRDD = people.map(_.split(",")).map(p => Row(p(0), p(1).trim)) // 创建DF类,参数为Row类和StructType类
val peopleDataFrame = spark.createDataFrame(rowRDD, schema) // 将DataFrame注册成临时表
peopleDataFrame.registerTempTable("people")
val results = spark.sql("SELECT name FROM people")
results.show
———————————————————————————————————————————
4.常用操作
//引用select
ds.select("name")
ds.select(ds("name"))
ds.select(col("name"))
ds.select(column("name"))
ds.select('name')
ds.select($"name")
//统计指标
df.describe("colname").show()
//日期处理类(JAVA)
import java.time.LocalDate
import java.time.format.DateTimeFormatter
LocalDate.parse("2018-08-01 12:22:21", DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")).getDayOfWeek
//包装这个JAVA方法为scala函数,方便使用
def TimeParse(x:String):String={
LocalDate.parse(x,DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")).getDayOfWeek.toString
}
//join操作,列比较时使用三元符号===
val joined=orderDS.join(order_itemsDS,orderDS("id")===order_itemsDS("order_id"))
//常见操作
val df = spark.read.json("file:///home/hadoop/data/people.json")
// 使用printSchema方法输出DataFrame的Schema信息
df.printSchema()
// 使用select方法来选择我们所需要的字段
df.select("name").show()
// 使用select方法选择我们所需要的字段,并未age字段加1!!!!!!!!!!!!!!
df.select(df("name"), df("age") + 1).show()
// 使用filter方法完成条件过滤
df.filter(df("age") > 21).show()
// 使用groupBy方法进行分组,求分组后的总数
df.groupBy("age").count().show()
//sql()方法执行SQL查询操作
df.registerTempTable("people") //先要将df注册为临时表
spark.sql("SELECT * FROM people").show //直接在sql中查询注册的表
5、类型转换
read方法获得的字段都是String型,需进行类型转换
//1、单列转化方法
import org.apache.spark.sql.types._
val data = Array(("1", "2", "3", "4", "5"), ("6", "7", "8", "9", "10"))
val df = spark.createDataFrame(data).toDF("col1", "col2", "col3", "col4", "col5")
import org.apache.spark.sql.functions._
df.select(col("col1").cast(DoubleType)).show()
+----+
|col1|
+----+
| 1.0|
| 6.0|
+----+
//2、循环转变
//然后就想能不能用这个方法循环把每一列转成double,但没想到怎么实现,可以用withColumn循环实现。
val colNames = df.columns
var df1 = df
for (colName <- colNames) {
df1 = df1.withColumn(colName, col(colName).cast(DoubleType))
}
df1.show()
+----+----+----+----+----+
|col1|col2|col3|col4|col5|
+----+----+----+----+----+
| 1.0| 2.0| 3.0| 4.0| 5.0|
| 6.0| 7.0| 8.0| 9.0|10.0|
+----+----+----+----+----+
//3、通过:_*
//但是上面这个方法效率比较低,然后问了一下别人,发现scala 有array:_*这样传参这种语法,而df的select方法也支持这样传,于是最终可以按下面的这样写
val cols = df.columns.map(x => col(x).cast("Double"))
df.select(cols: _*).show()
+----+----+----+----+----+
|col1|col2|col3|col4|col5|
+----+----+----+----+----+
| 1.0| 2.0| 3.0| 4.0| 5.0|
| 6.0| 7.0| 8.0| 9.0|10.0|
+----+----+----+----+----+
//这样就可以很方便的查询指定多列和转变指定列的类型了:
val name = "col1,col3,col5"
df.select(name.split(",").map(name => col(name)): _*).show()
df.select(name.split(",").map(name => col(name).cast(DoubleType)): _*).show()
三、Spark外部数据源操作 (重点)
1.Parquet文件(默认文件)
一种流行的列式存储格式,以二进制存储,文件中包含数据与元数据 (ROW数据和Schema元数据)
import org.apache.spark.sql.types._
val schema=StructType(Array(StructField("name",StringType),
StructField("favorite_color",StringType),
StructField("favorite_numbers",ArrayType(IntegerType))))
val rdd=sc.parallelize(List(("Alyssa",null,Array(3,9,15,20)),("Ben","red",null)))
val rowRDD=rdd.map(p=>Row(p._1,p._2,p._3))
val df=spark.createDataFrame(rowRDD,schema)
val df=spark.read.parquet("/data/users/") //该目录下必须已存在parquet文件
df.write.parquet("/data/users_new/") //在该目录下生成parquet文件
——————————————————————————————————————————
2.Hive表
集成hive:
Spark SQL与Hive集成: hive打开元数据仓库的外部接口,spark连入该数据库,通过该元数据信息获取hive上的数据。其实与Hive的元数据管理相同。
测试环境集成(shell开发)
- 打开Hive的metastore服务
#hive打开元数据库的外部接口 9083
nohup hive --service metastore & #nohup 绑定系统,终端退出服务也会运行
- 进入Spark-shell 链接hive数据库
//Spark 链接到hive的元数据库
spark-shell --conf spark.hadoop.hive.metastore.uris=thrift://localhost:9083
//或者,自行建立session
val spark = SparkSession.builder()
.config("spark.sql.warehouse.dir", warehouseLocation)
.enableHiveSupport()
.getOrCreate()
//保存表到hive上
df.saveAsTable("tbl_name")
生成环境集成(IDE开发)
Spark SQL与Hive集成: 1、hive-site.xml拷贝至${SPARK_HOME}/conf下 2、追加内容
<property>
<name>hive.metastore.uris</name>
<value>thrift://master的IP:9083</value>
</property>
(可选,xml没有该配置会自动链接jdbc到mysql) 3、启动元数据服务:nohup hive --service metastore & 4、自行创建SparkSession,应用配置仓库地址与启用Hive支持
val spark = SparkSession.builder()
.config("spark.sql.warehouse.dir", warehouseLocation) //warehouse可选,默认在启动时的目录下
.enableHiveSupport()
.getOrCreate()
spark存储到hive
hive --service metastore
spark-shell
spark.sql("show tables").show
//或者
val df=spark.table("toronto") //返回的是DataFrame类
df.printSchema
df.show
df.write.saveAsTable("dbName.tblName")
//hive
select * from dbName.tblName;
———————————————————————————————————————————
3.MySQL表(MySQL)
RDBMS关系型数据库管理系统,集成与hive基本相同,但是没有通过hive的metastore服务,而是直接使用jdbc去链接mysql
spark-shell --driver-class-path /opt/hive/lib/mysql-connector-java-5.1.38.jar
val df=spark.read.format("jdbc").option("delimiter",",").option("header","true").option("url","jdbc:mysql://192.168.137.137:3306/test").option("dbtable","orders").option("user","root").option("password","rw").load()
//
$spark-shell --jars /opt/spark/ext_jars/mysql-connector-java-5.1.38.jar //使用jdbc的jar去连RDBMS
val url = "jdbc:mysql://localhost:3306/test" //test是一个数据库名
val tableName = "TBLS" //TBLS是库中的一个表名
// 设置连接用户、密码、数据库驱动类
val prop = new java.util.Properties
prop.setProperty("user","hive")
prop.setProperty("password","mypassword")
prop.setProperty("driver","com.mysql.jdbc.Driver")
// 取得该表数据
val jdbcDF = spark.read.jdbc(url,tableName,prop)
jdbcDF.show
//DF存为新的表
jdbcDF.write.mode("append").jdbc(url,"t1",prop)
四、Spark+SQL的函数
1.内置函数(org.apache.spark.sql.funtions.scala)
- 区分于scala的函数,spark的sql函数时针对表的列的。
- 内置函数是针对DataFrame的,并非hive的UDF
类别 | 函数举例 |
---|---|
聚合函数 | countDistinct**、sumDistinct** |
集合函数 | sort_array**、explode** |
日期、时间函数 | hour**、quarter、next_day** |
数学函数 | asin**、atan、sqrt、tan、round** |
开窗函数 | row_number |
字符串函数 | concat**、format_number、regexp_extract** |
其他函数 | isNaN**、sha、randn、callUDF** |
2.自定义函数
定义函数
注册函数
- SparkSession.udf.register():只在sql()中有效 (spark的sql集成)
- functions.udf():对DataFrame API均有效 (spark基于RDD的数据库)
函数调用
//注册自定义函数,注意是匿名函数
spark.udf.register("hobby_num", (s: String) => s.split(',').size)
spark.sql("select name, hobbies, hobby_num(hobbies) as hobby_num from hobbies").show
五、Spark-SQL
因为招了好多Hive的研发人员,因此Spark-SQL的操作类似于hive
但是Spark-SQL基于的框架不是MR,因此速度相比非常快。
- Spark SQL CLI是在本地模式下使用Hive元存储服务和执行从命令行所输入查询语句的简便工具 注意,Spark SQL CLI无法与thrift JDBC服务器通信
- Spark SQL CLI等同于Hive CLI(old CLI)、Beeline CLI(new CLI)
- 启动Spark SQL CLI,请在Spark目录中运行以下内容
./bin/spark-sql
六、性能优化
1.序列化
Java序列化,Spark默认方式
Kryo序列化,比Java序列化快约10倍,但不支持所有可序列化类型
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
//向Kryo注册自定义类型
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]));//case class如果没有注册需要序列化的class,Kyro依然可以照常工作,但会存储每个对象的全类名(full class name),这样往往比默认的 Java serialization 更浪费空间
2.注意事项
- 使用对象数组、原始类型代替Java、Scala集合类(如HashMap)
- 避免嵌套结构
- 尽量使用数字作为Key,而非字符串
- 以较大的RDD使用MEMORY_ONLY_SER
- 加载CSV、JSON时,仅加载所需字段
- 仅在需要时持久化中间结果(RDD/DS/DF)
- 避免不必要的中间结果(RDD/DS/DF)的生成
- DF更快,执行速度比DS快约3倍
- 自定义RDD分区与spark.default.parallelism,该参数用于设置每个stage的默认task数量
- 将大变量广播出去,而不是直接使用
- 尝试处理本地数据并最小化跨工作节点的数据传输
3.表连接(join操作)
- 包含所有表的谓词(predicate)
select * from t1 join t2 on t1.name = t2.full_name
where t1.name = 'mike' and t2.full_name = 'mike'
- 最大的表放在第一位
- 广播最小的表
- 最小化表join的数量
Spark集成的更多相关文章
- Spark:利用Eclipse构建Spark集成开发环境
前一篇文章“Apache Spark学习:将Spark部署到Hadoop 2.2.0上”介绍了如何使用Maven编译生成可直接运行在Hadoop 2.2.0上的Spark jar包,而本文则在此基础上 ...
- spark集成hive遭遇mysql check失败的问题
问题: spark集成hive,启动spark-shell或者spark-sql的时候,报错: INFO MetaStoreDirectSql: MySQL check failed, assumin ...
- 机器学习 - pycharm, pyspark, spark集成篇
AS WE ALL KNOW,学机器学习的一般都是从python+sklearn开始学,适用于数据量不大的场景(这里就别计较“不大”具体指标是啥了,哈哈) 数据量大了,就需要用到其他技术了,如:spa ...
- 机器学习 - 开发环境安装pycharm + pyspark + spark集成篇
AS WE ALL KNOW,学机器学习的一般都是从python+sklearn开始学,适用于数据量不大的场景(这里就别计较“不大”具体指标是啥了,哈哈) 数据量大了,就需要用到其他技术了,如:spa ...
- Ignite与Spark集成时,ClassNotFoundException问题解决
参考文章:https://apacheignite-fs.readme.io/docs/installation-deployment Spark application deployment mod ...
- spark集成hbase与hive数据转换与代码练习
帮一个朋友写个样例,顺便练手啦~一直在做平台的各种事,但是代码后续还要精进啊... import java.util.Date import org.apache.hadoop.hbase.HBase ...
- 使用spark集成kudu做DDL
spark对kudu表的创建 定义kudu的表需要分成5个步骤: 1:提供表名 2:提供schema 3:提供主键 4:定义重要选项:例如:定义分区的schema 5:调用create Table a ...
- Spark 集成开发
WordCount.py # coding:utf-8 from pyspark import SparkContext from pyspark import SparkConf def SetLo ...
- spark 集成elasticsearch
pyspark读写elasticsearch依赖elasticsearch-hadoop包,需要首先在这里下载,版本号可以通过自行修改url解决. """ write d ...
随机推荐
- Java 容器系列总结
为什么要使用集合 当我们需要保存一组类型相同的数据的时候,我们应该是用一个容器来保存,这个容器就是数组,但是,使用数组存储对象具有一定的弊端, 因为我们在实际开发中,存储的数据的类型是多种多样的,于是 ...
- 嵌入式设备上卷积神经网络推理时memory的优化
以前的神经网络几乎都是部署在云端(服务器上),设备端采集到数据通过网络发送给服务器做inference(推理),结果再通过网络返回给设备端.如今越来越多的神经网络部署在嵌入式设备端上,即inferen ...
- poj 1511-- Invitation Cards (dijkstra+优先队列)
刚开始想复杂了,一直做不出来,,,其实就是两遍dijkstra+优先队列(其实就是板子题,只要能有个好的板子,剩下的都不是事),做出来感觉好简单...... 题意:有n个车站和n个志愿者,早上每个志愿 ...
- Python内置模块(你还在pip install time?)&& apt-get install -f
一.内置模块 之前不知道time是python自带的,还用pip安装.......还报错..... Python中有以下模块不用单独安装 1.random模块 2.sys模块 3.time模块 4.o ...
- - 迷宫问题 POJ - 3984 bfs记录路径并输出最短路径
定义一个二维数组: int maze[5][5] = { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, ...
- Windows Terminal 更换主题
1. 打开设置,是个json文件 2. 在此处获取主题配置:https://atomcorp.github.io/themes/ 3.将主题配置粘贴到schemes节点(可以增加N个) 4.配置每个命 ...
- VMX - block by NMI和 NMI unblockinig due to IRET 之间的关系
相关SDM章节: 27.2.3- Information About NMI Unblocking Due to IRET 最近收到同事发来的一个问题,即: VMCS 中的 Guest Interru ...
- 使VS开发的程序在Win7系统运行时自动提升权限
软件开发时,总是会遇到在Win7系统上运行不起来或者异常的情况,这通常是用户的权限不够引起的. 下面提供一个可以使程序运行时,自动提升用户权限的方法. 1.右键点击启动项目,单击"属性&qu ...
- 关于free和delete的使用
上一篇篇幅太长,这里再区分free和delete的用法. 两个同时存在是有它的原因的,我们前面说过,free是函数,它只释放内存,但不会调用析构函数,如果用free去释放new申请的空间,会因为无法调 ...
- CodeForces 348D Turtles(LGV定理)题解
题意:两只乌龟从1 1走到n m,只能走没有'#'的位置,问你两只乌龟走的时候不见面的路径走法有几种 思路:LGV定理模板.但是定理中只能从n个不同起点走向n个不同终点,那么需要转化.显然必有一只从1 ...