spark知识点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WatTekGu-1614914949331)(C:\Users\Thinkpad\AppData\Roaming\Typora\typora-user-images\1614155080812.png)]


sparkcore前面

spark的rdd是核心,用于sparksql(dataframe、dataset),sparkstreaming和机器学习;

spark:

  • 基于内存的快速、通用、可扩展的大数据分析计算引擎
  • 迭代计算不落盘,但是遇到shuffle操作仍然会落盘

特点:(mr切片大小默认和块大小一致,128M==决定maptask;分区数决定reducetask数目,maptask和reducetask都是进程)

  • 快、spark基于内存的运算,快100倍,基于硬盘的也快10倍;实现了DAG执行引擎,通过基于内存来高效处理数据流。计算的中间结果是存在于内存中
  • 易用:支持java、scala、Python的api;
  • 通用:spark可以用于交互式查询sparksql、实时流处理sparkstreaming、机器学习和图计算;
  • 兼容性:spark可以使用yarn和mesos作为资源管理器和调度器

spark运行模式:

本地模式local:

bin/spark-submit \

–class org.apache.spark.examples.SparkPi
–master local[*] \(线程数)
./examples/jars/spark-examples_2.11-2.4.0.jar
10

本质:bin/spark-submit \ + 主类+jar包路径+本地模式+参数

====

  • 获取sparkcontext上下文对象!
  • sc.textFile(dir)将数据封存到rdd里面,即此时数据类型即为rdd,rdd里面也有常见的数据类型
  • 只是封装了思维逻辑而不是真正的数据,需要触发行动算子

standalone模式:

bin/spark-submit \

–class org.apache.spark.examples.SparkPi
–master spark://Centos2:7077
./examples/jars/spark-examples_2.11-2.4.0.jar
10

总结:bin/spark-submit + jar路径 + 主类 + 模式(–master) + 参数

其他一些参数:

bin/spark-submit \

–class org.apache.spark.examples.SparkPi
–master spark://Centos2:7077
–executor-memory 2G
–total-executor-cores 2
./examples/jars/spark-examples_2.11-2.4.0.jar
10

–executor-memory 2G 表示一个executor分配2G内存,默认1G

–total-executor-cores 2 表示总共分配2个CPU

类型:standalone-client和standalone-cluster,主要区别是driver程序的运行节点!!!

客户端模式:

bin/spark-submit \

–class org.apache.spark.examples.SparkPi
–master spark://Centos2:7077
–deploy-mode client \ #指明client模式,另外一个是cluster
./examples/jars/spark-examples_2.11-2.4.0.jar
10

上面代码总结:

  • 用–deploy-mode client来指明运行standalone的client模式

  • 在本地机器提交jar,即spark-submit命令,然后再本地开启了一个driver,然后driver把jar提交到master。让master来控制执行应用

  • 流程:spark-submit命令在本地机器开启driver,driver把jar相关流程交给master进行调度,master在worker节点开启executor运行相关的任务。相对应的executor也需要向driver反向注册,汇报相关进度。executor最终会把结果发送到driver(客户端能够看到结果)

集群模式:

bin/spark-submit \

–class org.apache.spark.examples.SparkPi
–master spark://Centos2:7077
–deploy-mode cluster\ #指明cluster模式,另外一个是client
./examples/jars/spark-examples_2.11-2.4.0.jar
10

上面代码总结:

  • 用–deploy-mode cluster来指明运行standalone的cluster模式
  • 本地机器提交任务,即spark-submit命令,但是此时客户端不会创建driver,把任务发送给master,master就会把请求封装为一个applicationmaster,即driver。会在worker相关 节点开启一个容器中创建applicationmaster。即在集群的某一个节点上创建driver
  • 流程:本地机器提交任务,将请求发送给master,master会把请求转换为一个applicationmaster即driver,会在集群中任一集群的某个节点上开启driver,然后master根据driver的需求,在相关的worker开启executor中运行任务,然后这些executor也需要向driver汇报。
  • 此时的driver不是在本地机器了
  • 查看结果,知道:端口号已经改变,机器也已经改变了。即不是spark:8020了!!!

yarn模式(重点):

需要先停掉standalone的spark模式、停掉zk,如果有高可用的话,也需要停掉。!!!

bin/spark-submit \

–class org.apache.spark.examples.SparkPi
–master yarn
./examples/jars/spark-examples_2.11-2.4.0.jar
10

区别在于–master yarn

两种模式:cluster和client,区别在于driver运行的节点(和standalone的类似)

客户端模式:

bin/spark-submit \

–class org.apache.spark.examples.SparkPi
–master yarn \

–deploy-mode client \ #指明client模式,另外一个是cluster

./examples/jars/spark-examples_2.11-2.4.0.jar
10

流程:

  • 在本地机器用spark-submit提交任务,并在本地机器上开启了driver,driver再提交jar到yarn。

  • resourcemanager进行资源调度,在nodeManager上执行任务,在nodeManager上开启executor,executor向driver反向注册。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Irdn3mm6-1614914949335)(C:\Users\Thinkpad\AppData\Roaming\Typora\typora-user-images\1614169382451.png)]

图理解:本地机器提交spark-submit,,执行spark-submit里面的main方法,通过反射获取类里面的main方法并执行,然后创建driver。driver创建yarnschdulerbackend,该yarnschdulerbackend会向resourcemanager发送指令,要求开启一个applicationmaster。resourcemanager会在一个nodeManager上开启一个容器,并注册applicationmaster。applicationmaster向resourcemanager申请资源。applicationmaster开启发送指令在有资源的nodeManager上开启相对应的executor,并运行任务。executor也会向driver回报


集群模式:

bin/spark-submit \

–class org.apache.spark.examples.SparkPi
–master yarn \

–deploy-mode cluster\ #指明cluster模式,另外一个是client

./examples/jars/spark-examples_2.11-2.4.0.jar
10

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-46M6vf9p-1614914949338)(C:\Users\Thinkpad\AppData\Roaming\Typora\typora-user-images\1614170042297.png)]

图理解:

  • 本地机器提交任务,执行spark-submit里面的main方法,并反射获取client类的main方法并执行(但是不创建driver),然后发送开启applicationmaster的命令给resourcemanager。resourcemanager在有资源的nodeManager上开启容器后开启一个applicationmaster,并且在applicationmaster内部开启driver,然后向resourcemanager申请资源,然后resourcemanager在nodeManager上开启executor,并执行任务。这些executor也会向driver汇报!

集群角色

集群角色:

master和worker(宏观)

master:spark特有的资源调度系统的leader,掌管着集群的资源信息,类似于yarn的resourcemanager

  • 监听worker,看worker是否正常工作
  • worker、application等的管理(1.接收worker的注册并管理所有的worker,2.接收客户端提交的application,3.调度等待的application并向worker提交)

worker:spark特有资源调度系统的slave,掌管着所在节点的资源信息,类似于yarn的nodeManager

  • 通过registerworker反向注册到master
  • 定时发送心跳到master
  • 根据master发送的application配置进程环境,并启动executorbackend(执行task所需的临时进行)

Driver和executor(微观)

driver:

  • 把用户程序转为作业(job)
  • 跟踪executor的运行状况
  • 为执行器节点调度任务
  • ui展示应用运行状况

端口:8080监听spark的master节点的情况,提交一个任务完成后可以到该页面进行查看。

开启了sparkshell,可以到4040端口查看历史记录,但是sparkshell关闭后,4040就无法访问了,而且以前的历史记录也不见了!!!

本集群配置了spark的历史记录配置,记录在hdfs上的spark-logs目录上

yarn历史配置 端口:8088

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HM2bDyzI-1614914949345)(C:\Users\Thinkpad\AppData\Roaming\Typora\typora-user-images\1614170448612.png)]


依赖问题:如果因为网络问题导致依赖下载不完整,到maven的目录下搜索.lastupdate即可,然后删除这些 文件。然后再到idea下载。


代码中获取sparkcontext:

val conf = new sparkconf().setmaster(“local[*]”).setappname(“suibiande mingzi”);

new sparkcontext(conf)即可获取到sc

后面需要关闭资源。sc.stop


RDD

rdd:弹性分布式数据集,是spark的最基本数据抽象,是一个弹性的、可分区的、里面的元素可并行计算的集合

rdd应用了装饰者模式。(一个个走和一起走的区别)

弹性:

  • 存储的弹性:内存与磁盘的自动切换,shuffle
  • 容错的弹性:数据丢失可以自动恢复
  • 计算的弹性:计算出错重试机制
  • 分片的弹性:可根据需要重新分片

调用一个算子会产生一个新的rdd!!!

分布式:

  • 数据存储在大数据集群不同节点上

数据集:

  • rdd封装了计算逻辑,并不保存数据

数据抽象:

  • rdd是抽象类

不可变:

  • rdd封装了计算逻辑,是不可以改变的,想要改变只能产生新的rdd。

可分区、并行计算:

  • 分区并行处理

算子:rdd的方法才是算子。

特性:

  • 一组分区,即是数据集的基本组成单位
  • 一个计算每个分区的函数,在分区里面(即算子链)
  • rdd之间的依赖关系:getdependence(宽依赖,窄依赖)
  • 分区器,即rdd的分片函数:控制分区的数据流向(只针对键值对数据,默认是hash分区)
  • 一个列表,存储每个partition的优先位置。

