一、安装
1、上传并解压。
2、修改spark/conf/spark_env.sh:
export JAVA_HOME=/usr/jdk1.8.0_152/
export SPARK_MASTER_IP=hadoop1
expart SPARK_MASTER_PORT=7077
export HADOOP_CONF_DIR=/usr/hadoop-2.7.4/etc/hadoop
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER-Dspark.deploy.zookeeper.url=hadoop1:2181,hadoop3:2181-Dspark.deploy.zookeeper.dir=/usr/Zookeeper"(高可用)
3、修改slaves。
4、将安装包拷贝到其他机器。
5、 (spark on yarn不需要启动)进入到master机器,进入到spark/sbin/,运行./start-all.sh,在其他节点的master机器上启动./start-master.sh (或者./start-master.sh和./start-slaves.sh),运行服务。
6、运行spark/bin/spark-shell,命令是:
./bin/spark-shell --master [ local | spark://192.168.80.12:7077 ]
注意:运行在yarn上为:
$ ./bin/spark-submit --class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode cluster \
--driver-memory 4g \
--executor-memory 2g \
--executor-cores 1 \
--queue thequeue \
examples/jars/spark-examples*.jar \
10
7、运行流程如下:
8、WordCount
9、spark提交任务
打成jar包后,进入到spark/bin,执行:
./spark-submit --master spark://192.168.80.12:7077 /root/MyWordCount.jar(jar包的位置)
二、程序
三、RDD操作
1、transfermation(转化操作)
(1)基本RDD转换
4.mapPartitionsWithIndex(func)
5.simple(withReplacement,fraction,seed)
10.coalesce(numPartitions,shuffle)
13.randomSplit(weight:Array[Double],seed)
(2)k-v类型RDD转换
2、action(行动操作)
四、分区(shuffle操作)
例如reduceByKey,foldByKey,combineByKey都会经过shuffle阶段,即不同key的值进入到同一个分区。
自定义分区(默认是HashPartitioner):可以根据不同规则建立不同分区
1、继承Partitioner并重写numPartitions(分区数量)和getPartioner(其中key为传进来的key值,返回值即为该记录将要分配的分区)。
2、RDD调用partitionBy调用该自定义分区器。
五、RDD缓存
1、RDD.cache和RDD.persist都可以缓存到内存中(或者磁盘中)。
2、sc.setCheckpointDir(“hdfs://”)和RDD.checkpoint()可以设定好把该计算结果保存到hdfs中,可以关机后再次利用该结果。
3、注意:RDD.checkpoint()前必须用RDD.cache才会速度更快,不然会从头计算该结果。
六、程序优化
1、应尽量避免shuffle,例如reduceByKey等----会造成大量的网络传输开销。
2、如果shuffle,可以把一个值分配到不同的分区。
2、对于多次调用的同一RDD(可以看有几次action),可以缓存在内存或磁盘。
3、对于多次调用的数据,如果内存够大可以放在数据库或者内存中
七、宽依赖和窄依赖
1、窄依赖:没有shuffle,即父RDD的一个分区的数据只给子RDD的一个分区(一对一)。
2、宽依赖:有shuffle,即父RDD的一个分区的数据给子RDD的多个分区(一对多)。
特点:
- 窄依赖是指父RDD的每个分区只被子RDD的一个分区所使用,子RDD分区通常对应常数个父RDD分区(O(1),与数据规模无关)
- 相应的,宽依赖是指父RDD的每个分区都可能被多个子RDD分区所使用,子RDD分区通常对应所有的父RDD分区(O(n),与数据规模有关)
八、stage的划分
从末尾开始往前推,一直到数据来源是一个stage,碰到shuffle,从shuffle往前到数据来源也是一个stage。
从一个action开始的执行流程:
DAG有向无环图:
触发Action时才会形成一个完整的DAG。
触发Action任务就要提交到集群执行了。
任务在提交的集群之前,要进行一些准备,这些准备工作都是在Driver端。
1.构建DAG
2.将DAG切分成1到多个Stage
3.任务执行的分阶段执行的,先提交前面的Stage, 前面的Stage执行完后,后面stage才能继续执行,因为后面的Stage要依赖前面Stage计算的结果
4.-个Stage生成多个Task提交的Executor中,Stage生成的Task的数量跟该阶段RDD的分区数量一致
九、spark执行过程
1、Standalone模式
2、Yarn模式
3、具体的执行过程
- 构建DAG。
- DAGScheduler实现了将依赖链进行分割的操作。先将依赖链划分为多个Stage阶段,每个Stage都是一组相互关联、没有shuffle依赖关系的任务集合,称为TaskSet。DAGScheduler根据分区的个数和窄依赖的个数,确定生成多少个任务,组成TaskSet。
- TaskScheduler为每一个TaskSet创建一个TaskSetManager。一方面,TaskScheduler通过底层的SchedulerBackend(调度器的后台进程)与Maste、Worker节点进行通信。另一方面,TaskScheduler将可用的物理资源提供给TaskSetManager,确定每个Task在哪个物理资源上执行;并将计划发给TaskScheduler,由TaskScheduler将Task提交给Spark集群实际执行,并跟踪Task的执行过程。如果失败,则重新提交该Task。
- 由executor实际执行。执行之后,TaskScheduler收到Executor发来的结果及状态后,找到并通知该Task对应的TaskManager。如果TaskSet中的Task全部执行完成,该TaskSetManager自动关闭,并将结果告知DAGScheduler。如果该TaskSetManager对应的Stage是FinalStage,就将结果本身返还给DAGScheduler。如果是中间的Stage,则返回给DAGScheduler的是运算结果在存储模块的相关位置信息,这些存储位置信息作为下一个阶段的Stage的输入数据。
十、全局广播
val broadcastValue=sparkContext.broadcast(T)
val value:T=broadcastValue.value
优点:避免从driver发送数据到所有partition(有全局广播后发送到每个work一个)
在driver端的程序中创建的变量会发送到每个task,会产生大量的网络传输和空间浪费。
注意:如果广播需要的文件是从RDD中读出来的,则需要把该RDD触发action收集到driver端(比如collect)。
十一、SparkSQL基础使用(入门)
def main(args: Array[String]): Unit = {
//获取session
val session: SparkSession = SparkSession.builder().master("local").appName("sparksqlall").getOrCreate()
val originRDD: RDD[String] = session.sparkContext.textFile("/root/people")
//创建ROW格式数据
val ROW: RDD[Row]
= originRDD.map(_.split(",")).map(arr=>Row(arr(0),arr(1),arr(2).toInt))
// 创建schema信息
val structType: StructType = StructType(
List(
StructField("id", StringType, true),
StructField("name", StringType, true),
StructField("age", IntegerType, true)
)
)
//创建DataFrame
val frame: DataFrame = session.createDataFrame(ROW,structType)
//创建临时表
val view: Unit = frame.createTempView("people")
//执行SQL
val frameSql: DataFrame = session.sql("select * from people")
//action操作
frameSql.show()
session.close()
}
十二、SparlSQL常用使用(重要)
def main(args: Array[String]): Unit = {
val session = SparkSession.builder().master("local").appName("sqlTest").getOrCreate()
//获取DataFrame数据信息
val frame1: DataFrame = session.read.csv("")
val frame2: DataFrame = session.read.jdbc("","",new Properties())
val frame3: DataFrame = session.read.json("")
val frame4 = session.read.parquet("") //二进制,储存空间小
val Dataset: Dataset[String] = session.read.textFile("")
val frame5: DataFrame = session.read.format("textFile").option("","").load()//通用,最后一定要load
//DataFrame与Dataset互相转化
val frame:DataFrame=Dataset.toDF("colName")//Dataset2DataFrame
val value: Dataset[Int] = frame1.as[Int] //DataFrame2Dataset
//操作DataFrame方式---SQL
val unit: Unit = frame1.createTempView("myTable")
val frame6: DataFrame = session.sql("select * from myTable")
//操作DataFrame方式---DSL
frame1.select("*").where("name > 20 " )
//将DataFrame保存
frame1.write.mode(SaveMode.Append).csv("")
frame1.write.mode(SaveMode.ErrorIfExists).jdbc("","",new Properties())
frame1.write.mode(SaveMode.Ignore).json("")
frame1.write.mode(SaveMode.Overwrite).parquet("")
frame1.write.mode(SaveMode.Append).text("")
frame1.write.mode(SaveMode.Append).format("jdbc").option("","")
}
注意:如果将该jar连接到数据库并且提交到集群时,需指定--driver-class-path(即jdbc驱动的位置)。
十三、自定义UDF
UDF (1—>1)
UDAF(N—>1)
//注册自定义函数,使其能在SQL语句中调用
val session = SparkSession.builder().master("local").appName("sqlTest").getOrCreate()
session.udf.register("UDFname",(x:Int,y:Int)=>x+y)
十四、自定义UDAF
class MyUDAF extends UserDefinedAggregateFunction{
//输入类型
override def inputSchema: StructType = StructType(List(StructField("inputData",LongType)))
//定义聚合函数的中间结果类型
override def bufferSchema: StructType = StructType(List(StructField("add",LongType),StructField("pow",DoubleType)))
//UDAF的返回值类型
override def dataType: DataType = DoubleType
//不同数据输入顺序结果是否相同(一般为true)
override def deterministic: Boolean = true
//在bufferSchema中定义的中间结果的聚合函数的初始值
override def initialize(buffer: MutableAggregationBuffer): Unit = {
buffer(0)=0L
buffer(1)=1.0
}
//每处理一条数据执行的操作(即bufferSchema中几个聚合函数的数值操作)
//buffer为initialize中定义的初始值或聚合后的值,input为传进来的一条数据
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
buffer(0)=buffer.getAs[Long](0)+1
buffer(1)=buffer.getAs[Double](1)+input.getAs[Double](0)
}
//每个分区进行聚合
override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
buffer1(0)=buffer1.getAs[Long](0)+buffer2.getAs[Long](0)
buffer1(1)=buffer1.getAs[Double](1)+buffer2.getAs[Double](1)
}
override def evaluate(buffer: Row): Any = {
math.pow(buffer.getAs[Double](1),1.toDouble/buffer.getAs[Long](0))
}
}
十五、hive on spark(2.X)
- 拷贝$HIVE_HOME/conf/hive-site.xml和hive-log4j.properties到 $SPARK_HOME/conf/
- 拷贝$HADOOP_HOME/conf/core-site.xml和hdfs-site.xml到 $SPARK_HOME/conf/
- 在$SPARK_HOME/conf/目录中,修改spark-env.sh,添加
export HIVE_HOME=/usr/local/apache-hive-0.13.1-bin
export SPARK_CLASSPATH=$HIVE_HOME/lib/mysql-connector-java-5.1.44-bin.jar:$SPARK_CLASSPATH - 进入$SPARK_HOME/bin
执行 ./spark-sql --master spark://node:7077 --driver-class-path /root/ mysql-connector-java-5.1.44-bin.jar进入spark-sql
5、如果是在idea中写程序,则需要指定
val spark = SparkSession
.builder()
.appName("Spark Hive Example")
.config("spark.sql.warehouse.dir", warehouseLocation)
.enableHiveSupport()
.getOrCreate()
十六、hive on spark的自定义函数
- 在java程序中继承UDF,重写evaluate方法
- 再session.sql(“create temporary function funName as ‘com.ncu.chang‘ ”)
- 最后再使用funName方法
十七、spark-streaming结合kafka(reciver)
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
object SparkStreamingAndKafka {
var updateState=(it:Iterator[(String, Seq[Int], Option[Int])])=>{
it.map(t=>(t._1,t._2.sum+t._3.getOrElse(0)))
}
def main(args: Array[String]): Unit = {
val sparkStreamingConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkStreaming")
val context: StreamingContext = new StreamingContext(sparkStreamingConf,Seconds(5))
context.checkpoint("./streamingCheckPoint")
val zkAddress="192.168.80.12:2181,192.168.80.12:2181" //zookeeper的地址及端口号
val myGroupId="groupId" //消费者的groupId
val topic=Map("wordCount"->2) //topic->线程数
//得到的stream类型为(String, String),第一个为topic,第二个为内容
val stream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(context,zkAddress,myGroupId,topic,StorageLevel.MEMORY_ONLY)
val dStream1: DStream[String] = stream.flatMap(_._2.split(" "))
val dStream2: DStream[(String, Int)] = dStream1.map((_,1))
val sparkContext=new SparkContext(sparkStreamingConf)
val dStream3: DStream[(String, Int)] = dStream2.updateStateByKey(updateState,new HashPartitioner(sparkContext.defaultParallelism),true)
dStream3.foreachRDD(rdd=>rdd.foreachPartition(p=>p.foreach(t=>(t,1))))
context.start() //开始行动
context.awaitTermination() //等待停止
}
}
十八、spark-streaming结合kafka 0.8.X(direct)
import org.apache.spark.{SparkConf, SparkContext}
import kafka.common.TopicAndPartition
import kafka.message.MessageAndMetadata
import kafka.serializer.StringDecoder
import kafka.utils.{ZKGroupTopicDirs, ZkUtils}
import org.I0Itec.zkclient.ZkClient
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka.{HasOffsetRanges, KafkaUtils, OffsetRange}
object DirectStreaming {
def main(args: Array[String]): Unit = {
val zkHost = "192.168.80.12:2181,192.168.80.13:2181"
val brokerList = "192.168.80.12:9092,192.168.80.13:9092"
val zkClient = new ZkClient(zkHost)
val kafkaParams = Map[String, String](
"metadata.broker.list" -> brokerList,
"zookeeper.connect" -> zkHost,
"group.id" -> "testid"
)
var kafkaStream: InputDStream[(String, String)] = null
var offsetRanges = Array[OffsetRange]()
val conf = new SparkConf().setAppName("test").setMaster("local[*]")
val sc = new SparkContext(conf)
val ssc = new StreamingContext(sc, Seconds(5))
val topic = "TEST_TOPIC"
val topicDirs = new ZKGroupTopicDirs("TEST_TOPIC_spark_streaming_testid", topic) //创建一个 ZKGroupTopicDirs 对象,对保存
val children = zkClient.countChildren(s"${topicDirs.consumerOffsetDir}") //查询该路径下是否字节点(默认有字节点为我们自己保存不同 partition 时生成的)
var fromOffsets: Map[TopicAndPartition, Long] = Map() //如果 zookeeper 中有保存 offset,我们会利用这个 offset 作为 kafkaStream 的起始位置
if (children > 0) { //如果保存过 offset,这里更好的做法,还应该和 kafka 上最小的 offset 做对比,不然会报 OutOfRange 的错误
for (i <- 0 until children) {
val partitionOffset = zkClient.readData[String](s"${topicDirs.consumerOffsetDir}/${i}")
val tp = TopicAndPartition(topic, i)
fromOffsets += (tp -> partitionOffset.toLong) //将不同 partition 对应的 offset 增加到 fromOffsets 中
}
val messageHandler = (mmd: MessageAndMetadata[String, String]) => (mmd.topic, mmd.message()) //这个会将 kafka 的消息进行 transform,最终 kafak 的数据都会变成 (topic_name, message) 这样的 tuple
kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder, (String, String)](ssc, kafkaParams, fromOffsets, messageHandler)
}
else {
kafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, Set("TEST_TOPIC")) //如果未保存,根据 kafkaParam 的配置使用最新或者最旧的 offset
}
kafkaStream.transform { rdd =>
offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges //得到该 rdd 对应 kafka 的消息的 offset
rdd
}.map(_._2).foreachRDD(rdd => {
for (o <- offsetRanges) {
val zkPath = s"${topicDirs.consumerOffsetDir}/${o.partition}"
ZkUtils.updatePersistentPath(zkClient, zkPath, o.fromOffset.toString) //将该 partition 的 offset 保存到 zookeeper
}
rdd.foreach(s => println(s))
})
ssc.start()
ssc.awaitTermination()
}
}
十九、窗口函数(即横跨几个批次的数据来计算)
为了计算某一段时间和前一段时间的差距。