前置条件:完成Scala环境部署
可以参考我的博客:Scala环境部署和简单介绍
然后我们在vmware虚拟机上配置Scala和Spark并初步使用
诞生于加州大学伯克利分校AMP实验室,是一个基于内存的分布式计算框架
发展历程:
2009年诞生于加州大学伯克利分校AMP实验室
2010年正式开源
2013年6月正式成为Apache孵化项目
2014年2月成为Apache顶级项目
2014年5月正式发布Spark 1.0版本
2014年10月Spark打破MapReduce保持的排序记录
2015年发布了1.3 1.4 1.5版本
2016年发布了1.6 2.x版本
MapReduce编程模型的局限性
繁杂
只有Map和Reduce两个操作,复杂的逻辑需要大量的样板代码
MapReduce处理效率低
Map中间结果写磁盘,Reduce写HDFS,多个Map通过HDFS交换数据
任务调度与启动开销大
不适合迭代处理、交互式处理和流式处理
Spark是类Hadoop MapReduce的通用并行框架
Job中间输出结果可以保存在内存,不再需要读写HDFS
比MapReduce平均快10倍以上
spark-shell --master local[*]
spark-shell --master spark://MASTERHOST:7077
spark-shell --master yarn-client
import org.apache.spark.{
SparkConf, SparkContext}
val conf=new SparkConf().setMaster("local[*]").setAppName("appName")
val sc=SparkContext.getOrCreate(conf)
import org.apache.spark.sql.SparkSession
val spark = SparkSession.builder
.master("local[*]")
.appName("appName")
.getOrCreate()
Spark核心,主要数据抽象
从Spark1.6开始引入的新的抽象,特定领域对象中的强类型集合,它可以使用函数或者相关操作并行地进行转换等操作
DataFrame是特殊的Dataset
简单的解释:RDD是将数据项拆分为多个分区的集合,存储在集群的工作节点上的内存中,并执行正确的操作
复杂的解释:RDD是用于数据转换的接口;RDD指向了存储在HDFS、Cassandra、HBase等、或缓存(内存、内存+磁盘、仅磁盘等),或在故障或缓存收回时重新计算其他RDD分区中的数据
RDD是 弹性分布式数据集 (Resilient Distributed Datasets):
两者是Spark提供的核心抽象
DAG(有向无环图)反映了RDD之间的依赖关系
第一种:使用集合创建RDD
val rdd=sc.parallelize(List(1,2,3,4,5,6))
rdd.count
rdd.partitions.size
val rdd=sc.parallelize(List(1,2,3,4,5,6),5)
rdd.partitions.size
val rdd=sc.makeRDD(List(1,2,3,4,5,6))
第二种:通过加载文件产生RDD
val distFile=sc.textFile("file:///home/hadoop/data/hello.txt")
distFile.count
val distHDFSFile=sc.textFile("hdfs://hadoop000:8020/hello.txt")
支持目录、压缩文件以及通配符
sc.textFile("/my/directory")
sc.textFile("/my/directory/*.txt")
sc.textFile("/my/directory/*.gz")
第三种:其他创建RDD的方法
分区是RDD被拆分并发送到节点的不同块之一
分为lazy与non-lazy两种:
对于转换操作,RDD的所有转换都不会直接计算结果:
spark常用RDD算子汇总(java和scala版本)
结论:相比于宽依赖,窄依赖对优化更有利
为什么需要划分Stage?
数据本地化
移动计算,而不是移动数据
保证一个Stage内不会发生数据移动
最佳实践:
尽量避免Shuffle
提前部分聚合减少数据移动
RDD持久化
RDD共享变量
RDD分区设计
数据倾斜
val u1 = sc.textFile("file:///root/data/users.txt").cache
u1.collect//删除users.txt,再试试
u1.unpersist()
缓存应用场景:
从文件加载数据之后,因为重新获取文件成本较高
经过较多的算子变换之后,重新计算成本较高
单个非常消耗资源的算子之后
使用注意事项:
cache()或persist()后不能再有其他算子
cache()或persist()遇到Action算子完成后才生效
类似于快照
sc.setCheckpointDir("hdfs:/checkpoint0918")
val rdd=sc.parallelize(List(('a',1), ('a',2), ('b',3), ('c',4)))
rdd.checkpoint
rdd.collect //生成快照
rdd.isCheckpointed
rdd.getCheckpointFile
检查点与缓存的区别
检查点会删除RDD lineage,而缓存不会
SparkContext被销毁后,检查点数据不会被删除
广播变量:允许开发者将一个只读变量(Driver端)缓存到每个节点(Executor)上,而不是每个任务传递一个副本
val broadcastVar=sc.broadcast(Array(1,2,3)) //定义广播变量
broadcastVar.value //访问方式
注意事项:
1、Driver端变量在每个Executor每个Task保存一个变量副本
2、Driver端广播变量在每个Executor只保存一个变量副本
指分区中的数据分配不均匀,数据集中在少数分区中,严重影响性能
通常发生在groupBy,join等之后
解决方案
使用新的Hash值(如对key加盐)重新分区
CSV格式:
//使用SparkContext
val lines = sc.textFile("file:///home/kgc/data/users.csv")
val fields = lines.mapPartitionsWithIndex((idx, iter) => if (idx == 0) iter.drop(1) else iter).map(l => l.split(","))
val fields = lines.filter(l=>l.startsWith("user_id")==false).map(l=>l.split(",")) //移除首行,效果与上一行相同
//使用SparkSession
val df = spark.read.format("csv").option("header", "true").load("file:///home/kgc/data/users.csv")
JSON格式:
//SparkContext
val lines = sc.textFile("file:///home/kgc/data/users.json")
//scala内置的JSON库
import scala.util.parsing.json.JSON
val result=lines.map(l=>JSON.parseFull(l))
//SparkSession
val df = spark.read.format("json").option("header", "true").load("file:///home/kgc/data/users.json")
Catalyst优化器是Spark SQL的核心
Catalyst Optimizer:Catalyst优化器,将逻辑计划转为物理计划
SELECT name FROM
(
SELECT id, name FROM people
) p
WHERE p.id = 1
1、无特殊说明时,下文中“spark”均指SparkSession实例
2、如果是spark-shell下,会自动创建“sc”和“spark”
val spark = SparkSession.builder
.master("master")
.appName("appName")
.getOrCreate()
特定域对象中的强类型集合
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
1、createDataset()的参数可以是:Seq、Array、RDD
2、上面三行代码生成的Dataset分别是:Dataset[Int]、Dataset[(String,Int)]、Dataset[(String,Int,Int)]
3、Dataset=RDD+Schema,所以Dataset与RDD有大部共同的函数,如map、filter等
使用case class创建Dataset
case class Point(label:String,x:Double,y:Double)
case class Category(id:Long,name:String)
val pointsRDD=sc.parallelize(List(("bar",3.0,5.6),("foo",-1.0,3.0)))
val categoriesRDD=sc.parallelize(List((1,"foo"),(2,"bar")))
val points=pointsRDD.map(line=>Point(line._1,line._2,line._3)).toDS
val categories=categories.map(line=>Category(line._1,line._2)).toDS
points.join(categories,points("label")===categories("name")).show
DataFrame=Dataset[Row]
类似传统数据的二维表格
在RDD基础上加入了Schema(数据结构信息)
DataFrame Schema支持嵌套数据类型:struct、map、array
提供更多类似SQL操作的API
/** 将JSON文件转成DataFrame
* people.json内容如下
* {"name":"Michael"}
* {"name":"Andy", "age":30}
* {"name":"Justin", "age":19}
*/
val df = spark.read.json("file:///home/hadoop/data/people.json")
// 使用show方法将DataFrame的内容输出
df.show
//DataFrame API常用操作
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")
spark.sql("SELECT * FROM people").show
//RDD->DataFrame
//方式二:通过编程接口指定Schema
case class Person(name String,age Int)
val people=sc.textFile("file:///home/hadoop/data/people.txt")
// 以字符串的方式定义DataFrame的Schema信息
val schemaString = "name age"
//导入所需要的类
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.{
StructType, StructField, StringType}
// 根据自定义的字符串schema信息产生DataFrame的Schema
val schema = StructType(schemaString.split(" ").map(fieldName =>StructField(fieldName,StringType, true)))
//将RDD转换成Row
val rowRDD = people.map(_.split(",")).map(p => Row(p(0), p(1).trim))
// 将Schema作用到RDD上
val peopleDataFrame = spark.createDataFrame(rowRDD, schema)
// 将DataFrame注册成临时表
peopleDataFrame.registerTempTable("people")
val results = spark.sql("SELECT name FROM people")
results.show
//DataFrame->RDD
/** people.json内容如下
* {"name":"Michael"}
* {"name":"Andy", "age":30}
* {"name":"Justin", "age":19}
*/
val df = spark.read.json("file:///home/hadoop/data/people.json")
//将DF转为RDD
df.rdd.collect
Parquet文件:是一种流行的列式存储格式,以二进制存储,文件中包含数据与元数据
//Spark SQL写parquet文件
import org.apache.spark.sql.types.{
StructType, StructField, StringType,ArrayType,IntegerType}
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)
df.write.parquet("/data/users") //在该目录下生成parquet文件
//Spark SQL读parquet文件
val df=spark.read.parquet("/data/users") //该目录下存在parquet文件
df.show
df.printSchema
Spark SQL与Hive集成:
1、hive-site.xml拷贝至${SPARK_HOME}/conf
下
2、检查hive.metastore.uris
是否正确
3、启动元数据服务:$hive service metastore
4、自行创建SparkSession
,应用配置仓库地址与启用Hive支持
//创建一个Hive表
//hive>create table toronto(full_name string, ssn string, office_address string);
//hive>insert into toronto(full_name, ssn, office_address) values('John S. ', '111-222-333 ', '123 Yonge Street ');
//集成Hive后spark-shell下可直接访问Hive表
val df=spark.table("toronto")
df.printSchema
df.show
val spark = SparkSession.builder()
.config("spark.sql.warehouse.dir", warehouseLocation)
.enableHiveSupport()
.getOrCreate()
val df = spark.sql("select * from toronto")
df.filter($"ssn".startsWith("111")).write.saveAsTable("t1")
$spark-shell --jars /opt/spark/ext_jars/mysql-connector-java-5.1.38.jar
val url = "jdbc:mysql://localhost:3306/metastore"
val tableName = "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)
内置函数的使用:
import org.apache.spark.sql.Row
import org.apache.spark.sql.types.{
IntegerType, StringType, StructField, StructType}
val accessLogRDD = sc.parallelize(accessLog).map(row => {
val splited = row.split(",")
Row(splited(0), splited(1).toInt)
})
val structTypes = StructType(Array(
StructField("day", StringType, true),
StructField("userId", IntegerType, true)
))
//根据数据以及Schema信息生成DataFrame
val accessLogDF = spark.createDataFrame(accessLogRDD, structTypes)
//导入Spark SQL内置的函数
import org.apache.spark.sql.functions._
//求每天所有的访问量(pv)
accessLogDF.groupBy("day").agg(count("userId").as("pv")).select("day", "pv").collect.foreach(println)
//求每天的去重访问量(uv)
accessLogDF.groupBy("day").agg(countDistinct('userId).as("uv")).select("day", "uv").collect.foreach(println)
case class Hobbies(name: String, hobbies: String)
val info = sc.textFile("/data/hobbies.txt")
//需要手动导入一个隐式转换,否则RDD无法转换成DF
import spark.implicits._
val hobbyDF = info.map(_.split("\t")).map(p => Hobbies(p(0), p(1))).toDF
hobbyDF.show
hobbyDF.registerTempTable("hobbies")
//注册自定义函数,注意是匿名函数
spark.udf.register("hobby_num", (s: String) => s.split(',').size)
spark.sql("select name, hobbies, hobby_num(hobbies) as hobby_num from hobbies").show
Java序列化,Spark默认方式
Kryo序列化,比Java序列化快约10倍,但不支持所有可序列化类型
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
//向Kryo注册自定义类型
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]));
使用对象数组、原始类型代替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数量
将大变量广播出去,而不是直接使用
尝试处理本地数据并最小化跨工作节点的数据传输
表连接(join操作)
包含所有表的谓词(predicate)
select * from t1 join t2 on t1.name = t2.full_name
where t1.name = 'mike' and t2.full_name = 'mike'
最大的表放在第一位
广播最小的表
最小化表join的数量
Spark性能优化指南高级篇
为什么需要图计算:
图的术语:
1、对于每条边,矩阵中相应单元格值为1
2、对于每个循环,矩阵中相应单元格值为2,方便在行或列上求得顶点度数
弹性分布式属性图(Resilient Distributed Property Graph):
Graph[VD,ED]
VertexRDD[VD]
EdgeRDD[ED]
EdgeTriplet[VD,ED]
Edge:样例类
VertexId:Long的别名
演示实例1:创建graph
scala> val users = sc.parallelize(Array((3L,("rxin","student")),(7L,("jgonzal","postdoc")),(5L,("franklin","professor")),(2L,("istoica","professor"))))
users: org.apache.spark.rdd.RDD[(Long, (String, String))] = ParallelCollectionRDD[49] at parallelize at <console>:27
scala> val relationship = sc.parallelize(Array(Edge(3L,7L,"Colla"),Edge(5L,3L,"Advisor"),Edge(2L,5L,"Colleague"),Edge(5L,7L,"Pi")))
relationship: org.apache.spark.rdd.RDD[org.apache.spark.graphx.Edge[String]] = ParallelCollectionRDD[50] at parallelize at <console>:27
scala> val graphUser = Graph(users, relationship)
graphUser: org.apache.spark.graphx.Graph[(String, String),String] = org.apache.spark.graphx.impl.GraphImpl@40bfabed
演示实例2:创建graph
scala> val userRdd = sc.makeRDD(
Array(
(1L,("Alice",28)),
(2L,("Bob",27)),
(3L,("Charlie",65)),
(4L,("David",42)),
(5L,("Ed",55)),
(6L,("Fran",50))
)
)
userRdd: org.apache.spark.rdd.RDD[(Long, (String, Int))] = ParallelCollectionRDD[69] at makeRDD at <console>:27
scala> val usercallRdd = sc.makeRDD(
Array(
Edge(2L,1L,7),
Edge(3L,2L,4),
Edge(4L,1L,1),
Edge(2L,4L,2),
Edge(5L,2L,2),
Edge(5L,3L,8),
Edge(3L,6L,3),
Edge(5L,6L,3)
)
)
usercallRdd: org.apache.spark.rdd.RDD[org.apache.spark.graphx.Edge[Int]] = ParallelCollectionRDD[70] at makeRDD at <console>:27
scala> val userCallGraph = Graph(userRdd,usercallRdd)
userCallGraph: org.apache.spark.graphx.Graph[(String, Int),Int] = org.apache.spark.graphx.impl.GraphImpl@db8615d
转载自:SparkGraphX中的pregel函数