移动数据不如移动计算,除非资源不够!


  • rdd算子相关的代码在executor执行;rdd算子外的代码在driver 执行,做的是初始化操作;
  • rdd处理的是思维逻辑而不是真实的数据!!!
  • 只有触发到行动算子才能真正触发计算。

分区的概念引出==大数据的数据很大,不可能只给一个节点运行,需要多个节点运行。

  1. 不同分区并行进行工作,互不干扰。即一个分区内的数据就跑自己的算子操作。但是遇到shuffle操作算子的话,就会落盘。(如果不落盘的话,就会可能有多个数据在等待!!!stage阶段分界线)。然后再从磁盘读数据。

编程模式:

在spark中,rdd被表示为对象,通过对象上的方法来对rdd进行转换。rdd经过一系列的transfromation转换定义之后,就可以调用action行动算子触发rdd的计算。

action可以是向应用程序返回结果,或者是向存储系统保存数据。在spark中,只有遇到action,才会执行rdd的计算(即延时计算)


rdd的创建

  • 从集合中创建rdd

    •  //1.集合创建rdd
      //创建集合
      val list: List[Int] = List(1, 2, 3, 4, 5)
      //创建rdd
      val collectionRdd: RDD[Int] = sc.parallelize(list)
      collectionRdd.collect().foreach(println)
      
  • 从外部存储创建rdd

    •      //2.从存储系统创建rdd
          val fileRdd: RDD[String] = sc.textFile("/danci")
          fileRdd.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect()
            .foreach(println)
      
  • 从其他的rdd创建(转换)

分区:

默认分区:

集合创建rdd的分区数为CPU核数或者local[num];

文件创建rdd的分区数为:找到最小分区数local[num]和2比较得到最小的为最小分区数

指定分区:即在创建rdd的时候添加分区数设置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xjgoTN1t-1614914949349)(C:\Users\Thinkpad\AppData\Roaming\Typora\typora-user-images\1614185524023.png)]

文件的:确定分区数(传进去的不是最终的分区数),然后用总字节/分区数获取范围(/r/n也算)

  • 设置分区:在创建rdd的时候直接设置
  • 获取分区数:sc.getNumPartitions即可获取到

转换算子

单value算子

  • map算子

    • 能够转换格式包含数据类型,不改变分区数。

    • 一个个数据为单位进行map运算

    • 弄懂rdd的数据类型

    • //map算子
        val list: List[Int] = List(1, 2, 3, 4)
        val listRdd: RDD[Int] = sc.parallelize(list)
        println(listRdd.getNumPartitions)
        println("=====")
        val newRdd: RDD[Int] = listRdd.map(_ * 2)
        println("分区后的分区数")
        println(newRdd.getNumPartitions)
      
  • mapPartitions算子

    • 以一个分区数据为单位进行mapPartitions算子操作,里面需要再调用map算子

    • 分区数不改变

    • 当内存空间较大的时候用mapPartitions好,可以提高效率

    • //map算子
        val list: List[Int] = List(1, 2, 3, 4)
        val listRdd: RDD[Int] = sc.parallelize(list)
        println(listRdd.getNumPartitions)
        println("=====")
        val newRdd: RDD[Int] = listRdd.map(_ * 2)
        println("分区后的分区数")
        println(newRdd.getNumPartitions)
      
  • mapPartitionsWithIndex算子

    • 以一个分区数据为单位进行mapPartitionwindex操作,然后对数据进行map算子操作

    • 传入的参数为(分区号,对应分区的数据)

    • 下面的例子要用1 to n,如果用list的话,会出错。

    • //mapPartitionsWithIndex算子
      val rdd: RDD[Int] = sc.parallelize(1 to 4, 2)
      val mapPartitionsWithIndexRdd: RDD[(Int, Int)] = rdd.m
        (index, datas) => datas.map((index, _))
      )
      mapPartitionsWithIndexRdd.collect().foreach(println)
      
  • filter算子

    • 里面的函数时判断语句,对集合里面的元素进行筛选

    • contains方法、取模等操作

    • //flatmap算子
      val list: List[Int] = List(1, 2, 3, 4, 5, 6, 8)
      val listRdd: RDD[Int] = sc.parallelize(list)
      val largerthantwo: RDD[Int] = listRdd.filter(_ > 2)
      val oushu: RDD[Int] = listRdd.filter(_ % 2 == 0)
      oushu.collect().foreach(println)
      largerthantwo.collect().foreach(println)
      
  • flatmap算子

    • 对数据进行扁平化处理,里面一般带分隔函数,也可以为自身数据

    • //flatMap算子,对数据进行扁平化处理,后面的函数可以为自身也可以为分隔函数
      val rdd: RDD  [List[Int]] = sc.parallelize(List(List(1, 2, 3), List(8, 9), List(4, 5, 6)))
      rdd.flatMap(datas=>datas).collect().foreach(println)
      
  • glom算子(和flatMap的功能相反)

    • 将分区的数据转换为一组数组,并放置在rdd中

    • //glom算子
      val rdd1: RDD[Int] = sc.parallelize(1 to 4, 2)
      rdd1.mapPartitionsWithIndex(
        (index,datas)=>datas.map((index,_))
      ).collect().foreach(println)
      println("====")
      val maxRdd: RDD[Int] = rdd1.glom().map(_.max)
      println(maxRdd.sum())
      
  • groupby算子

    • 相同规则的进入到同一个迭代器里面

    • 规则:被2整除、以首字母为确认值。。。

    • //groupby算子
      val rdd1: RDD[Int] = sc.parallelize(1 to 4)
      val groupbyRdd: RDD[(Int, Iterable[Int])] = rdd1.groupBy(_ % 2)
      groupbyRdd.collect().foreach(println)
      
    • groupby注意点

      • groupby存在shuffle操作,即将不同的分区数据进行打乱重组
      • 因为存在shuffle过程,所以一定会落盘
  • sample算子

    • 两种情况:

      1. 当第一个参数为false,表示无放回的抽样,则第二个参数表示每个元素的概率,范围为0-1,第三个为随机数生成器种子
      2. 当第一个参数为true,表示有放回抽样,则第二个参数表示每个元素的期望次数,范围必须大于等于0,第三个为随机数生成器种子
    • 功能:从大量的数据中采样

    • //sample算子
      val rdd1: RDD[Int] = sc.parallelize(1 to 10)
      val sampleRdd1: RDD[Int] = rdd1.sample(true, 0.2, 4)
      //上面一行代码中,4好像不是规定产生多少个数!!!
      sampleRdd1.collect().foreach(println)
      val sampleRdd2: RDD[Int] = rdd1.sample(false, 0.2, 2)
      sampleRdd2.collect().foreach(println)
      
  • distinct算子

    • 去重的作用

    • 存在shuffle操作

    • //distinct算子
      val list: List[Int] = List(1, 2, 3, 4, 3, 2, 4)
      val rdd1: RDD[Int] = sc.parallelize(list)
      rdd1.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      println("===")
      val rdd2: RDD[Int] = rdd1.distinct()
      rdd2.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      rdd2.collect().foreach(println)
      
  • coalesce算子

    • 重新分区,但是不涉及shuffle操作,一般用于减少分区

    • 但是coalesce算子也可以设置存在shuffle操作

    • //coalesce算子
      val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
      val rdd1: RDD[Int] = sc.parallelize(list, 4)
      rdd1.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      println("========")
      val coalesceRdd: RDD[Int] = rdd1.coalesce(2)
      coalesceRdd.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      
  • reparation算子

    • 存在shuffle过程,底层是coalesce

    • 一般用于增大分区

    • //reparation算子重新分区,存在shuffle过程,底层是coalesce算子,一般用于增大分区
      val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
      val rdd1: RDD[Int] = sc.parallelize(list, 2)
      rdd1.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      println("========")
      val coalesceRdd: RDD[Int] = rdd1.repartition(4)
      coalesceRdd.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      
  • sortBy算子

    • 按照规则排序

    • 存在shuffle过程

    • //sortBy算子
      val list: List[Int] = List(3, 4, 2, 1, 6, 8, 7, 5)
      val rdd1: RDD[Int] = sc.parallelize(list)
      val rdd2: RDD[Int] = rdd1.sortBy(x => (-x))
      rdd2.collect().foreach(println)
      rdd1.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      println("===")
      rdd2.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      
  • pipe算子

    • 调用脚本的算子,作用于每一个分区

双value类型

  • union并集

  • substract差集

    • rdd的位置不同,结果不同
  • intersection交集

    • //union并集、subtract差集、intersect交集
          val list1: List[Int] = List(1, 2, 3)
          val list2: List[Int] = List(3, 4, 5)
          val rdd1: RDD[Int] = sc.parallelize(list1)
          val rdd2: RDD[Int] = sc.parallelize(list2)
          rdd1.union(rdd2).collect().foreach(println)
          println("===")
          rdd1.intersection(rdd2).collect().foreach(println)
          println("===")
          rdd1.subtract(rdd2).collect().foreach(println)
      
  • zip拉链

    • 注意点

      • 需要两个rdd的分区数相等
      • 两个rdd的每个对应分区数据元素相等
    • 生成的是键值对数据,元组数据

    • //zip拉链
      val list1: List[Int] = List(1, 2, 3, 4)
      val list2: List[Int] = List(2, 3, 4, 5)
      val rdd1: RDD[Int] = sc.parallelize(list1, 2)
      val rdd2: RDD[Int] = sc.parallelize(list1, 4)
      val rdd3: RDD[Int] = sc.parallelize(list2, 2)
      rdd1.zip(rdd3).collect().foreach(println)
      println("===")
      //会报错,需要分区数一致
      rdd2.zip(rdd3).collect().foreach(println)
      

