Spark学习笔记

一、安装

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转换

               1.map(func)

               2.flatMap(func)

               3.mapPartitions(func)

               4.mapPartitionsWithIndex(func)

               5.simple(withReplacement,fraction,seed)

               6.union(ortherDataset)

               7.intersection(otherDataset)

               8.distinct([numTasks])

               9.cartesian(otherDataset)

               10.coalesce(numPartitionsshuffle)

               11.repartition(numPartition)

               12.glom()

               13.randomSplit(weight:Array[Double],seed)

 

         (2)k-v类型RDD转换

               1.mapValus

               2.flatMapValues

               3.comineByKey

               4.foldByKey

               5.reduceByKey

               6.groupByKey

               7.sortByKey

               8.cogroup

               9.join

               10.LeftOutJoin

               11.RightOutJoin

 

2、action(行动操作)

               1.reduce

               2.collect

               3.count

               4.first

               5.take

               6.top

               7.takeOrdered

               8.countByKey

               9.collectAsMap

               10.lookup

               11.aggregate

               12.fold

               13.saveAsFile

               14.saveAsSequenceFile

 

四、分区(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、具体的执行过程

  1. 构建DAG。
  2. DAGScheduler实现了将依赖链进行分割的操作。先将依赖链划分为多个Stage阶段,每个Stage都是一组相互关联、没有shuffle依赖关系的任务集合,称为TaskSet。DAGScheduler根据分区的个数和窄依赖的个数,确定生成多少个任务,组成TaskSet。
  3. TaskScheduler为每一个TaskSet创建一个TaskSetManager。一方面,TaskScheduler通过底层的SchedulerBackend(调度器的后台进程)与Maste、Worker节点进行通信。另一方面,TaskScheduler将可用的物理资源提供给TaskSetManager,确定每个Task在哪个物理资源上执行;并将计划发给TaskScheduler,由TaskScheduler将Task提交给Spark集群实际执行,并跟踪Task的执行过程。如果失败,则重新提交该Task。
  4. 由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)

  1. 拷贝$HIVE_HOME/conf/hive-site.xml和hive-log4j.properties到 $SPARK_HOME/conf/
  2. 拷贝$HADOOP_HOME/conf/core-site.xml和hdfs-site.xml到 $SPARK_HOME/conf/
  3. 在$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
  4. 进入$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的自定义函数

  1. 在java程序中继承UDF,重写evaluate方法
  2. 再session.sql(“create temporary function funName as ‘com.ncu.chang‘ ”)
  3. 最后再使用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()
  }
}

 

十九、窗口函数(即横跨几个批次的数据来计算)

为了计算某一段时间和前一段时间的差距。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值