Spark学习笔记(一)

一、Spark 简述

 Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是——Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。

二、开始Spark

首先需要引入maven

<!-- spark core -->
<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-core_2.12</artifactId>
    <version>3.1.1</version>
</dependency>

<!-- spark core(看yarn启动源码用) -->
<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-yarn_2.12</artifactId>
    <version>3.1.1</version>
</dependency>
// 创建和spark的连接
val wordCount: SparkConf = new SparkConf().setMaster("local[*]") // [*]代表当前系统最大可用核数
                                .setAppName("RDD")  // 设置名称
val context: SparkContext = new SparkContext(wordCount)

// 读取文件的数据,进行筛选
val lines: RDD[String] = context.textFile("datas/1.txt")
val words: RDD[String] = lines.flatMap(_.split(" "))
val tuples: Array[(String, Int)] = value.collect()
tuples.foreach(println)

// 关闭连接
context.stop()

三、RDD

RDD是Spark中重要的一部分,是弹性分布式数据,spark的基本数据模型。RDD中不保存数据,如果重复使用RDD就会重新计算。RDD有五个重要的属性:

  • 分区列表:执行并行计算,实现分布式的重要属性 
  • 分区的计算函数 
  • RDD之间的依赖关系:多个RDD之间的依赖   spark任务的容错机制就是根据这个特性而来
  • 对于kv类型的rdd才会有分区函数 
  • 当计算每个分区会有首选的位置 选择最优的执行节点

1. 在内存中创建RDD

val data = List(1, 2, 3, 4, 5, 6)

1. 方法一
val rdd: RDD[Int] = context.parallelize(data)  

2. 方法二
val rdd: RDD[Int] = context.makeRDD(data)  // makeRDD底层是parallelize

2. 通过文件创建RDD

Spark底层读取文件实际上使用的是Hadoop的读取方式。

1. 读取文件内容 path可以是具体路径也可以写到目录(就会读取多个文件), 文件创建RDD
2. textFile 以行为单位读取 wholeTextFile 以文件为单位读取 返回结果是个元组(文件路径, 内容)
3. minPartitions 指定最小分区数, 默认是2

val line1: RDD[String] = context.textFile("datas/1.txt", minPartitions = 2)

val line2: RDD[String] = context.wholeTextFile("datas")

3. 分区的设置

// 默认 "spark.default.parallelism"这个配置的 就是去SparkConf读这个参数 没有配置就是总核数
val rdd: RDD[Int] = context.makeRDD(data, 2)  // numSlices 代表分区数
    
//将处理的数据保存成分区文件
rdd.saveAsTextFile("outPut")

当我们传入一个数组时Spark是如何将数据进行分区的,源码解读

context.makeRDD(data, 2)  // 进入到makeRDD中
------------------------------------------------

def makeRDD[T: ClassTag](
    seq: Seq[T],
    numSlices: Int = defaultParallelism): RDD[T] = withScope {
  parallelize(seq, numSlices)   // 进入parallelize
}
------------------------------------------------

def parallelize[T: ClassTag](
    seq: Seq[T],
    numSlices: Int = defaultParallelism): RDD[T] = withScope {
  assertNotStopped()
  new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())
}
------------------------------------------------

private[spark] class ParallelCollectionRDD[T: ClassTag](
    sc: SparkContext,
    @transient private val data: Seq[T],
    numSlices: Int,
    locationPrefs: Map[Int, Seq[String]])
    extends RDD[T](sc, Nil) {

  override def getPartitions: Array[Partition] = {
    val slices = ParallelCollectionRDD.slice(data, numSlices).toArray  // 在这里进行分区
    slices.indices.map(i => new ParallelCollectionPartition(id, i, slices(i))).toArray
  }
}
-------------------------------------------------

private object ParallelCollectionRDD {
  def slice[T: ClassTag](seq: Seq[T], numSlices: Int): Seq[Seq[T]] = {
    // 在这里根据数组的长度进行的计算,然后通过数组进行切片
    def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
      (0 until numSlices).iterator.map { i =>
        val start = ((i * length) / numSlices).toInt
        val end = (((i + 1) * length) / numSlices).toInt
        (start, end)
      }
    }
    seq match {
      case _ =>   // 由于是数组格式走这个匹配
        val array = seq.toArray 
        positions(array.length, numSlices).map { case (start, end) =>
            array.slice(start, end).toSeq
        }.toSeq
    }
  }
}