key-value类型

  • partitionBy算子:按照key重新分区

    • 存在shuffle操作

    • 可以自定义分区器,默认的是hash分区器

    • //partitionBy,按照key重新分区
      val rdd1: RDD[(Int, String)] = sc.parallelize(Array((1, "aaa"), (2, "bbb"), (3, "ccc")),3)
      rdd1.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      val rdd2: RDD[(Int, String)] = rdd1.partitionBy(new HashPartitioner(2))
      rdd2.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      
  • 自定义分区器

    • 设置分区数
    • 重写getpartition方法,具体的分区逻辑
  • reducebykey算子

    • 将rdd按照key来对value进行聚合操作

    • 先落盘,然后采集相同key的数据到一块,然后逐项累加(下面例子)

    • //reducebykey算子
      val rdd: RDD[(String, Int)] = sc.parallelize(Array(("aaa", 2), ("bbb", 2), ("bbb", 3), ("aaa", 4), ("bbb", 5), ("aaa", 6), ("ccc", 7)))
      val reducebykeyRdd: RDD[(String, Int)] = rdd.reduceByKey(_ + _)
      reducebykeyRdd.collect().foreach(println)
      
  • groupByKey算子

    • 对key进行操作,但是只生成一个seq,不进行聚合操作

    • 存在shuffle操作

    • 得到的结果是一个元组

    • //groupByKey算子
      val rdd: RDD[(String, Int)] = sc.parallelize(Array(("aaa", 2), ("bbb", 2), ("bbb", 3), ("aaa", 4), ("bbb", 5), ("aaa", 6), ("ccc", 7)))
      rdd.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      println("===")
      val groupbykeyRdd: RDD[(String, Iterable[Int])] = rdd.groupByKey()
      groupbykeyRdd.mapPartitionsWithIndex(
        (index,datas)=>(datas.map((index,_)))
      ).collect().foreach(println)
      groupbykeyRdd.map {//得到的是一个元组,然后让第二个元素进行累加操作
        tuple =>(tuple._1,tuple._2.sum)
      }.collect().foreach(println)
      

reducebykey和groupByKey的区别:

  • reducebykey有聚合操作,而groupByKey没有聚合操作

  • reducebykey相当于hadoop中的预聚合操作,如果不影响业务的话,用reducebykey算子会好于groupByKey

  • aggregatebykey算子

    • 该算子的分区内和分区间的逻辑不同,第一个参数括号为赋予初始值,第二个参数括号为分区内和分区间的逻辑体现

    • //aggregateBykey算子,要括号赋予初始值,然后另一个括号进行封装分区内和分区间的不同思维
      val list: List[(String, Int)] = List(("a", 2), ("b", 3), ("c", 4), ("a", 5), ("b", 6), ("c", 7), ("a", 8))
      val newRdd: RDD[(String, Int)] = sc.parallelize(list)
      val aggregateBykey: RDD[(String, Int)] = newRdd.aggregateByKey(0)(math.max(_, _), _ + _)
      
    • 流程:所有的分区都初始化一个key的value为0,然后将相同的key的value不断比较,取出最大值;最后不同分区间的最大值求和

  • foldBykey算子

    • 是aggregatebykey的特例,即分区内和分区间的思维一致

    • 第一个参数括号为赋予初始值,第二个参数括号为分区内和分区间的逻辑体现

    • //foldbykey算子:和aggregatebykey类似,是其特殊例子,即分区内和分区间的逻辑思维一致
      val list: List[(String, Int)] = List(("a", 2), ("b", 3), ("c", 4), ("a", 5), ("b", 6), ("c", 7), ("a", 8))
      val rdd: RDD[(String, Int)] = sc.parallelize(list)
      val result: RDD[(String, Int)] = rdd.foldByKey(0)(_ + _)
      result.collect().foreach(println)
      
  • combineByKey算子

    • 共有三个参数

      • 第一个,针对key的value进行转换,这里转换为(value,1)的形式
      • 第二个,分区内的逻辑体现
        • 先由一个经历了转换,然后和相同key的value进行操作(但是一个是(value,1)形式,一个是value形式)==>需要**指明泛型,两个都要指明泛型,**然后函数内部为你自身的思维体现
      • 第三个,分区间的逻辑体现
        • 两个参数都为(value,count)的形式,此时仍然需要指明泛型,然后函数内部为分区间的逻辑思维体现
    • //combineByKey算子:
      val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
      val rdd1: RDD[(String, Int)] = sc.parallelize(list,2)
      val rdd2: RDD[(String, (Int, Int))] = rdd1.combineByKey(
        (_, 1),
        (acc: (Int, Int), v: Int) => (acc._1 + v, acc._2 + 1),
        (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
      )
      rdd2.collect().foreach(println)
      println("===")
      val res: RDD[(String, Double)] = rdd2.map {
        case (key, value) => {
          (key, value._1 / value._2.toDouble)
        }
      }
      res.collect().foreach(println)
      
  • sortbykey算子

    • 按照key来进行排序,也可以用sortBy算子来实现

    • //sortbykey算子
      val list: List[(String, Int)] = List(("a", 88), ("d", 95), ("e", 91), ("c", 93), ("f", 95), ("b", 98))
      val rdd1: RDD[(String, Int)] = sc.parallelize(list)
      rdd1.sortByKey().collect().foreach(println)
      
  • mapValues算子

    • 只对value操作,返回结果还是原来的数据类型,key不变,value改变了

    • //mapValues算子:只对v进行操作
      val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "a"), (1, "d"), (2, "b"), (3, "c")))
      rdd.mapValues(_+"++++").collect().foreach(println)
      
  • join算子

    • 将相同的key对应的多个value关联在一起(不同rdd)

    • 返回的rdd类型为(key,(value1,value2))

    • //join算子,对相同key的多个value关联在一起
      val rdd1: RDD[(Int, String)] = sc.parallelize(Array((1, "a"), (2, "b"), (3, "c")))
      val rdd2: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (4, 6)))
      val res: RDD[(Int, (String, Int))] = rdd1.join(rdd2)
      val rdd3: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (4, 6)))
      val res: RDD[(Int, ((String, Int), Int))] = rdd1.join(rdd2).join(rdd3)
      res.collect().foreach(println)
      
  • cogroup算子:类似于全连接,但是在同一个rdd中对key聚合

    • 每个rdd中相同key中的元素分别聚合成一个集合

    • 先rdd内部聚合,然后rdd键聚合(即两次聚合)

    • //cogroup算子:先rdd内对相同key进行聚合,然后rdd间对相同key聚合
      val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1,"a"),(2,"b"),(3,"c"),(3,"d")))
      val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1,4),(2,5),(3,6)))
      val result: RDD[(Int, (Iterable[String], Iterable[Int]))] = rdd.cogroup(rdd1)
      result.collect().foreach(println)
      

行动算子

  • reduce算子聚合

    • 先聚集rdd中的所有元素,先聚合分区内数据,再聚合分区间数据

    • //reduce算子
      val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 4, 4, 7, 9, 11, 23, 45, 11))
      val res: Int = rdd1.reduce(_ + _)
      println(res)
      
  • collect算子:以数组的形式返回数据集

    • 在驱动程序中,以数组的形式返回数据集的所有元素
    • 所有的数据都会从executor拉取到driver端
    • 1614237243749
  • count算子:返回rdd中的元素个数

    • //count算子
      val rdd1: RDD[Int] = sc.parallelize(List(1, 2, 4, 4, 7, 9, 11, 23, 45, 11))
      println(rdd1.count())
      
  • first算子:返回rdd中的第一个元素

    • //first算子
      val rdd1: RDD[Int] = sc.parallelize(List( 11, 23, 45, 11))
      println(rdd1.first())
      
  • take算子:返回有rdd前n个元素组成的数组

    • 数组可以用mkString方法来拆分数组元素,并展示出来

    • //take算子
      val rdd1: RDD[Int] = sc.parallelize(List( 11, 23, 45, 11))
      println(rdd1.take(10).mkString("|"))
      
  • takeordered算子

    • 返回排序后,rdd的前n个元素的数组

    • 先排序,后取元素

    • //takeordered算子:返回排序后的前n个元素组成的数组
      val list: List[Int] = List(1, 3, 5, 4, 2, 8, 7, 6)
      val rdd1: RDD[Int] = sc.parallelize(list)
      println(rdd1.takeOrdered(5).mkString("|"))
      
  • aggregate算子

    • 对rdd分区内的元素通过分区内逻辑和初始值聚合,然后用分区间逻辑和初始值进行操作

    • 注意点

      • 分区内逻辑再次使用初始值和aggregatebykey是有区别的
      • 分区内逻辑和初始值聚合的时候,每个分区都会和初始值聚合,但是到了分区间和初始值聚合的时候,整个rdd作为整体和初始值聚合
    • //aggregate算子
      val list: List[Int] = List(1, 2, 3, 4)
      val rdd1: RDD[Int] = sc.parallelize(list, 4)
      println(rdd1.reduce(_ + _))
      println("===")
      val res: Int = rdd1.aggregate(10)(_ + _, _ + _)
      println(res)
      
  • fold算子

    • 是aggregate算子的特例,即分区内和分区间的逻辑一致,其余都一致!

    • //fold算子
      val list: List[Int] = List(1, 2, 3, 4)
      val rdd1: RDD[Int] = sc.parallelize(list,4)
      println(rdd1.fold(10)(_ + _))
      println("===")
      println(rdd1.reduce(_+_))
      
  • countbykey算子

    • 统计每种key的个数

    • 返回的结果是一个hashmap格式

    • //countByKey算子
      val array: Array[(Int, String)] = Array((1, "hello"), (2, "meiyou"), (2, "jiayou"), (3, "success"), (1, "goodjob"))
      val rdd1: RDD[(Int, String)] = sc.parallelize(array)
      val res1: collection.Map[Int, Long] = rdd1.countByKey()
      val res2: collection.Map[(Int, String), Long] = rdd1.countByValue()
      println(res1)
      println(res2)
      
  • save的相关算子

    1. saveAstextfile算子(还有saveasObjectFile算子)

      • 将数据集的元素以textfile的形式保存到hfs文件系统或者其他文件系统。对于每个元素,spark将会调用toString方法,将它转换为文件中的文本

      • //saveAstextfile算子
        val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
        val rdd1: RDD[Int] = sc.parallelize(list, 2)
        rdd1.saveAsTextFile("/saveAsTextfile")
        
  • foreach算子:遍历rdd中每一个元素

    • 收集起来在一一打印和分布式一一打印的区别(下面例子)

    • 分布式打印的时候,整个分区数据的顺序不同于收集起来再打印

    • //foreach算子
      val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
      val rdd1: RDD[Int] = sc.parallelize(list, 2)
      val rdd2: RDD[Int] = rdd1.map(x => x)
      //先收集起来,再打印
      rdd2.collect().foreach(println)
      println("===")
      //分布式打印
      rdd1.foreach(println)
      

