Spark最初由美国加州伯克利大学(UC Berkeley)的AMPLab于2009年开发,是基于内存计算的大数据并行计算框架,可用于构建大型的、低延迟的数据分析应用程序
运行速度快:采用DAG执行引擎和内存计算
容易使用:支持包括Scala、Java、Python和R语言在内的多种语言进行编程
与HDFS等存储层兼容:可以独立运行,也可以运行在YARN等集群管理系统之上,
特别地,它可以运行在任何的Hadoop数据源上,例如,HDFS和HBase
通用性:类似Hadoop,Spark提供了完整而强大的技术栈,包括SQL查询、流式计算、机器学习和图算法等组件
Spark Application的运行架构由两部分组成:driver program和若干个executors
集群资源管理器Cluster Manager可以是Spark自带的或mesos或YARN
4.1 Spark SQL
Spark SQL是用于对结构化数据进行处理的Saprk模块。与基本的Spark RDD API不同,Spark SQL提供的端口提供了结构和正进行的计算更多的信息。
4.2数据类型
spark主要数据类型有RDD, DataFrame以及Dataset
~/spark/bin/spark-shell
进入spark-shell模式
5.1 sparksession
在spark中所有功能的进入点是sparksession类,可以使用sparksession.builder()创建一个基本SparkSession
sparksession在spark2中提供内置的hive特征,包括使用HiveQL写入查询,读取Hive UDFs以及从Hive表读取数据的能力,但不必现有hive设置
使用sparksession可以从现有的RDD、Hive表或者Spark数据源中创建DataFrame
import org.apache.spark.sql.SparkSession
val spark = SparkSession
.builder()
.appName("Spark SQL basicexample")
.config("spark.some.config.option","some-value") .getOrCreate()
// 用于隐式变换如将RDDs转换为DataFrames
import spark.implicits._
5.2 创建DataFrames
val df = spark.read.json("examples/src/main/resources/people.json")
// 在标准输出中显示内容
df.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
5.3无类型DataSet操作(也称为DataFrame操作)
import spark.implicits._
// 使用树格式打印结构
df.printSchema()
// root // |-- age:
long (nullable = true)
// |-- name: string (nullable =
true)
// 仅选取姓名列
df.select("name").show()
// +-------+
// | name|
// +-------+
// |Michael|
// | Andy|
// | Justin|
// +-------+
// 选取所有人并并对年龄增值
df.select($"name", $"age" + 1).show()
// +-------+---------+
// | name|(age + 1)|
// +-------+---------+
// |Michael| null|
// | Andy| 31|
// | Justin| 20|
// +-------+---------+
//选取大于21岁的人
df.filter($"age" > 21).show()
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+
//通过年龄计数
df.groupBy("age").count().show()
// +----+-----+
// | age|count|
// +----+-----+
// | 19| 1|
// |null| 1|
// | 30| 1|
// +----+-----+
5.3 运行SQL程序化查询
sparksession的sql函数使得可以运行sql查询,并将结果作为DataFrame返回
// 注册 DataFrame 为SQL 临时视图
df.createOrReplaceTempView("people")
val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
5.4 全局临时视图
spark sql中的临时视图是会话域的即会随着创建它的会话的终止而消失,如果想要临时视图能在会话间共享直至spark应用终止,则可以创建全局临时视图
// 注册 DataFrame 为全局临时视图
df.createGlobalTempView("people")
// 全局临时视图是连接到系统保护的数据库中
spark.sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
// 全局临时视图是跨会话的
spark.newSession().sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
5.5 创建数据集
Datasets与RDDs相同,它并非使用java的序列化或kryo,而是使用定制的Encoder去序列化对象用于处理或在网络中传输。当Encoder和标准序列化都能转换对象为byte时,Encoder是动态生成代码并使用准许spark进行需要操作的格式,比如过滤、排序和hashing而不用再将byte反序列化为对象
// 条件类在scala2.10中仅支持最多22个字段,注意该限制
// 可以使用定制类来实现产品接口
case class Person(name: String, age: Long)
// Encoder创建用来条件类使用
val caseClassDS = Seq(Person("Andy", 32)).toDS() caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+
// Encoder对大多常见类型而言可以通过载入spark.implicits._提供
val primitiveDS = Seq(1, 2, 3).toDS() primitiveDS.map(_ + 1).collect()
// DataFrames可以转换为DataSet通过提供一个类,映射将通过name完成
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person] peopleDS.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
5.6 与RDD相互转换
Spark SQL支持两种不同方法来将现有的RDD转化为Datasets
(1)使用映射
scala对Spark SQL的接口支持自动转换一个包含条件类的RDD为DataFrame,该条件类定义了表的结构。对条件类参数的名称使用映射进行读取并作为列名称。条件类同样可以嵌入或包含复杂的类型比如Seqs或Arrays.该RDD可以隐式转换为一个DataFrame并注册为一个表,表可以用于后续的SQL语句
import spark.implicits._
// 从一个文本文件中创建一个Person对象的RDD,并转换为DataFrame
val peopleDF = spark.sparkContext
.textFile("examples/src/main/resources/people.txt")
.map(_.split(","))
.map(attributes => Person(attributes(0), attributes(1).trim.toInt))
.toDF()
// 注册DataFrame为临时视图
peopleDF.createOrReplaceTempView("people")
// SQL 语句可以通过spark提供的sql方法运行
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND
19")
// 结果中一行的列可以通过字段索引获取
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
//或者通过字段名称
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
// +------------+
// | value|
// +------------+
// |Name: Justin|
// +------------+
// 非预定义的encoder用于Dataset[Map[K, V]], 显式定义
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
// 原始类型以及条件类型也可以定义为
// implicit val stringIntMapEncoder:
Encoder[Map[String, Any]]=ExpressionEncoder()
// row.getValuesMap[T]立刻获取多个列到一个Map[String, T]中
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()
// Array(Map("name"
-> "Justin", "age" -> 19))
(2)使用程序定义
当条件类不能提前定义时(比如记录的结构被编码为字符串或一个文本数据集用来解析且字段将被映射到不同的用户),一个DataFrame可以编程创建按如下3步:
1 从原始RDD创建一个行的RDD
2 创建一个结构由StructType表示来匹配1创建的RDD行的结构
3 应用该结构到RDD的行中通过SparkSession提供的createDataFrame方法
import org.apache.spark.sql.types._
// 创建一个RDD
val peopleRDD = spark.sparkContext.textFile("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)
// 转换RDD记录(people)为行
val rowRDD = peopleRDD
.map(_.split(","))
.map(attributes => Row(attributes(0), attributes(1).trim))
// 将结构应用于RDD
val peopleDF = spark.createDataFrame(rowRDD, schema)
// 使用DataFrame创建临时视图
peopleDF.createOrReplaceTempView("people")
// 该sql查询返回结果为DataFrame且支持所有正常RDD操作
val results = spark.sql("SELECT name FROM people")
results.map(attributes => "Name: " + attributes(0)).show()
// +-------------+
// | value|
// +-------------+
// |Name: Michael|
// | Name: Andy|
// | Name: Justin|
// +-------------+
import org.apache.spark.sql.expressions.MutableAggregationBuffer
import org.apache.spark.sql.expressions.UserDefinedAggregateFunction
import org.apache.spark.sql.types._
import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession
object MyAverage extends UserDefinedAggregateFunction {
// 该聚合函数输入参数的数据类型
def inputSchema: StructType = StructType(StructField("inputColumn", LongType) :: Nil)
// 聚合函数缓存值的数据类型
def bufferSchema: StructType = {
StructType(StructField("sum", LongType) :: StructField("count", LongType) :: Nil)
}
// 返回值的数据类型
def dataType: DataType = DoubleType
// 是否以上函数对于同一输入总是返回相同输出
def deterministic: Boolean = true
// 初始化给定的聚合缓存,该缓存自身是一个'Row'即额外的标准方法如按位置获取值(如 get(), getBoolean()), 提供了更新值的可能
// 注意缓存内的arrays和maps仍是不可变的
def initialize(buffer: MutableAggregationBuffer): Unit = {
buffer(0) = 0L
buffer(1) = 0L
}
// 更新给定聚合缓存,'buffer'里是来自'input'的新输入数据
def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
if(!input.isNullAt(0)) {
buffer(0) = buffer.getLong(0) + input.getLong(0)
buffer(1) = buffer.getLong(1) + 1
}
}
// 合并两个聚合缓存并将已更新的缓存值存回'buffer 1'
def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
}
// 计算最后结果
def evaluate(buffer: Row): Double = buffer.getLong(0).toDouble / buffer.getLong(1)
}
// 注册该函数以使用它
spark.udf.register("myAverage", MyAverage)
val df = spark.read.json("file:///root/hdp/spark2/examples/src/main/resources/employees.json")
df.createOrReplaceTempView("employees")
df.show()
// +-------+------+
// | name|salary|
// +-------+------+
// |Michael| 3000|
// | Andy| 4500|
// | Justin| 3500|
// | Berta| 4000|
// +-------+------+
val result = spark.sql("SELECT myAverage(salary) as average_salary FROM employees")
result.show()
// +--------------+
// |average_salary|
// +--------------+
// | 3750.0|
// +--------------+
类型安全用户定义聚合函数
用户定义的聚合函数用户强类型数据集围绕聚合抽象类。示例如下:
import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.Encoder
import org.apache.spark.sql.Encoders
import org.apache.spark.sql.SparkSession
case class Employee(name: String, salary: Long)
case class Average(var sum: Long, var count: Long)
object MyAverage extends Aggregator[Employee, Average, Double] {
// 用于该聚合的零值,应当满足特性:任一 b + zero = b
def zero: Average = Average(0L, 0L)
// 结合里两个值产生新的值。处于性能的考虑,该函数应当修改缓存并将其返回而不是新建一个对象
def reduce(buffer: Average, employee: Employee): Average = {
buffer.sum += employee.salary
buffer.count += 1
buffer
}
// 合并两个中间值
def merge(b1: Average, b2: Average): Average = {
b1.sum += b2.sum
b1.count += b2.count
b1
}
// 转换reduction的输出
def finish(reduction: Average): Double = reduction.sum.toDouble / reduction.count
// 指定中间值类型的编码器
def bufferEncoder: Encoder[Average] = Encoders.product
// 指定最终输出值类型给的编码器
def outputEncoder: Encoder[Double] = Encoders.scalaDouble
}
val ds = spark.read.json("file:///root/hdp/spark2/examples/src/main/resources/employees.json").as[Employee]
ds.show()
// +-------+------+
// | name|salary|
// +-------+------+
// |Michael| 3000|
// | Andy| 4500|
// | Justin| 3500|
// | Berta| 4000|
// +-------+------+
// 变换该函数为一个'TypedColumn' 并命名
val averageSalary = MyAverage.toColumn.name("average_salary")
val result = ds.select(averageSalary)
result.show()
// +--------------+
// |average_salary|
// +--------------+
// | 3750.0|
// +--------------+
Spark SQL 支持操作多种数据源通过DataFrame接口。一个DataFrame可以使用关系转换进行操作,还可以用于创建临时视图。注册一个DataFrame为临时视图允许在该数据上进行SQL查询。本节描述了使用Spark数据源加载与保存数据的一般方法然后可以对内置的数据源进行特定的操作。
一般加载/保存函数
最简单的形式,默认数据源(parquer,除非通过spark.sql.sources.default配置)将用于所有操作
val usersDF = spark.read.load("file:///root/hdp/spark2/examples/src/main/resources/users.parquet")
usersDF.select("name", "favorite_color").write.save("namesAndFavColors.parquet")
手动指定选项
同样也可以手动指定数据源,它将会一直被你想传递给数据源的额外的操作使用。数据源被指定为它们的完整名称(如org.apache.spark.sql.parquet),但对于内置数据源同样可以使用短名(json, parquet, jdbc, orc, libsvm, csv, text)。从任意数据源类型加载而来的DataFrame可以被转换为其他数据类型使用以下语句
val peopleDF = spark.read.format("json").load("examples/src/main/resources/people.json")
peopleDF.select("name", "age").write.format("parquet").save("namesAndAges.parquet")
直接对文件运行SQL
除了使用API加载文件为
参考教程:
Spark 2.2.0 官方手册