文章目录
函数中引用一个Driver端的一个类
案例1
package day6
import java.net.InetAddress
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object SerTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => {
//在map的函数中,创建一个rules实例,下面程序在executor的Task中执行。
val rules = new Rules //每获取一个word数据就会创建一个Rules对象
val hostname = InetAddress.getLocalHost.getHostName
val threadName = Thread.currentThread().getName
(hostname, threadName, rules.rulesMap.getOrElse(word, 0), rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
package day6
class Rules {
val rulesMap = Map("spark" -> 1, "hadoop" -> 2)
}
由结果可知,每获取一个word数据就会创建一个Rules对象。浪费内存资源。
案例2
闭包:函数的内部使用了一个外部的变量(函数式编程经常会遇到的问题)。
package day6
import java.net.InetAddress
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object SerTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
//在Driver端进行实例化
val rules = new Rules
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => {
val hostname = InetAddress.getLocalHost.getHostName
val threadName = Thread.currentThread().getName
//Task里面使用了一个Driver端的引用。
(hostname, threadName, rules.rulesMap.getOrElse(word, 0), rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
package day6
class Rules {
val rulesMap = Map("spark" -> 1, "hadoop" -> 2)
}
Task not serializable,Task没有被实例化原因?
因为Task中引用了Rules对象,Rules没有被实例化,所以不能通过Rpc通信传输到executor中。
实例3
package day6
import java.net.InetAddress
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object SerTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
//在Driver端进行实例化
val rules = new Rules
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => {
val hostname = InetAddress.getLocalHost.getHostName
val threadName = Thread.currentThread().getName
(hostname, threadName, rules.rulesMap.getOrElse(word, 0), rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
package day6
class Rules extends Serializable {
val rulesMap = Map("spark" -> 1, "hadoop" -> 2)
}
由此得出,每一个Task对应一个Rules实例,同一个Executor中的不同的Task使用不同的Task,但是有多个Task使用相同的规则时,还是会造成内存浪费。
实例4
package day6
import java.net.InetAddress
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object SerTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
//在Driver端进行实例化
val rules = Rules
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => {
val hostname = InetAddress.getLocalHost.getHostName
val threadName = Thread.currentThread().getName
(hostname, threadName, rules.rulesMap.getOrElse(word, 0), rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
package day6
object Rules extends Serializable {
val rulesMap = Map("spark" -> 1, "hadoop" -> 2)
}
上面的结果表明:在一个Executor中只有一个Rules实例,在一个JVM进程中只有一个实例。上面的方法,在Driver端进行了初始化。
实例5
package day6
import java.net.InetAddress
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object SerTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("SerTest")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(0))
val r = lines.map(word => {
val hostname = InetAddress.getLocalHost.getHostName
val threadName = Thread.currentThread().getName
(hostname, threadName, Rules.rulesMap.getOrElse(word, 0), Rules.toString)
})
r.saveAsTextFile(args(1))
sc.stop()
}
}
package day6
import java.net.InetAddress
//第三种方法,希望Rules在Executor中被初始化,不走网络,就不用在Driver端进行初始化和序列化。
object Rules{
//静态变量
val rulesMap = Map("spark" -> 2.7, "hadoop" -> 2.3)
val hostname = InetAddress.getLocalHost.getHostName
println(hostname + "@@@@@@@@@@@@!!!!!!!!!!!")
}
在Executor端的日志文件中可以看见初始化信息,只会读取一次,每个JVM中存放一个Rules对象。
Executor中多线程问题
案例1
package cn.edu360.game
import java.text.SimpleDateFormat
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/*
读取指定文件中的日志,选取在指定时间段内创建的新用户,并统计新用户的数量。
*/
object GameKPI {
def main(args: Array[String]): Unit = {
//"2016-02-01"
val startDate = args(0)
//"2016-02-02"
val endDate = args(1)
//查询条件
val dateFormat1 = new SimpleDateFormat("yyyy-MM-dd")
//查寻条件的的起始时间
val startTime = dateFormat1.parse(startDate).getTime
//查寻条件的的截止时间
val endTime = dateFormat1.parse(endDate).getTime
//Driver定义的一个simpledataformat,在Task中引用所以要发送到Executor中执行。
val dateFormat2 = new SimpleDateFormat("yyyy年MM月dd日,E,HH:mm:ss")
val conf = new SparkConf().setAppName("GameKPI").setMaster("local")
val sc = new SparkContext(conf)
//以后从哪里读取数据
val lines: RDD[String] = sc.textFile(args(2))
//整理并过滤
val splited: RDD[Array[String]] = lines.map(line => line.split("[|]"))
//按日期过过滤
val filterd = splited.filter(fields => {
val t = fields(0)
val time = fields(1)
val timeLong = dateFormat2.parse(time).getTime //在Executor中使用了Driver端的对象
t.equals("1") && timeLong >= startTime && timeLong < endTime //过滤器的判断条件
})
val dnu = filterd.count()
println(dnu)
sc.stop()
}
}
案例2
package cn.edu360.game
import java.text.SimpleDateFormat
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object GameKPIV2 {
def main(args: Array[String]): Unit = {
//"2016-02-01"
val startDate = args(0)
//"2016-02-02"
val endDate = args(1)
val dateFormat1 = new SimpleDateFormat("yyyy-MM-dd")
val startTime = dateFormat1.parse(startDate).getTime
val endTime = dateFormat1.parse(endDate).getTime
val conf = new SparkConf().setAppName("GameKPI").setMaster("local[4]")
val sc = new SparkContext(conf)
val lines: RDD[String] = sc.textFile(args(2))
val splited: RDD[Array[String]] = lines.map(line => line.split("[|]"))
val filteredByType = splited.filter(fields => {
//一个Task中会创建很多的FilterUtils实例,因为每处理一条就会创建一个实例
val fu = new FilterUtilsV3
fu.filterByType(fields,"1")
})
val filtered = filteredByType.filter(fields => {
//一个Task中会创建很多的FilterUtils实例,因为每处理一条就会创建一个实例
val fu = new FilterUtilsV3
fu.filterByTime(fields, startTime, endTime)
})
val dnu = filtered.count()
println(dnu)
sc.stop()
}
}
package cn.edu360.game
import java.text.SimpleDateFormat
class FilterUtilsV3{
//如果object使用了成员变量,那么会出现线程安全问题,因为object是一个单例,多线程可以同时调用这个方法
val dateFormat = new SimpleDateFormat("yyyy年MM月dd日,E,HH:mm:ss")
def filterByType(fields: Array[String], tp: String) = {
val _tp = fields(0)
_tp == tp
}
def filterByTime(fields: Array[String], startTime: Long, endTime: Long) = {
val time = fields(1)
val timeLong = dateFormat.parse(time).getTime
timeLong >= startTime && timeLong < endTime
}
}
案例3
package cn.edu360.game
import java.text.SimpleDateFormat
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* Created by zx on 2017/9/2.
*/
object GameKPIV3 {
def main(args: Array[String]): Unit = {
//"2016-02-01"
val startDate = args(0)
//"2016-02-02"
val endDate = args(1)
val dateFormat1 = new SimpleDateFormat("yyyy-MM-dd")
val startTime = dateFormat1.parse(startDate).getTime
val endTime = dateFormat1.parse(endDate).getTime
val conf = new SparkConf().setAppName("GameKPI").setMaster("local[4]")
val sc = new SparkContext(conf)
//以后从哪里读取数据
val lines: RDD[String] = sc.textFile(args(2))
//整理并过滤
val splited: RDD[Array[String]] = lines.map(line => line.split("[|]"))
//FilterUtils是在Driver端创建的
val fu = new FilterUtilsV3 with Serializable
val filtered = splited.filter(fields => {
fu.filterByTime(fields, startTime, endTime)
})
val dnu = filtered.count()
println(dnu)
sc.stop()
}
}
package cn.edu360.game
import java.text.SimpleDateFormat
class FilterUtilsV3{
//如果object使用了成员变量,那么会出现线程安全问题,因为object是一个单例,多线程可以同时调用这个方法
val dateFormat = new SimpleDateFormat("yyyy年MM月dd日,E,HH:mm:ss")
def filterByType(fields: Array[String], tp: String) = {
val _tp = fields(0)
_tp == tp
}
def filterByTime(fields: Array[String], startTime: Long, endTime: Long) = {
val time = fields(1)
val timeLong = dateFormat.parse(time).getTime
timeLong >= startTime && timeLong < endTime
}
}
案例4
package cn.edu360.game
import java.text.SimpleDateFormat
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object GameKPIV4 {
def main(args: Array[String]): Unit = {
//"2016-02-01"
val startDate = args(0)
//"2016-02-02"
val endDate = args(1)
val dateFormat1 = new SimpleDateFormat("yyyy-MM-dd")
val startTime = dateFormat1.parse(startDate).getTime
val endTime = dateFormat1.parse(endDate).getTime
val conf = new SparkConf().setAppName("GameKPI").setMaster("local[4]")
val sc = new SparkContext(conf)
//以后从哪里读取数据
val lines: RDD[String] = sc.textFile(args(2))
//整理并过滤
val splited: RDD[Array[String]] = lines.map(line => line.split("[|]"))
val filtered = splited.filter(fields => {
FilterUtilsV4.filterByTime(fields, startTime, endTime)
})
val dnu = filtered.count()
println(dnu)
sc.stop()
}
}
package cn.edu360.game
import java.text.SimpleDateFormat
import org.apache.commons.lang3.time.FastDateFormat
object FilterUtilsV4{
//如果object使用了成员变量,那么会出现线程安全问题,因为object是一个单例,多线程可以同时调用这个方法
val dateFormat = new SimpleDateFormat("yyyy年MM月dd日,E,HH:mm:ss")
//FastDateFormat是线程安全的
//val dateFormat = FastDateFormat.getInstance("yyyy年MM月dd日,E,HH:mm:ss")
def filterByType(fields: Array[String], tp: String) = {
val _tp = fields(0)
_tp == tp
}
//在同一个Executor中有一个FilterUtilsV4对象,一个Executor中可能有多个Task,不同的Task同时调用FilterUtilsV4的filterByTime方法,该方法中引用了SimpleDataFormat对象,当多个线程同时调用这一个对象时,由于线程不安全会导致异常。
def filterByTime(fields: Array[String], startTime: Long, endTime: Long) = {
val time = fields(1)
val timeLong = dateFormat.parse(time).getTime
timeLong >= startTime && timeLong < endTime
}
}
Spark SQL(处理结构化数据)
Spark SQL is Apache Spark’s module for working with structured data.
Spark SQL与Hive的作用相同:将SQL语句解析成RDD(DataFrame)和Map Reduce,对相应的数据进行处理。
特点:
1.SparkSQL是Spark上的高级模块,Spark SQL是一个SQL解析引擎,将SQL解析成特殊的RDD(DataFrame),然后在Spark集群中运行。
2.Spark SQL是用来处理结构化数据的(先将非结构化的数据转换成结构化数据)
3.SparkSQL支持两种编程API:SQL方式,DataFrame的方式(DSL)
4.Spark SQL兼容hive(元数据库,SQL语法,UDF自定义函数,序列化,反序列化机制)
5.SparkSQL支持统一的数据源,可以读取多种类型的数据
6.Spark SQL提供了标准的连接(JDBC,ODBC),以后可以对接一下BI工具。
什么是Data Frames
与RDD相似,DataFrame也是一个分布式数据容器。然而DataFrame更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即schema。同时,与Hive类似,DataFrame也支持嵌套数据类型(struct、array和map)。从API易用性的角度上 看,DataFrame API提供的是一套高层的关系操作,比函数式的RDD API要更加友好,门槛更低。由于与R和Pandas的DataFrame类似,Spark DataFrame很好地继承了传统单机数据分析的开发体验。
RDD和Data Frames有哪些区别
DataFrames里面存放的结构化数据的描述信息,DataFrame要有表头(表的描述信息),描述了有多少列,每一列叫什么名字,什么类型,能不能为空?
DataFrame是一个特殊的RDD(RDD+Schema信息就变成了DataFrame)
Spark SQL的第一个入门程序
首先在pom中添加spark SQL的依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
SparkSQL1.x和2.x的编程API有一些变化,企业中都有使用,所以两种方式都将
用1.x的方式
a.SQL
创建一个SQLContext
1.创建Spark Context,然后再创建SQL Context
2.先创建RDD,对数据进行处理,然后关联case class,将非结构化数据转换成结构化数据
3.显示的调用toDF方法将RDD转化成DataFrame
4.注册临时表
5.执行SQL(Transformation,lazy)
6.执行Action
=============
1.创建Spark Context,然后再创建SQL Context
2.先创建RDD,对数据进行处理,然后关联Row,将非结构化数据转换成结构化数据
3.定义schema
4.调用sqlContext的createDataFrame方法
5.注册临时表
6.执行SQL(Transformation,lazy)
7.执行Action
b.DSL(DataFrame)
import org.apache.spark.sql.{DataFrame, SQLContext}
import org.apache.spark.{SparkConf, SparkContext}
object SQLDemo1 {
def main(args: Array[String]): Unit = {
//提交的这个程序可以连接到Spark集群中
val conf = new SparkConf().setAppName("SQLDemo1").setMaster("local[2]")
//创建Spark SQL的连接(程序执行的入口)
val sc = new SparkContext(conf)
//sparkContext不能创建特殊的RDD(DataFrame)
//将SparkContext包装进而增强
val sqlContext = new SQLContext(sc)
//创建特殊的RDD(DataFrame),就是有schema信息的RDD
//先有一个普通的RDD,然后在关联上schema,进而转成DataFrame
val lines = sc.textFile("hdfs://node1:8020/person")
//将数据进行处理
val boyRDD = lines.map(line => {
val fields = line.split(",")
val id = fields(0).toLong
val name = fields(1)
val age = fields(3).toInt
val fv = fields(2).toDouble
Boy(id, name, age, fv)
})
//该RDD装的是Boy类型的数据,有了shcam信息,但是还是一个RDD
//将RDD转换成DataFrame
//对原先的RDD进行增强,导入隐式转化
import sqlContext.implicits._ //这个隐式转化在类里面,必须创建类的对象,才能导入
val bdf: DataFrame = boyRDD.toDF()
//变成DF后就可以使用两种API进行编程了
//把DataFrame注册临时表,DataFrame=RDD+schema
bdf.registerTempTable("t_boy")
//书写SQL(SQL方法其实是Transformation)
val result: DataFrame = sqlContext.sql("SELECT * FROM t_boy ORDER BY fv desc, age asc")
//查看结果(触发Action)
result.show()
sc.stop()
}
}
case class Boy(id: Long, name: String, age: Int, fv: Double)
package day6
import org.apache.spark.sql.types.{DoubleType, IntegerType, LongType, StringType, StructField, StructType}
import org.apache.spark.sql.{DataFrame, Row, SQLContext}
import org.apache.spark.{SparkConf, SparkContext}
object SQLDemo2 {
def main(args: Array[String]): Unit = {
//提交的这个程序可以连接到Spark集群中
val conf = new SparkConf().setAppName("SQLDemo1").setMaster("local[2]")
//创建Spark SQL的连接(程序执行的入口)
val sc = new SparkContext(conf)
//sparkContext不能创建特殊的RDD(DataFrame)
//将SparkContext包装进而增强
val sqlContext = new SQLContext(sc)
//创建特殊的RDD(DataFrame),就是有schema信息的RDD
//先有一个普通的RDD,然后在关联上schema,进而转成DataFrame
val lines = sc.textFile("hdfs://node1:8020/person")
//将数据进行处理
val rowRDD = lines.map(line => {
val fields = line.split(",")
val id = fields(0).toLong
val name = fields(1)
val age = fields(3).toInt
val fv = fields(2).toDouble
Row(id, name, age, fv)
})
//结构类型,其实就是表头,用于描述DataFrame
val schema = StructType(List(
StructField("id", LongType, true),
StructField("name", StringType, true),
StructField("age", IntegerType, true),
StructField("fv", DoubleType, true)
))
//将Row RDD关联schema
val bdf = sqlContext.createDataFrame(rowRDD, schema)
//该RDD装的是Boy类型的数据,有了schema信息,但是还是一个RDD
//将RDD转换成DataFrame
//对原先的RDD进行增强,导入隐式转化
//import sqlContext.implicits._ //这个隐式转化在类里面,必须创建类的对象,才能导入
//val bdf: DataFrame = boyRDD.toDF()
//变成DF后就可以使用两种API进行编程了
//把DataFrame注册临时表,DataFrame=RDD+schema
bdf.registerTempTable("t_boy")
//书写SQL(SQL方法其实是Transformation)
val result: DataFrame = sqlContext.sql("SELECT * FROM t_boy ORDER BY fv desc, age asc")
//查看结果(触发Action)
result.show()
sc.stop()
}
}
object SQLDemo3 {
def main(args: Array[String]): Unit = {
//提交的这个程序可以连接到Spark集群中
val conf = new SparkConf().setAppName("SQLDemo3").setMaster("local[2]")
//创建Spark SQL的连接(程序执行的入口)
val sc = new SparkContext(conf)
//sparkContext不能创建特殊的RDD(DataFrame)
//将SparkContext包装进而增强
val sqlContext = new SQLContext(sc)
//创建特殊的RDD(DataFrame),就是有schema信息的RDD
//先有一个普通的RDD,然后在关联上schema,进而转成DataFrame
val lines = sc.textFile("hdfs://node1:8020/person")
//将数据进行处理
val rowRDD = lines.map(line => {
val fields = line.split(",")
val id = fields(0).toLong
val name = fields(1)
val age = fields(3).toInt
val fv = fields(2).toDouble
Row(id, name, age, fv)
})
//结构类型,其实就是表头,用于描述DataFrame
val schema = StructType(List(
StructField("id", LongType, true),
StructField("name", StringType, true),
StructField("age", IntegerType, true),
StructField("fv", DoubleType, true)
))
//将Row RDD关联schema
val bdf: DataFrame = sqlContext.createDataFrame(rowRDD, schema)
//不使用SQL的方式,就不用注册临时表了
val df1: DataFrame = bdf.select("name", "age", "fv")
import sqlContext.implicits._
val df2: DataFrame = df1.orderBy($"fv" desc, $"age" asc)
df2.show()
sc.stop()
}
}
用2.x的方式
SparkSQL会在执行之前指定执行计划,优化执行流程。
sql的API
package day6
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, Row, SparkSession}
import org.apache.spark.sql.types.{DoubleType, IntegerType, LongType, StringType, StructField, StructType}
object SQLTest1 {
def main(args: Array[String]): Unit = {
//spark2.x SQL的变成API(Spark Session)
//是spark2.x SQL执行入口
val session = SparkSession.builder()
.appName("SQLTest1")
.master("local[3]")
.getOrCreate()
//创建RDD
val lines: RDD[String] = session.sparkContext.textFile("hdfs://node1:8020/person")
//将数据进行处理
val rowRDD = lines.map(line => {
val fields = line.split(",")
val id = fields(0).toLong
val name = fields(1)
val age = fields(3).toInt
val fv = fields(2).toDouble
Row(id, name, age, fv)
})
//结构类型,其实就是表头,用于描述DataFrame
val schema = StructType(List(
StructField("id", LongType, true),
StructField("name", StringType, true),
StructField("age", IntegerType, true),
StructField("fv", DoubleType, true)
))
//创建DataFram
val df: DataFrame = session.createDataFrame(rowRDD, schema)
import session.implicits._
val df2 = df.where($"fv" > 99).orderBy($"fv" desc, $"age" asc)
df2.show()
session.stop()
}
}
package day6
import org.apache.spark.sql.{DataFrame, Dataset, SparkSession}
/**
实现WordCount使用SQL的API。
*/
object SQLWordCount {
def main(args: Array[String]): Unit = {
//创建SparkSession
val spark = SparkSession.builder()
.appName("SQLWordCount")
.master("local[2]")
.getOrCreate()
//(指定以后从哪里)读数据,是lazy
//Dataset分布式数据集,是对RDD的进一步封装,是更加智能的RDD
//dataset只有一列,默认这列叫value
val lines: Dataset[String] = spark.read.textFile("hdfs://node1:8020/words")
//整理数据
//切分压平
//导入隐式转换,flatMap是DataSet上面的方法。
import spark.implicits._
val words: Dataset[String] = lines.flatMap(_.split(" "))
//注册表
words.createTempView("v_wc")
//执行SQL(Transformation)
val result: DataFrame = spark.sql("SELECT value,COUNT(*) counts FROM v_wc GROUP BY value ORDER BY counts desc")
//执行
result.show()
spark.stop()
}
}
DataSet的API
package day6
import org.apache.spark.sql.{DataFrame, Dataset, SparkSession}
//使用Dataset数据结构实现
object DatasetWordCount {
def main(args: Array[String]): Unit = {
//创建SparkSession
val spark = SparkSession.builder()
.appName("DatasetWordCount")
.master("local[2]")
.getOrCreate()
//(指定以后从哪里)读数据,是lazy
//Dataset分布式数据集,是对RDD的进一步封装,是更加智能的RDD
//dataset只有一列,默认这列叫value
val lines: Dataset[String] = spark.read.textFile("hdfs://node1:8020/words")
//整理数据
//切分压平
//导入隐式转换
import spark.implicits._
val words: Dataset[String] = lines.flatMap(_.split(" "))
//使用Dataset的API(DSL)
//val count = words.groupBy($"value" as "word").count().sort($"count" desc)
//导入聚合函数
import org.apache.spark.sql.functions._
val counts = words.groupBy($"value" as "word").agg(count("*") as "counts").orderBy($"counts" desc)
counts.show()
spark.stop()
}
}