rdd序列化

  • 初始化工作是在driver端进行的,而实际运行程序是在executor端进行的。这就涉及到了跨进程通信,是需要序列化的
  • driver:算子以外的代码都是在driver端进行的
  • executor:算子里面的代码都是在executor端进行的
  • Kryo序列化框架
    • java能够序列化任何的类,但是比较笨重,序列化对象的提交也比较大
    • kryo是由spark2.0开始支持的序列化机制
    • kryo特点:
      • 速度快,是java序列化的10倍
      • 当rdd在shuffle的时候,简单数据类型、数组和字符串类型已经在spark内部使用kryo序列化了
    • 即使使用kryo序列化,也需要继承Serializable接口,然后再主方法内替换默认的序列化机制

rdd依赖关系

rdd血缘关系

  • rdd只支持粗粒度转换,即在大量记录上执行的单个操作。将创建rdd的一系列Linerage(血缘)记录下来,以便恢复丢失的分区
  • 血缘会记录rdd的元数据信息和转换行为。当rdd的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区
  • 查看血缘关系:通过toDebugString方法,查看Rdd血缘关系(返回前面所有的新的rdd)
  • 血缘关系返回的结果中,圆括号的数字表示rdd的并行度,即分区数

依赖关系

  • 查看依赖关系:通过dependencies方法,查看rdd的依赖关系
  • 只会返回上级的rdd

依赖关系类型:

  • 窄依赖
    • 表示每一个父rdd的partition最多被子rdd的一个partition使用。窄依赖类似于独生子女
  • 宽依赖
    • 表示同一个父rdd的partition被多个子rdd的partition依赖,会引起shuffle操作。宽依赖比喻为超生
    • 具有宽依赖的转换算子有:sortBy,reducebykey、groupByKey、join和repartion函数
    • 宽依赖对spark去评估一个转换有更加重要的影响。比如对性能的影响

spark中的job调度

  • 一个spark应用包含一个驱动进程(driver进程,在这个进程写spark的逻辑代码)和多个执行器进程(executor进程,跨越集群中多个节点。)spark程序自己是运行在驱动节点,然后发送指令到执行器节点
  • spark集群中可以同时运行多个spark应用,应用由集群管理器来调度

spark应用(sparkcontext对应着application应用)

  • 一个spark应用可以包含多个spark job,spark job是在驱动程序中由sparkcontext来定义的(一个sparkcontext对应着一个spark应用)
  • 启动sparkcontext时,就开启了一个spark应用。一个驱动程序被启动了,多个执行器在集群中的多个工作节点也被启动了。一个执行器就是一个jvm,一个执行器不能跨越多个节点,但是一个节点可以包含多个执行器
  • 一个rdd会跨多个执行器被并行计算,每个执行器可以有这个rdd的过个分区,但是一个分区不能跨越多个执行器

spark job的划分(行动算子对应job)总结

  • spark是懒执行的,在驱动程序调用一个action之前,spark应用是不会做任何事情的,针对每个action,spark调度器就创建一个执行图(execution graph)和启动一个spark job
  • 每个job由多个stages组成,这些stages就是实现最终rdd所需的数据转换的步骤。一个宽依赖划分一个stage每个stage由多个tasks来组成这些tasks就表示每个并行计算(分区的含义),并且会在多个执行器上执行。
    • 上面的总结:
      • 一个sparkcontext对应着一个spark应用
      • 行动算子对应着一个job
      • 宽依赖对应stage
      • 宽依赖操作最后一个rdd的分区数即为task数。分区数对应着tasks

Stage任务划分(面试重点)

  • DAG有向无环图
    • DAG有向无环图是由点和线组成的拓扑图形,具有方向,但不会闭环。
    • 原始的rdd通过一系列转换就形成了DAG,根据rdd之间的依赖关系的不同将DAG划分成不同的stage。对于窄依赖,partition的转换处理在stage中完成任务。对于宽依赖,由于存在shuffle,只能在parent rdd(父rdd)处理完后,才能开始接下来的计算,因此宽依赖是划分stage的依据。
  • rdd任务切分中间分为:application、job、stage和task
    • application:初始化一个sparkcontext即生成一个application
    • job:一个action算子就会生成一个job
    • stage:stage等于宽依赖的个数加1
    • task:一个stage阶段中,最后一个rdd的分区个数就是task的个数

RDD持久化(cache、persist、shuffle操作的算子、检查点)

rdd cache、persist缓存

  • rdd通过cache或者persist方法将前面的计算结果缓存,默认情况下会把数据以序列化的形式缓存在jvm的堆内存中

  • 注意点

    • 这两个方法cache和persist并不是被调用时就立即缓存而是触发后面的action时,该rdd才会被缓存在计算节点的内存中。并供后面重用。
    • cache操作会增加血缘关系,不改变原有的血缘关系
    • cache底层是persist,persist可以设置缓存级别和份数(内存,磁盘)
  • 缓存有可能丢失,或者存储与内存的数据由于内存不足而被删除,rdd的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于rdd的一系列转换,丢失数据会被重算,由于rdd的各个partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部的partition

  • 自带缓存算子

    • spark会自动对一些shuffle操作的中间数据做持久化操作(reducebykey,groupByKey)。这样做的目的是为了当一个节点shuffle失败了,可以避免重新计算整个输入。但是在实际情况下,想重用数据,仍然建议调用cache或persist

rdd checkpoint检查点:检查点是对 缓存点的进一步保障

  • 检查点:是通过将rdd中间结果写入hdfs

  • 为什么要做检查点:

    • 由于血缘依赖过长,会导致容错成本过高,这样就还不如在中间阶段做检查点容错,如果检查点之后有节点出问题,可以从检查点开始重做血缘,减少了开销
  • 检查点存储路径:checkpoint的数据通常是存储在hdfs等容错、高可用的文件系统

  • 检查点数据存储格式为:二进制文件

  • 检查点会切断血缘:在checkpoint过程中,该rdd的所有依赖于父rdd中的信息将全部被移除

  • 检查点触发时间:对rdd进行checkpoint操作并不会马上被执行,必须执行action操作才会触发执行。但是,检查点为了数据安全,会从血缘关系的最开始执行一遍

  • 设置检查点步骤:

    • 设置检查点的数据存储路径:sc.setCheckPointDir(“hdfs路径”)
    • 调用检查点方法:rdd.checkpoint()
  • 检查点和cache经常会一起使用!!!

  • 例子

    • //需要访问集群的用户名,否则无权限访问
      System.setProperty("HADOOP_USER_NAME","root")
      val conf: SparkConf = new SparkConf().setAppName("checkpoint").setMaster("local[*]")
      val sc: SparkContext = new SparkContext(conf)
      //设置检查点数据的存储路径
      sc.setCheckpointDir("/checkPoint")
      val rdd: RDD[String] = sc.textFile("/saveAsTextfile")
      val rdd1: RDD[String] = rdd.flatMap(_.split(" "))
      val word_timeRdd: RDD[(String, Long)] = rdd1.map {
        word => {
          (word, System.currentTimeMillis())
        }
      }
      //设置缓存持久化
      word_timeRdd.cache()
      //设置检查点持久化
      word_timeRdd.checkpoint()
      //触发上面两个持久化操作
      word_timeRdd.collect().foreach(println)
      //再次触发执行逻辑
      word_timeRdd.collect().foreach(println)
      //每触发一次action算子,都会执行一次
      word_timeRdd.collect().foreach(println)
      //为了能够在web UI看效果,故意睡眠程序
      Thread.sleep(100000)
      sc.stop()
      
