spark集群提交任务的命令:
bin/spark-submit --master spark://node-1.XXXXX.com:7077,node-2.xxxx.com:7077
--executor-memory 512mb --total-executor-cores 4
--class com.xxxx.day1.WordCount /root/spark-1.0.jar
hdfs:// node-1.xxxx.com:9000/wc hdfs://node-1.xxx.com:9000/out
Spark一个分布式计算系统,可以替代MR编程模型
下载spark-2.1.1-hadoop2.6
java8 scala2.11.x
安装spark只要安装JDK就可以了。因为scala已经内置进去了
先解压,然后修改两个配置文件conf spark-env.sh slaves
sbin/start-all.sh
start-master.sh -> Master
start-slaves.sh -> 读取slave配置文件,然后通过ssh的方法向slaves中机器发送启动Worker命令
配置高可用的Spark集群
首先安装zk集群
在spark-env.sh中加入一个选项
spark-shell 通过一个交互式的命令行,编写spark应用程序
bin/spark-shell --master spark://node-1.xxx.com:7077,node-2.xxx.com:7077
--executor-memory 5g --total-executor-cores 50
sc.textFile("hdfs://node-1.xxx.com:9000/wc").flatMap(_.split("")).map((_,1)).reduceByKey(_+_)
.sortBy(_._2,false).saveAsTextFile("hdfs://node-1.xxx.com:9000/out")
sortBy传入的只是规则,对原本数据类型没有改变。例如sortBy(x=>x + "",true),按照字典序来排,若原本x属于Int类型,则排序后仍然是Int类型
默认情况下,RDD的分区(partition)跟指定的核数是一样的。
可以使用:
val rdd1 = sc.parallelize(List(1,2,3,4,5,6,7,8,9,10,11,12,13),2)
参数2来指定分区
如果从hdfs读取文件。则RDD分区数量跟文件切片数量有关。HDFS默认文件切片块大小为128M
join操作:
只有key,value类型的数据才会被join
scala>val rdd1 = sc.parallelize(List(("tom",1),("jerry",2),("kitty",3)))
scala>val rdd2 = sc.parallelize(List(("jerry",9),("tom",8),("shuke",7),("tom",2)))
scala>val rdd3 = rdd1.join(rdd2)
scala>rdd3.collect
res0:Array[(String,(Int,Int))] = Array((tom,(1,2)),(tom,(1,8)),(jerry,(2,9)))
scala>val rdd3 = rdd1.leftOuterJoin(rdd2)
scala>rdd3.collect
res1:Array[(String,(Int,Option[Int]))] = Array((kitty,(3,None)),(tom,(1,Some(8))),(tom,(1,Some(2))),(jerry,(2,Some(9))))
Some代表有值,None代表没值
CompactBuffer:是一个集合。——groupByKey后会出现。
=======例子1=================
val rdd2 = sc.parallelize(List("a","b","c","d","e","f"),2)
def func2(index:Int,iter:Iterator[(String)]):Iterator[String] = {
iter.toList.map(x => "[partId": + index + ",val:" + x + "]").iterator
}
rdd2.aggregate("")(_+_,_+_)
rdd2.aggregate("=")(_+_,_+_)
scala>rdd2.mapPartitionsWithIndex(func2).collect
scala>rdd2.aggregate("")(_+_,_+_)
res0:String = abcdef
scala>rdd2.aggregate("")(_+_,_+_)
res1:String = defabc
scala>rdd2.aggregate("|")(_+_,_+_)
res2:String = ||abc|def
========例子2====================
scala>val rdd3 = sc.parallelize(List("12","23","345","4567"),2)
scala>rdd3.aggregate("")((x,y) => math.max(x.length,y.length).toString,(x,y) => x + y)
res3:String = 24 //res3:String = 42
scala>rdd3.aggregate("x")((x,y) => math.max(x.length,y.length).toString,(x,y) => x + y)
res4:String = x24 //res4:String = x42
scala>val rdd4 = sc.parallelize(List("12","23","345",""),2)
scala>rdd4.aggregate("")((x,y) => math.min(x.length,y.length).toString,(x,y) => x + y)
res5:String = 10 //res5:String = 01 ,注意这边的toString
====================================
repartition和coalesce :都是重新分配partition,区别在于repartition有suffer。coalesce无法使得partition数量变大。
====================================
countByKey/countByValue
filterByRange
flatMapValues(_.split(""))
scala>val a = sc.parallelize(List(("a","1,2"),("b","3 4")))
scala>a.flatMapValues(_.split(" ")).collect
res0:Array[(String,String)] = Array((a,1),(a,2),(b,3),(b,4))
===================================
拉链操作 zip
scala>val rdd4 = sc.parallelize(List("dog","cat","gnu","salmon","rabbit","turkey","wolf","bear"))
scala>val rdd5 = sc.parallelize(List("1","2","1","3","1","4","2","1"))
scala>rdd4.zip(rdd5).collect
res0:Array[(String,Int)] = Array((dog,1),(cat,2),(gnu,1),(salmon,3),(rabbit,1),(turkey,4),(wolf,2),(bear,1))
===================================
集合操作
scala>val lst = List(1)
res0:List[Int] = List(1)
scala>val l2 = lst :+ 2
ls:List[Int] = List(1,2)
scala>lst ++l2
res0:List[Int] = List(1,1,2)
==================================
重要算子:combineByKey()
==================================
rdd.toDebugString 查看依赖关系
==================================
自定义分区器
计算每个学科内数量最多的前2个。
val subjects = reduced.map(_._1._1).distinct().collect()
//定义分区器
val subPartitioner: SubjectPartitioner = new SubjectPartitioner(subjects)
//安装自定义分区器的规则shuffle
val partitioned: RDD[(String, (String, Int))] = reduced.map(t => (t._1._1,(t._1._2,t._2))).partitionBy(subPartitioner)
//每个分区中只有一个学科
//拿出来iterator转为List计算,然后再转为iterator
val result: RDD[(String, (String, Int))] = partitioned.mapPartitions(_.toList.sortBy(_._2._2).reverse.take(2).iterator)
//导入spark的partitioner包
class SubjectPartitioner(subjects: Array[String]) extends Partitioner {
//定义分区规则
val rules = new mutable.HashMap[String,Int]()
var i = 0
for(sub <- subjects) {
rules += (sub -> i)
i += 1
}
//定义分区数量
override def numPartitions: Int = subjects.length + 1
//根据传入的key决定该条数据到哪个分区
override def getPartition(key:Any): Int = {
val k = key.toString
rules.getOrElse(k,0)
}
}
Tips:显示类型,有两种方法,一种是Ctrl+alt+v,一种是在变量后加上“.var”,然后按制表符
当rdd从HDFS多次读取相同的数据时,可以调用
rdd.cache()来缓存数据。当内存空间不足时,不会全部存储。
当数据不使用时,使用
rdd.unpersist(true)来释放资源。
rdd.persist(StorageLevel.MEMORY_ONLY_SER)。也是用来缓存数据的方法,而且可以传入StorageLevel参数。
可以指定RDD的存储级别。MEMORY_ONLY_SER表示将数据序列化后再存到内存中。
文件以java对象直接读入内存占用的内存会比原文件大。例如37M会变成116M等等。解决方法是使用压缩算法
或者序列化。
====================
CheckPoint的使用技巧
在hdfs中设置一个目录。用来存放计算中间结果
scala>sc.setCheckpointDir("hdfs://node-1/ck0001")
scala>val rdd1 = sc.textFile("hdfs://node-1/xxx.log")
scala> rdd2 = rdd1.filter(_contains("java"))
scala>rdd2.chekpoint()
scala>rdd2.count //在对应ck0001下生成对应结果。
scala>rdd2.count //直接用ck0001下的数据。
如果一个rdd同时被cache和checkpoint,优先从cache中取数据。
RDD和RDD之间存在着依赖关系,分两种。有shuffle就是宽依赖,没有shuffle就是窄依赖。
DAG 有向无环图
触发Action时才会形成一个完整的DAG
触发Ation任务就要提交到集群执行了。
任务在提交集群之前,要进行一些准备,这些准备工作都是在Driver端
1、构建DAG
2、将DAG切分成1到多个Stage
3、任务执行分阶段进行,先提交前面的Stage,前面的Stage执行完后,后面stage才能继续执行,因为后面的Stage要依赖前面Stage计算的结果
4、一个Stage生成多个Task提交的Executor中,Stage生成的Task的数量跟该阶段RDD的分区数量一致
=========================
创建一个rules,如果在Driver端创建的一个变量,并且在传入到RDD方法的函数中,RDD方法中传入的函数是在Executor的Task中执行的,
Driver会将这个在Driver中定义的变量发送给每一个Task。
解决方案:广播变量
//将Driver端的变量广播到属于自己的所有Executor
val broadcast: Broadcast[Map[String,String]] = sc.broadcast(rules)
//在Executor端拿到广播变量中的值
val r:Map[String,String] = broadcast.value
val nation_name = r(nation_code)
==========================
//利用foreachPartition将数据写入数据库
//传入的是分区
def data2MySQL (part: Iterator[(String,Int)]):Unit = {
//创建一个jdbc连接
val conn: Connection = DriverManager.getConnection("jdbc:mysql://localhost:3306?charactorEncoding=utf-8","root","123456")
val prepareStatement = conn.prepareStatement("INSERT INTO access_log (provinc,count) values (?,?)")
//写入数据
part.foreach(data => {
prepareStatement.setString(1,data._1)
prepareStatement.setInt(2,data._2)
prepareStatement.executeUpdate()
})
prepareStatement.close()
conn.close()
}
=============================
JdbcRDD类的使用:从数据库读取数据到RDD中,然后对数据进行处理。
=============================
多比较条件排序。可以往sortBy()里传入自定义查询类
userRDD.sortBy(t => User(t._1,t._2,t._3,t._4))
自定义查询类举例:
case class User(id:Long, name:String, age:Int, fv:Int) extends Comparable[User] {
override def compareTo(that:User):Int = {
//疑问,为什么这么写
if(this.fv == that.fv) {
this.age - that.age
} else {
that.fv - this.fv
}
}
}
==============================
spark 1.6版本使用spark SQL
scala>val lines = sc.textFile("hdfs://node-1.xxxxxxx.com:9000/person.txt")
scala>case class Person(id:Long,name,String,age:Int,fv:Int)
scala>val userRDD = lines.map(_.split(",")).map(arr => Person(arr(0).toLong),arr(1),arr(2).toInt,arr(3).toInt)
scala>val pdf = userRDD.toDF()
scala>pdf.registerTempTable("t_user")
scala>val sqlContext = new org.apache.spark.sql.SQLContext(sc)
scala>val result = sqlContext.sql("SELECT * FROM t_user order by fv desc,age asc")
scala>result.show()
==============================
如何创建DataFrame对象
//方法一,RDD里面存Person对象(spark2.x之前的方法)
//对数据进行整理并映射成case class(将RDD和case class进行关联)
val personRDD:RDD[Person] = lines.map(lines => {
val fields = line.split("[,]")
val id = fields(0).toLong
val name = fields(1)
val age = fields(2).toInt
val fv = fields(3).toInt
Person(id,name,age,fv)
})
//创建SQLContext
val sqlContext = new SQLContext(sc)
//导入隐式转换
import sqlContext.implicits._1
//将RDD转换成DataFrame
val personDFS = personRDD.toDF()
//将DataFrame注册成临时表
personDF.registerTempTable("t_person")
//执行SQL,sql方法是一个Transformation,不会执行任务
val result:DataFrame = sqlContext.sql("SELECT name,age,fv FROM t_person ORDER BY fv DESC")
//触发action
result.show()
//释放资源
sc.stop()
//方法二,RDD里面存Row,同时需要有个schema与Row进行匹配(spark2.x之前的方法)
val rowRDD:RDD[Row] = lines.map(lines => {
val fields = line.split("[,]")
val id = fields(0).toLong
val name = fields(1)
val age = fields(2).toInt
val fv = fields(3).toInt
Row(id,name,age,fv)
})
//创建SQLContext
val sqlContext = new SQLContext(sc)
//导入隐式转换
import sqlContext.implicits._1
val schema = StructType(
List(
StructField("id",LongType,true),
StructField("name",StringType,true),
StructField("age",IntegerType,true),
StructField("fv",IntegerType,true)
)
)
val pdf:DataFrame = sqlContext.createDataFrame(rowRDD,schema)
//将DataFrame注册成临时表
pdf.registerTempTable("t_person")
//方法三 DataSet
val session = SparkSession
.builder()
.appName("HelloDataSet")
.master("local[*]")
.getOrCreate()
//通过sparkSession获得SparkContext
val lines:RDD[String] = session.sparkContext.textFile("hdfs://xxxxx")
val rowRDD:RDD[Row] = lines.map(lines => {
val fields = line.split("[,]")
val id = fields(0).toLong
val name = fields(1)
val age = fields(2).toInt
val fv = fields(3).toInt
Row(id,name,age,fv)
})
val schema = StructType(
List(
StructField("id",LongType,true),
StructField("name",StringType,true),
StructField("age",IntegerType,true),
StructField("fv",IntegerType,true)
)
)
val pdf:DataFrame = session.createDataFrame(rowRdd,schema)
pdf.createTempView("v_person")
val result:DataFrame = session.sql("select * from v_person where id > 1 or by fv desc")
result.show()
session.close()
========例子=========
val session = SparkSession
.builder()
.appName("HelloDataSet")
.master("local[*]")
.getOrCreate()
//通过sparkSession获得SparkContext
val lines:Dataset[String] = session.read.textFile("hdfs://xxxxx")
//导入session对象中的隐式转换
import session.implicits._
val words:Dataset[String] = lines.flatMap(_.split(""))
//DSL
//为了可以使用agg中的聚合函数,导入spark sql中的函数
import org.apache.spark.sql.functions._
val result:Dataset[row] = words.groupBy($"value" as "word").agg(count(*) as "count").sort($"count" desc)