读取文件时的分区,Spark是通过所有文件的总字节数 ÷ 最小分区数得到要分多少个区,上源码。

context.textFile("datas/1.txt")
------------------------------------------

def textFile(
    path: String,
    minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {
  assertNotStopped()
  //  在这里传入TextInputFormat,这里对文件的格式做了区分
  hadoopFile(path, classOf[TextInputFormat], classOf[LongWritable], classOf[Text],
    minPartitions).map(pair => pair._2.toString).setName(path)
}
------------------------------------------

// 然后看TextInputFormat的父类 FileInputFormat
public InputSplit[] getSplits(JobConf job, int numSplits) throws IOException {
      StopWatch sw = (new StopWatch()).start();
      FileStatus[] stats = this.listStatus(job);
      job.setLong("mapreduce.input.fileinputformat.numinputfiles", (long)stats.length);
      long totalSize = 0L;
      
      List<FileStatus> files = new ArrayList(stats.length);
      FileStatus[] var9 = stats;
      int var10 = stats.length;
      // 在这里进行了分区个数的计算
      long goalSize = totalSize / (long)(numSplits == 0 ? 1 : numSplits);
}

hadoop在读取的时候,已经读取过的不会在进行读取。

文件:
1
2
3

这样读出来会有三个分区,是因为三个字符加上两个回车换行(/r/n)总共7字节,
7/2 = 3  每个分区读三个字节,所以需要三个分区。

分区:
0 => [0,3] => 1 2     每次左右都是包含
1 => [3,6] => 3       第三个字节被读过不会再次读入
2 => [6,7] =>

4. collect

// 如果最后不去触发collect方法,spark是不会进行作业的,前面的只是在进行一层层的包装

val tuples: Array[(String, Int)] = value.collect()

5. 算子

每个算子就是一个操作,通过算子来转换状态,下面开始介绍RDD相关的算子。

RDD的计算在一个分区内都是一个个执行,前面一个执行完成才会执行下一个,同一个分区内的数据是有序的,分区间的数据是无序的。

※  转换算子

map:一个个进行操作。

val rdd: RDD[Int] = context.makeRDD(data, 2) 

val value: RDD[Int] = rdd.map((number: Int) => {
    number * 2
})

mapPartitions: 是把一个分区的数据全部拿到,而不是一个个进行操作, 但是会将整个分区的数据加载内存,处理完的数据不会释放内存。

rdd.mapPartitions(iter => 
  {
    iter.map(_*2)  // iter就是迭代器,每次iter拿到的都是分区的数据
  }
)

mapPartitionsWithIndex:将待处理的数据以分区为单位发送到计算节点。

// index代表哪个分区 iter代表当前分区全部数据
val value: RDD[Int] = rdd.mapPartitionsWithIndex((index, iter) => {
  if(index == 1){
    iter
  }else{
    Nil.iterator
  }
})

flatMap:扁平映射  flatten + map的操作。

val data: Seq[Any] = List(List(1,2), 3,4, List(5,6), List(7,8), List(9))
val rdd: RDD[Any] = context.makeRDD(data)  // numSlices 代表分区数

// 传入是个每一个单个的值,返回一定要是个可迭代的元素
val value: RDD[Any] = rdd.flatMap((data) => {
  data match {
    case list: List[_] => list
    case d => List(d).iterator
  }
})

glom:将一个分区的数据转换成相同类型的内存数组进行处理,分区不变。

val gloms: RDD[Array[Int]] = rdd.glom()

groupBy:分组,分区默认不变,但是数据会打乱重新组合,一个组的数据在一个分区中。但并不是说一个分区只有一个组,根据返回的key进行分组。

val groups: RDD[(Int, Iterable[Int])] = rdd.groupBy((num: Int) => {
  num % 2
})

filter: 符合规则的数据会保留,过滤后分区不变,分区内的数据可能不平衡,会出现数据倾斜的情况。

val groups: RDD[Int] = rdd.filter(num => num % 2 == 0)

sample: 抽取一部分数据,用于验证是否出现数据倾斜的情况。

val groups: RDD[Int] = rdd.sample(false,  // 抽取是否有放回  
                             0.4,    // 如果不放回就是代表一个基准率,有放回的情况就是代表抽取的次数
                             1   // 随机种子,不传就是使用的是系统的默认时间
)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值