缓存和检查点的区别
  • cache缓存只是将数据保存起来,不切断血缘关系。而checkpoint检查点切断血缘关系
  • cache缓存的数据通常存储在磁盘、内存等地方,可靠性较低;checkpoint的数据通常存储在hdfs等容错性高、高可用的文件系统,可靠性高。
  • 建议对checkpoint的rdd使用缓存,这样即使checkpoint的job只需要从cache缓存中读取数据即可,否则需要再从头计算一次rdd(先cache后做检查点)
  • 如果使用完了缓存,可以通过unpersist方法释放缓存
  • persist与checkpoint的区别:
    rdd.persist(StorageLevel.DISK_ONLY) 与 checkpoint 也有区别。前者虽然可以将 RDD 的 partition 持久化到磁盘,但该 partition 由 blockManager 管理。一旦 driver program 执行结束,也就是 executor 所在进程 CoarseGrainedExecutorBackend stop,blockManager 也会 stop,被 cache 到磁盘上的 RDD 也会被清空(整个 blockManager 使用的 local 文件夹被删除)。而 checkpoint 将 RDD 持久化到 HDFS 或本地文件夹,如果不被手动 remove 掉,是一直存在的,也就是说可以被下一个 driver program 使用,而 cached RDD 不能被其他 dirver program 使用。

键值对rdd数据分区

  • spark支持hash分区和range分区和自定义分区,hash分区为当前默认分区
  • 分区器直接决定了rdd中分区器的个数、rdd中每条数据经过shuffle后进入哪个分区和 reduce的个数
  • 注意点:
    • 只有键值对类型的rdd才有分区器,其他的类型rdd的分区器为none
    • 每个rdd的分区id范围为0-(numpartitions-1),决定这个值是属于哪个分区的
  • 获取rdd的分区器:rdd.partitioner
  • 使用hashPartitioner对rdd重新分区:rdd.partitionBy(new HashPartitioner(2))
  • range范围分区:
    • 对数据的key进行排序
    • 抽样得到边界,划分为一定数量的区间
    • 将数据放入合适的分区
  • 自定义分区
    • 设置分区数量
    • 重写partitioner方法,封装分区的逻辑思维

数据读取与保存

  • text文件

    • 数据读取:sc.textfile(dir)
    • 数据保存:rdd.saveAsTextFile(dir)
  • json文件(伪json文件)

    • 先通过将json文件当做文本文件text来读取,然后利用相关的json库对每条数据进行json解析

    • 处理json文件一般是用sparksql操作来读取的!!!

    • //读取Json输入文件
      val jsonRDD: RDD[String] = sc.textFile("input/user.json")
      
      //导入解析Json所需的包并解析Json
      import scala.util.parsing.json.JSON
      val resultRDD: RDD[Option[Any]] = jsonRDD.map(JSON.parseFull)
      
  • sequence文件

    • sequencefile文件是hadoop采用存储二进制形式键值对而设计的一种平面文件。

    • sc.sequencefile[keyClass,valueClass](dir)来读取sequencefile文件(中括号的class即为数据类型)

    • 只针对键值对类型

    • // 保存数据为SequenceFile
      dataRDD.saveAsSequenceFile("output")
      // 读取SequenceFile文件
      sc.sequenceFile[Int,Int]("output").collect().foreach(println)
      
  • object对象文件

    • 对象文件是将对象序列化后保存的文件,采用java的序列化机制。
    • 读取object对象文件:
      • 通过用sc.objectFile[k,v](path)函数接收一个路径,读取对象文件,返回相对应的rdd
    • 保存object对象文件:
      • 通过调用saveasobjectfile()实现对对象文件的输出。因为序列化,所以要指定类型

spark对mysql的操作

  • 对mysql读操作

    • //3.定义连接mysql的参数
         val driver = "com.mysql.jdbc.Driver"
         val url = "jdbc:mysql://hadoop102:3306/rdd"
         val userName = "root"
         val passWd = "000000"
      
         //创建JdbcRDD
         val rdd = new JdbcRDD(sc, () => {
             //反射类
           Class.forName(driver)
             //拿到权柄
           DriverManager.getConnection(url, userName, passWd)
         },
           "select * from `rddtable` where `id`>=? and `id`<=?;",
           1,
           10,
           1,
           r => (r.getInt(1), r.getString(2))
         )
      
  • 对mysql写操作(不用foreach,用foreachPartition操作,减少连接次数)

    •     //数据库连接4要素
          val driver = "com.mysql.jdbc.Driver"
          val url = "jdbc:mysql://hadoop202:3306/test"
          val userName = "root"
          val passWd = "123456"
      
          val rdd: RDD[(String, Int)] = 		 				sc.makeRDD(List(("qiaofeng",18),("duanyu",20),("xuzhu",21)))
      
      	rdd.foreachPartition{
            datas=>{
              //注册驱动
              Class.forName(driver)
              //获取连接
              val conn: Connection = DriverManager.getConnection(url,userName,passWd)
              //执行的sql
              var sql:String = "insert into user(name,age) values(?,?)"
              //获取数据库操作对象
              val ps: PreparedStatement = conn.prepareStatement(sql)
              datas.foreach{
                case (name,age)=>{
                  //给参数赋值
                  ps.setString(1,name)
                  ps.setInt(2,age)
                  //执行sql语句
                  ps.executeUpdate()
                }
              }
      

累加器

  • 累加器:分布式共享只写变量。(task和task之间不能读数据)

  • 累加器用来对信息进行聚合,通常在向spark传递参数时,比如使用map函数或则filter传条件时,可以使用驱动器程序中定义的变量。但是集群中运行的每个任务都会得到这些变量的一份新的副本更新这些副本的值是不会影响到驱动器的对应变量的。而累加器能够实现我们想要的效果

  • 如果是副本变量的操作,仅仅是对区间内部进行的,分区间是无法共享的!!!而且driver端的变量时固定不变的(如果不用累加器)

  • 累加器代替变量sum的作用

  • 累加器也是懒加载,如果不在action算子中操作,其值也为0(初始值)

  • val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a", 1), ("a", 2), ("a", 3), ("a", 4)))
    val reducebykeyRdd: RDD[(String, Int)] = rdd1.reduceByKey(_ + _)
    reducebykeyRdd.collect().foreach(println)
    println("===")
    //演示副本变量的改变与driver端的变量无关
    //说明executor获得的副本变量是无法改变driver的对应变量
    var sum =0
    rdd1.foreach{
      case (string,count)=>{
        sum = sum + count
        println("sum="+sum)//返回结果是四个结果,即分区内逐一累加,分区间不累加
      }
    }
    println("===")
    println("a",sum)
    println("===")
    //声明累加器
    val accumulator: LongAccumulator = sc.longAccumulator("sum")
    rdd1.foreach{
      case (string,count) =>{
          //使用累加器进行加,即累加器相当于sum
        accumulator.add(count)
      }
    }
    //获取累加器的值
    println(accumulator.value)
    
  • 驱动器调用sc.accumulator(initialvalue)方法可给累加器设置初始值

  • 注意点

    • 工作节点上的任务不能相互访问累加器的值。累加器只是一个只写变量
    • 对于要在行动操作中使用的累加器,spark只会把每个任务对各累加器的修改应用一次。因此,如果想要一个无论在失败还是重复计算时都绝对可靠的累加器,必须要把累加器放在类似于foreach这样的行动算子中。
    • 如果累加器在非行动算子中,则累加器可能会发生不止一次更新
  • 自定义累加器

    • 继承AccumulatorV2,设定输入和输出的泛型
    • 重写方法(6个方法)
      • 定义输出数据的集合(对应着输出的泛型)
      • 定义初始化状态(初始值)
      • 复制累加器
      • 重置累加器
      • 增加数据(累加器的作用)
      • 合并累加器
      • 获取累加器的值
  • 使用自定义累加器需要注册一下自定义的累加器


广播变量(只发送一次到一个节点)

  • 广播变量:分布式共享只读变量

  • 在多个并行操作中executor使用同一个变量,spark默认会为每个任务task分别发送;这样如果共享比较大的对象,就会占用很大工作节点的内存

  • 广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个spark操作使用。(例如机器学习算法中的一个很大的特征向量,广播变量很适合使用)

  • 使用广播变量

    • 通过对一个类型T的对象调用Sparkcontext.broadcast创建一个Broadcast[T]对象,任何可序列化的类型都可以这么实现
    • 通过value属性访问该对象的值
    • 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)
  • 其实广播变量就是一个对象,这里例子的广播变量即为list,即将list封装为广播变量而已。

  • val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a", 1), ("b", 2), ("c", 3)))
    val rdd2: RDD[(String, Int)] = sc.parallelize(List(("a", 4), ("b", 6), ("c", 6)))
    //采用rdd的方式实现:rdd1.join(rdd2),用到shuffle,性能较低
    rdd1.join(rdd2).collect().foreach(println)
    println("===")
    //采用集合的方式,实现rdd和list的join
    val rdd3: RDD[(String, Int)] = sc.parallelize(List(("a", 1), ("b", 2), ("c", 3)))
    val list: List[(String, Int)] = List(("a", 4), ("b", 6), ("c", 6))
    //声明广播变量
    val broadcast: Broadcast[List[(String, Int)]] = sc.broadcast(list)
    res=rdd3.map{
      case(k1,v1)=>{
        var v2 = 0
        //使用广播变量,和累加器类似,代替list的功能,把list封装为广播变量
        for ((k3,v3) <- broadcast.value){
          if (k1==k3){
            v2 = v3
          }
        }
        (k1,(v1,v2))
      }
    }
    res.collect().foreach(println)
    

sparksql

  • sparksql是spark用于结构化数据处理的spark模块
  • sparksql的数据抽象为spark提供的关于数据结构和正在执行的计算的更多信息
  • sparksql是将sparksql转换为rdd,然后提交到集群执行,执行效率非常快
  • sparksql数据抽象
    • dataframe
    • dataset
  • sparksql特点:
    • 易整合
      • 无缝的整合了sql查询和spark编程
    • 统一的数据访问方式
      • 使用相同的方式连接不通的数据源
    • 兼容hive(数仓上直接运行sql)
    • 标准的数据连接

spark的dataframe

  • dataframe是一种以rdd为基础的分布式数据集,类似于传统的数据库中的二维表格
  • dataframe和rdd的区别:
    • dataframe带schema元信息,即dataframe表示的二维表数据的每一列都有相对应的名称和类型。
    • 支持嵌套数据类型(struct、array和map)
  • dataframe也是懒执行的,但是性能上比rdd要高。主要的原因:优化的执行计划,即查询计划通过spark catalystoptimiser进行优化(类似hive的先筛选后join)

spark的dataset(强类型)

  • dataset是dataframe一个扩展
  • 和类的概念很类似,即person类中有属性dataframe(id,name,age)
  • 用样例类来定义dataset中数据的结构信息。样例类中每个属性的名称直接映射到dataset 的字段名称

sparksql编程

  • sparkSession是spark最新的sql查询起始点,实质上是sqlcontext和hivecontext的组合。sparkSession还在内部封装了sparkContext,所以计算实际上是由sparkcontext完成的

dataframe的创建方式

  • 创建dataframe的方式:

    • 通过spark的数据源进行创建

      • spark.read.数据源
        • df=spark.read.json(“dir”)===>读取json文件创建dataframe
        • df.show==>展示df的数据
    • 从一个存在的rdd进行转换

    • 从hive table进行查询返回

  • 注意:如果从内存中获取数据,spark可以知道具体的数据类型,如果是数字,默认作为int处理;但是从文件中读取的数字,不能确定什么数据,所以用bigint接收,bigint和long类型可以进行转换,但是无法和int进行转换!!!

sql风格语法

  • sql风格语法:sql语法风格是指我们查询数据的时候使用sql语句来查询,前提是必须要有临时视图或者全局视图来辅助
    1. 创建dataframe
      • val df = spark.read.json(“dir”)
    2. 对dataframe创建一个临时视图
      • df.createOrReplaceTempView(“视图名”)#局部
      • df.createGlobalTemView(“全局视图名”)#全局,使用的时候需要在全局视图名加一个global_temp.字段
    3. 通过对sql语句实现查询全表
      • val sqlDF = spark.sql(“sql语句:select * from 视图名”)
    4. 结果展示
      • sqlDF.show

DSL风格语法

  • 步骤:

    1. 创建一个dataframe
    2. 查询
      • df.select(“属性或者*”)
      • 如果查询的是属性涉及到函数计算的话,属性前面必须要加一个 符 号 , 否 则 会 报 错 。 如 果 多 个 属 性 的 话 , 其 中 一 个 涉 及 到 函 数 计 算 , 所 有 的 属 性 都 必 须 加 符号,否则会报错。如果多个属性的话,其中一个涉及到函数计算,所有的属性都必须加 符号
      • df.filter($“age”>19) df.groupby(“属性”).count
    3. 查询完之后调用show方法可以展示结果
  • 查看dataframe元数据信息:df.printschema


rdd转换dataframe

  • 如果rdd、df和ds之间相互操作,那么需要引入import spark.implicits._ 。其中(spark不是包名,而是sparkSession对象的名称,因此必须先创建sparkSession对象再导入;implicits是一个内部object)
  • rdd转dataframe:()
    1. 创建rdd
    2. 实现属性和元组一一对应的映射关系。调用toDF(“属性对应列表”)
  • rdd手动确认转换并一一对应(两步:转换为元组、元组和属性一一对应
    • 把rdd转换为和dataframe结构化相对应的元组(可以利用toInt或其他方法转换数据类型)#但是dataframe的属性和rdd的元组是无法一一对应的。因此还需要操作
    • 上一步调用toDF(“name”,“age”)方法即可实现属性和元组一一对应起来。
  • 通过样例类转换
    • 创建一个样例类
      • case class 类名(属性:数据类型,…,…)#里面的属性列表和dataframe的一一对应
    • 根据样例类将rdd转换为dataframe
      • 把上面获得的元组有类名用小括号括起来(表示的是该类的类型)
  • 通过编程的方法

dataframe转换为rdd

  • 直接用dataframe调用rdd方法即可完成转换了!!!
  • 这时候的rdd的数据类型为ROW类型

rdd和dataset之间的转换

  • 创建dataset
    • 使用样例类序列创建dataset
      • case class person(“name:string”,“age:int”)
      • 创建样例类序列(集合)
        • Seq(person(“name值”,“age值”))
      • 样例类序列调用toDS即可创建dataset #调用toDF创建dataframe
  • 实际上,一般都是通过rdd来得到dataset的

rdd转换为dataset

sparksql能够自动将包含有样例类的rdd转换为dataset,样例类定义了table的结构,样例类属性通过反射编程了表的列名/属性名。样例类可以包含诸如seq或array等发杂结构

  • 步骤:(和rdd转换为dataframe一样)
    • 创建rdd
    • 创建一个样例类
    • 把元组和样例类属性一一对应起来,并调用toDS方法即可获取dataset

dataset转换为rdd

  • dataset调用rdd方法即可完成转换
  • 返回的rdd的数据类型为dataset的样例类名

dataframe和dataset的转换

dataframe转换为dataset

  • 步骤:
    • 创建一个dataframe:spark.read.json
    • 定义一个样例类:case class 类名(属性:数据类型)
    • dataframe调用as[类名]即可完成转换

dataset转换为dataframe

  • 步骤:
    • 创建一个dataset
    • dataset调用toDF方法即可完成转换

  • rdd、dataframe和dataset三者共性

    1. 三者都是spark里面的分布式数据集
    2. 三者都是惰性机制,需要碰到action才会真正触发计算
    3. 三者有共同的函数,如filter,map函数等
    4. 需要引入spark.implicits._
    5. 三者都会根据spark的内存情况自动缓存运算,从而不用担心内存溢出
    6. 三者都有partition的概念
    7. dataframe和dataset均可使用模式匹配获取各个字段的值和类型
  • 三者的区别

    • rdd不支持sql操作
    • dataframe每一行的类型固定为row,每一列的值没法直接访问,只有通过解析才能获取各个字段的值
    • dataframe和dataset均支持sparksql操作,类似于select、groupby和注册临时表
    • dataframe和dataset支持溢写特别方便的保存方式。(CSV可以设置带不带表头)
  • 常见操作代码:

    • //    import spark.implicits._
      //    val df: DataFrame = spark.read.json("/sparkfile_text/employees.json")
      //    //DSL风格
      //    df.show()
      //    df.select("name").show()
      //    df.select("*").show()
      //    df.filter("salary > 4000").show()
      //    df.printSchema()
      //    df.select($"salary" +1000).show()
      //    //sql风格
      //    df.createOrReplaceTempView("employee")
      //    spark.sql("select name from employee").show()
      //    //创建rdd
      //    val rdd: RDD[Int] = spark.sparkContext.parallelize(List(1, 2, 3, 4))
      //    val rdd1: RDD[Int] = rdd.map(_ * 8)
      //    rdd1.collect().foreach(println)
      //    println("====")
      //    val df1: DataFrame = rdd.toDF("newvalue")
      //    val ds1: Dataset[Int] = rdd.toDS()
      //    df1.show()
      //    ds1.show()
      
      //    //样例类匹配  rdd->dataframe->dataset
      //    val caseRdd: RDD[(String, Int)] = spark.sparkContext.parallelize(List(("spark", 5), ("flink", 4), ("hive", 4)))
      //    val df1: DataFrame = caseRdd.toDF("name", "money")
      //    val ds1: Dataset[emp] = df1.as[emp]
      //    //dataset->dataframe->rdd
      //    val df2: DataFrame = ds1.toDF()
      //    val rdd3: RDD[Row] = df2.rdd
      //    //dataset->rdd      dataframe->rdd
      //    val rdd4: RDD[emp] = ds1.rdd
      //    val rdd5: RDD[Row] = df1.rdd
      
      case class emp(name:String,money:Int)
      

用户自定义函数

udf函数

  • udf:输入一行,返回一个结果。
    • spark.udf功能用户可以自定义函数:spark.udf.register(“函数名”,(参数)=>{对参数进行处理,即方法体})
    • 使用:直接用函数传入相关参数即可

udaf函数

  • udaf函数:输入多行,返回一行。类似于count、avg、max、min函数
  • 可以通过继承UserDefineAggregateFunction来实现用户自定义聚合函数
  • 自定义udaf函数(弱类型):主要是确定参数的数据类型、函数缓冲区的数据类型、函数缓冲区的初始化、更新缓冲区的数据、合并缓冲区、最后的结果
  • 使用弱类型的自定义udaf函数:创建函数对象,用spark.udf.register(“函数名”,函数对象)。最后直接应用即可

udtf函数

  • 输入一行,返回多行(hive)

  • sparksql中没有udtf函数,spark用flatMap实现该功能


sparksql数据的加载与保存

通用的加载和保存方式

  • spark.read.load是加载数据的通用方法
  • spark.write.save是保存数据的通用方法

加载数据

  1. read直接加载数据:spark.read.json(“dir”)/textfile/csv等文件和jdbc等#read文件后面括号路径
  2. format指定加载数据类型:spark.read.format(“加载的数据类型”)[.option("…")].load(“dir”)
    1. format 指定加载的数据类型,有text,CSV,jdbc、orc等
    2. load 传入加载数据的路径
    3. option:在jdbc格式需要传入jdbc响应参数

保存数据

  1. write直接保存数据:df.write.json/csv/orc/parquet等
    1. df.write.json(“dir”)
    2. df.write.save(“dir”)#此时默认的存储格式为parquet
  2. format指定保存数据类型:df.write.format("…")[.option(…)].save("…")
    1. format:指定保存的数据类型
    2. save:保存数据的路径
    3. option:jdbc需要传入的相对应参数,URL、user、password和datable
  3. 文件保存选项:
    • 保存操作可以使用savemode,用来指明如何处理数据,使用mode方法来设置。
    • savemode是一个枚举类,常量包括有:append追加、overwrite覆盖、default抛异常、ignore忽略本次操作
  4. df.write.mode(“append”).json(“dir”)

从jdbc读取数据

  •     spark.read.format("jdbc")
          .option("url", "jdbc:mysql://hadoop202:3306/test")
          .option("driver", "com.mysql.jdbc.Driver")
          .option("user", "root")
          .option("password", "123456")
          .option("dbtable", "user")
          .load().show
    

从jdbc写数据

  •     ds.write
          .format("jdbc")
          .option("url", "jdbc:mysql://hadoop202:3306/test")
          .option("user", "root")
          .option("password", "123456")
          .option("dbtable", "user")
          .mode(SaveMode.Append)
          .save()
    
  • 读取和保存数据的相关代码例子

    • System.setProperty("HADOOP_USER_NAME","root")
      val conf: SparkConf = new SparkConf().setAppName("读取与保存").setMaster("local[*]")
      val spark: SparkSession = SparkSession.builder().config(conf).enableHiveSupport().getOrCreate()
      import spark.implicits._
      //读取json文件,用format和load搭配
      val df: DataFrame = spark.read.format("json").load("/sparkfile_text/people.json")
      df.show()
      println("===")
      df.select("age").show()
      println("===")
      //保存文件为json文件,并参数类型为追加类型,使用write和save搭配
      df.write.format("json").mode("append").save("/df_save_json")
      println("去看看路径和内容!")
      //直接看hive表
      spark.sql("show tables").show()
      println("===")
      //读取mysql数据库的表
      spark.read.format("jdbc")
        .option("url","jdbc:mysql://Centos2:3306/taidi")
        .option("user","root")
        .option("password","root")
        .option("dbtable","score")
        .load().show()
      
  • 读取hive表的时候,直接在设置追加一个设置值即可

  • 读取数据尽量使用format和load搭配,保存数据尽量用write和save搭配,可以用mode来选择参数类型(覆盖,追加等情况)


sparkstreaming

  • sparkstreaming是用于流式数据的处理

  • 支持多种数据源,如kafka、flume和tcp套接字等

  • 数据输入后可以用spark的高度抽象算子:map、reduce 、join、window等运算。

  • 结果保存可以保存在多种地方,如hdfs,数据库等

  • sparkstreaming处理数据的单位是一批,而不是一条。数据采集却是一条条采集的

  • sparkstreaming需要设置间隔使得数据汇总到一定量后再一起合并处理。这个间隔就叫批处理间隔

  • 批处理间隔决定了sparkstreaming提交作业的频率和数据处理的延迟,也影响着数据处理的吞吐量和性能

  • sparkstreaming使用了高级抽象离散化流,也叫DStreams,它是随着时间推移而收到的数据序列。在内部,收到的数据都作为rdd存在,而DStreams是由这些rdd所组成的序列,因此叫离散化流

  • 缺点:是一种批处理,高延迟(对于一条数据来就处理而言)

  • 需要有一个executor来专门采集数据!

  • 背压机制:根据jobscheduler反馈作业的执行信息来动态调整receiver数据接收率

    • spark1.5前,想限制receiver的 数据接收速率,可以通过静态配置参数spark.streaming.receiver.maxRate来实现。防止内存溢出,但是可能会导致资源利用效率下降(大量数据无法进入);
    • spark1.5后,可以动态控制数据接收速率来适配集群数据处理能力。通过属性spark.streaming.backpressure.enabled来控制是否启用背压机制,默认值为false,即不启用
  • Dstream入门

    • val conf: SparkConf = new SparkConf().setAppName("DstreamContext").setMaster("local[*]")
      //new Duration(300)以毫秒为单位,可以用Seconds类来用秒为单位做间隔,Minutes以分钟为单位等
      val ssc: StreamingContext = new StreamingContext(conf, Seconds(2))
      val socketDS: ReceiverInputDStream[String] = ssc.socketTextStream("Centso2", 9999)
      val wordDs: DStream[String] = socketDS.flatMap(_.split(" "))
      val res: DStream[(String, Int)] = wordDs.map((_, 1)).reduceByKey(_ + _)
      //打印输出
      res.print()
      //开启采集
      ssc.start()
      //需要收集数据,不能关闭
      ssc.awaitTermination()
      
    • sparkstreaming的上下文对象不同了,是StreamingContext了

    • 需要设置批处理间隔

    • 数据抽象是DStream,离散化流

    • 需要开启收集数据

    • 需要让程序不关闭(才有机会采集数据)

  • DStream是一系列连续的rdd来表示每个rdd含有一段时间间隔内的数据

  • 应用:水位监控

  • 注意点:

    • streamingcontext启动了,就不能再添加新的stream计算了,即在start方法后,不能做运算了
    • streamingcontext已经停止后,他也不能再重启了
    • 在同一个jvm内,同一时间内只能启动一个streamingcontext
    • 一个sparkcontext可以重用去创建多个Streamingcontext,前提是:以前的streamingcontext已经停掉,并且sparkcontext没有被停掉
  • 数据源可以将rdd封装到消息队列中,给DStream采集数据(kafka)

  • streamingcontext内部封装了sparkcontext!


kafka数据源(采集数据的调用者已经改变了)

  • 版本选型
    • receiverAPI:需要一个专门的executor去接收数据,然后发送给其他的executor做计算。
      • 弊端:接收数据的executor和计算的executor速度会有所不同,特别是在接收数据的executor速度大于计算的executor速度,会导致计算数据的节点内存溢出
    • directAPI:由计算的executor来主动消费kafka的数据,速度由自身控制

0-8版本

  • 版本为0-8例子(采集数据的调用者已经改变了),receiverAPI会有专门的executor采集数据,其他的executor计算

    • val conf: SparkConf = new SparkConf().setAppName("kafka-sparkstreaming").setMaster("local[*]")
      val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
      //不是用ssc来收集信息了,而是用kafkautils
      /**
       * 数据来源已经改变了,不是ssc了,而是kafkautils
       * 四个参数:
       *    1.ssc上下文
       *    2.zookeeper所在的位置,主机名:端口号
       *    3.消费者组名
       *    4.主题  Map(主题名,分区数)
       */
      val kafkaDs: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(
        ssc,
        "Centos2:2181,slave1:2181,slave2:2181,slave3:2181",//zookeeper所在的位置,主机名:端口号
        "first",//消费者组名
        Map("kafka_sparkstreaming" -> 2)//map(主题名->分区数)
      )
      //我们需要的是value
      val lineDs: DStream[String] = kafkaDs.map(_._2)
      //wordcount思路
      val res: DStream[(String, Int)] = lineDs.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _)
      //打印结果
      res.print()
      //开始采集
      ssc.start()
      //保持不关闭程序
      ssc.awaitTermination()
      
    • kafkautils采集数据调用createStream方法,里面四个参数如下:

      •  * 四个参数:
         *    1.ssc上下文
         *    2.zookeeper所在的位置,主机名:端口号
         *    3.消费者组名
         *    4.主题  Map(主题名,分区数)
        
    • 该程序停掉之后,重新开启是无法拿到了,但是能够到zookeeper去找到相对应的数据(即offset维护在zk)这是receiverAPI的维护offset

  • 0-8directAPI:

    • 自动维护offset模式:offset维护在检查点,需要手动添加检查点:ssc.checkpoint(“dir”)

    • //定义kafka参数:两个,kafka消费者的bootstrap(主机名:端口号),kafka消费者组名
      val kafkaParams: Map[String, String] = Map[String, String](
        ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "Centos2:9092,slave1:9092,slave2:9092,slave3:9092",
        ConsumerConfig.GROUP_ID_CONFIG -> "first"
      )
      
      //采集者不是ssc,还是kafkautils
      
      val kafkaDs: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](
        ssc,
        kafkaParams,
        Set("first")
      )
      
    • 注意点:

      • /**
         * 三个参数:
         *    1.ssc上下文
         *    2.kafka参数(两个,一个是kafka的bootstrap,一个是消费者组名),类型为map[string,string]
         *    3.set类型的主题名
         *
         * createDirectStream需要加参数:共四个
         *    1.topic的key数据类型
         *    2.topic的value数据类型
         *    3.序列化的编码:StringDecoder
         *    4.反序列化的编码类型:StringDecoder
         */
        
    • 但是尽管设置了检查点,但是你并没有从检查点拿数据(思维:先从检查点拿,然后再从kafka数据源拿数据!)

    • 先从检查点拿数据,然后再从kafka拿数据

      •     System.setProperty("HADOOP_USER_NAME","root")
        /**
         * StreamingContext调用getActiveOrCreate方法从检查点先拿数据,然后再从kakfa拿数据,参数有两个
         *    1.检查点的目录,字符串类型
         *    2.kafkaUtils拿数据到处理数据的逻辑思维体现
         *    
         *  但是,start方法和awaitTermination方法放在getActiveOrCreate方法外
         *  (因为从检查点开始采集,没有不代表结束,还会到kafka拿数据。知道kafka不发数据了才会停止)
         */
            val ssc = StreamingContext.getActiveOrCreate("/tryagain", () => getscc)
        
            ssc.start()
        
            ssc.awaitTermination()
        
          }
          def getscc:StreamingContext = {
            val conf = new SparkConf().setAppName("duowenjian").setMaster("local[*]")
            val ssc = new StreamingContext(conf, Seconds(3))
        
            ssc.checkpoint("/tryagain")
        
            val kafkaparmes = Map[String, String](
              ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "slave1:9092,slave2:9092,slave3:9092",
              ConsumerConfig.GROUP_ID_CONFIG -> "bigdata"
            )
        
            val kafkasources = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](
              ssc,
              kafkaparmes,
              Set("big")
            )
        
            val datas = kafkasources.map(_._2)
        
            val flatmapDs = datas.flatMap(_.split(" "))
        
            val mapDs = flatmapDs.map((_, 1))
        
            val res = mapDs.reduceByKey(_ + _)
        
            res.print()
        
            ssc
          }
        
      • 自动维护的缺点:

        • 大量小文件产生(每隔一个批处理间隔时间就会产生小文件)
        • 从停止到重新开始这段时间长度里,会以批处理间隔时间执行一遍。
    • 手动维护offset模式:

      • createDirectStream参数里面为fromoffset参数,类型不是主题了,而是(那个主题,那个分区,偏移量为多少)
      • 需要更新offset:利用transform算子操作底层rdd的一个实例asinstanceof

  • 0-10directAPI
    • 调用createDirectStream方法,参数为ssc位置策略(执行器executor机器与kafka节点机器)#调用的是一个常量、消费策略(需要调用subscribe方法订阅主题,subscribe方法参数:主题,为set类型,还有kafka参数)
    • 需要指明泛型:subscribe方法需要指明泛型,是kv类型,还有createDirectStream也要指明泛型,也是kv泛型
    • offset维护在_consumer_offset主题中
  • 0-8版本的receiverAPI想要增加消费并行度的话,需要多个流union
  • 0-8版本的directAPII想要增加消费并行度的话,增加executor数量即可
  • 0-10版本的directAPI想要增加消费并行度的话,增加executor数量即可

  • transform:将dstream转换为rdd

    • val lineDs: ReceiverInputDStream[String] = ssc.socketTextStream("Centos2", 9999)
          lineDs.transform{
            rdd =>{
              val wordRdd: RDD[String] = rdd.flatMap(_.split(" "))
              val mapRdd: RDD[(String, Int)] = wordRdd.map((_, 1))
              val reducebyKeyRdd: RDD[(String, Int)] = mapRdd.reduceByKey(_ + _)
              val sortbykeyRdd: RDD[(String, Int)] = reducebyKeyRdd.sortByKey()
              sortbykeyRdd  //返回rdd
            }
          }
      
  • updatestatebykey:用于将历史结果应用到当前批次,该操作允许在使用新信息不断更新状态的同时能够保留她的状态

    • 参数为函数,然后函数有两个参数,第一个参数为目前窗口的数据集合,第二个参数是缓冲区数据
    • 从缓冲区拿数据:getOrElse(初始值)函数
    • 思维类似于累加器、强类型的udaf函数
    • 该缓冲区的数据保存在检查点,需要手动设置。ssc.checkpoint
  • 输出操作

    • print:打印到控制台
    • saveastextfile:保存为text文件,里面参数可以设置前缀和后缀
      • DStream.saveAsTextFile(“dir”,preffix)
    • saveasHadoopfiles:将数据保存为hadoop files,参数也可以设置前后缀
    • saveasobjectfiles:以java序列化的方式将数据保存为sequencefiles。
  • foreachRdd:和transform算子类似,但是foreachRDD是没有返回值的,transform是有返回值的

    • foreachRDD:将函数用于产生于stream的每一个rdd,其中传入的函数应该事先每一个rdd中数据推送到外部系统
    • 使用注意点:
      • 连接不能写在driver层面(序列化)
      • 如果是涉及到连接数据库,以分区来操作好点,即foreachRDDPartition算子优于foreachRDD

    窗口函数

    • var kvDs = lineDs.flatMap(_.split(" ")).map((_,1))
          val windowDs: DStream[(String, Int)] = kvDs.window(Seconds(6), Seconds(3))
      

DStream中也有累加器和广播变量,使用方法和rdd的一致

sparksql操作sparkstreaming

  • 利用foreachRDD算子,对rdd进行转换为dataframe,然后注册临时表,使用sql查询

caching和persistence

  • 如果DStream中的数据多次被计算,可以使用缓存

spark相关的案例:top n

ubscribe方法订阅主题,subscribe方法参数:主题,为set类型,还有kafka参数)

  • 需要指明泛型:subscribe方法需要指明泛型,是kv类型,还有createDirectStream也要指明泛型,也是kv泛型
  • offset维护在_consumer_offset主题中
  • 0-8版本的receiverAPI想要增加消费并行度的话,需要多个流union
  • 0-8版本的directAPII想要增加消费并行度的话,增加executor数量即可
  • 0-10版本的directAPI想要增加消费并行度的话,增加executor数量即可

  • transform:将dstream转换为rdd

    • val lineDs: ReceiverInputDStream[String] = ssc.socketTextStream("Centos2", 9999)
          lineDs.transform{
            rdd =>{
              val wordRdd: RDD[String] = rdd.flatMap(_.split(" "))
              val mapRdd: RDD[(String, Int)] = wordRdd.map((_, 1))
              val reducebyKeyRdd: RDD[(String, Int)] = mapRdd.reduceByKey(_ + _)
              val sortbykeyRdd: RDD[(String, Int)] = reducebyKeyRdd.sortByKey()
              sortbykeyRdd  //返回rdd
            }
          }
      
  • updatestatebykey:用于将历史结果应用到当前批次,该操作允许在使用新信息不断更新状态的同时能够保留她的状态

    • 参数为函数,然后函数有两个参数,第一个参数为目前窗口的数据集合,第二个参数是缓冲区数据
    • 从缓冲区拿数据:getOrElse(初始值)函数
    • 思维类似于累加器、强类型的udaf函数
    • 该缓冲区的数据保存在检查点,需要手动设置。ssc.checkpoint
  • 输出操作

    • print:打印到控制台
    • saveastextfile:保存为text文件,里面参数可以设置前缀和后缀
      • DStream.saveAsTextFile(“dir”,preffix)
    • saveasHadoopfiles:将数据保存为hadoop files,参数也可以设置前后缀
    • saveasobjectfiles:以java序列化的方式将数据保存为sequencefiles。
  • foreachRdd:和transform算子类似,但是foreachRDD是没有返回值的,transform是有返回值的

    • foreachRDD:将函数用于产生于stream的每一个rdd,其中传入的函数应该事先每一个rdd中数据推送到外部系统
    • 使用注意点:
      • 连接不能写在driver层面(序列化)
      • 如果是涉及到连接数据库,以分区来操作好点,即foreachRDDPartition算子优于foreachRDD

    窗口函数

    • var kvDs = lineDs.flatMap(_.split(" ")).map((_,1))
          val windowDs: DStream[(String, Int)] = kvDs.window(Seconds(6), Seconds(3))
      

DStream中也有累加器和广播变量,使用方法和rdd的一致

sparksql操作sparkstreaming

  • 利用foreachRDD算子,对rdd进行转换为dataframe,然后注册临时表,使用sql查询

caching和persistence

  • 如果DStream中的数据多次被计算,可以使用缓存

spark相关的案例:top n

Hadoop和Spark是大数据处理领域中最流行的两个框架。以下是它们的知识点整理汇总: Hadoop: 1. Hadoop是一个开源的分布式计算框架,用于存储和处理大规模数据集。 2. Hadoop包括两个核心组件:HDFS(Hadoop分布式文件系统)和MapReduce(分布式计算框架)。 3. HDFS是一个分布式文件系统,用于存储大规模数据集。它将数据分成块并存储在不同的节点上,以实现数据的高可靠性和可扩展性。 4. MapReduce是一种分布式计算框架,用于处理大规模数据集。它将数据分成小块并在不同的节点上并行处理,以实现高效的数据处理。 5. Hadoop还包括其他组件,如YARN(资源管理器)和HBase(分布式NoSQL数据库)。 Spark: 1. Spark是一个快速、通用、可扩展的分布式计算框架,用于处理大规模数据集。 2. Spark的核心组件是Spark Core,它提供了分布式任务调度、内存计算和数据处理功能。 3. Spark还包括其他组件,如Spark SQL(用于结构化数据处理)、Spark Streaming(用于实时数据处理)和MLlib(用于机器学习)。 4. Spark使用RDD(弹性分布式数据集)作为其基本数据结构,它是一个可分区、可并行计算和可恢复的数据集合。 5. Spark支持多种编程语言,如Scala、Java、Python和R。 总结: Hadoop和Spark都是用于处理大规模数据集的分布式计算框架,它们有不同的核心组件和特点。Hadoop主要用于存储和处理大规模数据集,而Spark则更加注重数据处理的速度和效率。在实际应用中,可以根据具体需求选择合适的框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值