Spark 第三天总结

一: RDD

RDD是一个数据结构,是一个对象,是一个抽象类

RDD是一个数据模型,封装了计算逻辑

RDD内部使用了分区

Hbase中的Range为了解决高表查询效率慢 宽表(列组)

Kafka中的Range为了提高消费者组的消费效率

RDD中的分区适合并行计算(封装了最小功能,便于重复使用)

RDD实现方式采用了装饰者设计模式

RDD(Dataset)不保留数据,类似于管道,转移数据,但是不保留数据

1.1:既然有分区数据应该放到哪里

kafka中有个partitioner分区器

hash表类似于分区(哈希定位 路由定位)

 1.2:问题引入(哈希表 中的链表极限情况下什么时候会变成红黑二叉树)

(11个:) 首先变成红黑二叉树的前提条件是顺序表长度达到64并且链表长度达到8,因此,极限情况,就往一个哈希位置的链表上一直加数据当加到第9个 的时候,会进行扩容,16->32,到第10个的时候继续扩容32->64,第11个的时候,就会变成红黑二叉树。

此种情况,属于数据倾斜,解决数据倾斜的基本操作是分区;当扩容的不能解决问题的时候,就会改变存储的数据结构。

1.2.1:路由算法(为什么扩容是2倍)

1.2.2:小Demo(封装了最小功能,便于重复使用)

public class TestFlow {
    public static void main(String[] args) {
        String s = "hello";
//        System.out.println(headerUpper(s));
        String string = split(transform(s.substring(0, 1)), s.substring(1));
        System.out.println(string);
    }
    public static String split(String prex , String sufix){
        return prex + sufix;
    }

    public static String transform(String s){
       return s.toUpperCase();
    }

    /*public static String headerUpper(String s ){
        return  s.substring(0,1).toLowerCase() + s.substring(1);
    }
    public static String headerLower(String s ){
        return  s.substring(0,1).toLowerCase() + s.substring(1);
    }*/
}

二:RDD执行原理(将计算和资源整合到一起才能进行计算)

2.1:

从计算的角度来讲,数据处理过程中需要计算资源(内存 & CPU)和计算模型(逻辑)。执行时,需要将计算资源和计算模型进行协调和整合。

Spark框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计算。最后得到计算结果。

  1. 启动Yarn集群环境

2.Spark通过申请资源创建调度节点和计算节点

3.Spark框架根据需求将计算逻辑根据分区划分成不同的任务

 2.1.2:  yarn 调度 VS  RDD调度

yarn调度 调度的是job  ,RDD调度的是Task

学习RDD要写逻辑,底层会自动调度Task,然后送到Executor上进行计算

2.2:什么时候考虑使用tuple,容器的选取?

当数据之间没有关系的时候,可以使用tuple。因为没有关系,也不可能求和等运算。

 

 三:RDD的创建(RDD类似于管道)

3.1:从集合(内存)中创建:将内存的数据作为数据模型的处理

// TODO 使用Spark时,必须先从创建RDD数据模型
// 构建RDD的方式:
// 1. 从集合(内存)中创建:将内存的数据作为数据模型的处理【数据源】
// 2. 从文件(磁盘)中创建:将文件的数据作为数据模型的处理【数据源】

// 3. 从旧的RDD创建新的(框架调用)
// 4. 直接创建新的(框架调用)

 从集合(内存)中创建:将内存的数据作为数据模型的处理【数据源】

 val seqRDD: RDD[Int] = sc.parallelize(Seq(1, 2, 3, 4))
 val seqRDD: RDD[Int] = sc.makeRDD(Seq(1, 2, 3, 4))

 

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    //从集合(内存)中创建:将内存的数据作为数据模型的处理【数据源】
    val seqRDD: RDD[Int] = sc.parallelize(Seq(1, 2, 3, 4))
    seqRDD.collect().foreach(println)
    sc.stop()
  }

 3.1:从磁盘中创建:将内存的数据作为数据模型的处理

val fileRDD : RDD[String] = sc.textFile("data")(不能获取哪个文件中的数据一行一行的取数据
wholeTextFiles方法返回的数据的泛型为元组,第一个元素为文件路径,第二个元素为文件内容(能获取哪个文件中的数据缺点:一次性把数据全部拿过来,因此,当文件的能容数据过大的时候不能使用这个方法;

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    //按行进行读取     从文件(磁盘)中创建:将文件的数据作为数据模型的处理【数据源】
//    从文件中读取数据,文件路径可以使用通配符
// 从文件中读取数据,文件路径可以为文件目录
//    val fileRDD: RDD[String] = sc.textFile("datas/word.txt")
    //val fileRDD: RDD[String] = sc.textFile("datas")
//    val fileRDD: RDD[String] = sc.textFile("datas/word*.txt")
    //fileRDD.collect().foreach(println)
// wholeTextFiles方法返回的数据的泛型为元组,第一个元素为文件路径,第二个元素为文件内容
val fileRDD: RDD[(String, String)] = sc.wholeTextFiles("datas")
    fileRDD.collect().foreach(println)
    sc.stop()
  }

四:分区的方式将数据保存

查看源码:

 ①:val seqRDD: RDD[Int] = sc.makeRDD(Seq(1, 2, 3, 4),2)

( 

        makeRDD方法可以 传递两个参数
//    第一个参数表示数据源
//    第二个参数表示分区(切片)的数量, 存在默认值

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

②:taskScheduler.defaultParallelism

(

override def defaultParallelism(): Int = backend.defaultParallelism()

)

③:(

override def defaultParallelism(): Int =
  scheduler.conf.getInt("spark.default.parallelism", totalCores)

④:(

private[spark] def conf: SparkConf = _conf  (总核心数)

⑤:(

val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")

4.1:分区的计算

// 数据源为文件时的分区数量
// textFile方法可以传递2个参数
//  第一个参数表示文件路径
//  第二个参数表示【最小】分区数量 : math.min(defaultParallelism, 2)
// Spark中没有读取文件的能力,读取文件采用的就是Hadoop

(

hadoopFile(path, classOf[TextInputFormat], classOf[LongWritable], classOf[Text],  minPartitions).map(pair => pair._2.toString).setName(path)

)

def defaultMinPartitions: Int = math.min(defaultParallelism, 2)

 Hadoop中文件的切分数量的计算:

        

long totalSize = ;  (文件总的字节数)

long goalSize(预计平均每个分区的字节数) = totalSize / (numSplits == 0 ? 1 : numSplits);

10% (也就是hadoop中切片的1.1倍  切片平均之后 剩余的那个数如果占其余每个分区的百分之10以内就不用新的分区)

比如:7个字节     val fileRDD: RDD[String] = sc.textFile("datas/word1.txt",3) 设置3个分区

每个分区2个字节还剩一个字节  1/2 = 50% > 10% 因此实际运行之后为4个分区。

Hadoop 本地环境 32 M 切块,集群128M  企业256M

4.2:内存每个分区放什么数据(每个分区的存放形式)

数据切分的源码形式:

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)
    // TODO 使用Spark时,必须先从创建RDD数据模型
    // 【1,2】【3,4, 5】
    // 【1,2, 3】【4, 5】
    // 【1】【2,3】【4,5】
    val seqRDD: RDD[Int] = sc.makeRDD(Seq(1, 2, 3, 4, 5), 3)
    //分区保存数据
    seqRDD.saveAsTextFile("output")

  }

val slices = ParallelCollectionRDD.slice(data, numSlices).toArray

[1,2,3,4,5] 3

源码:

case _ =>
        val array = seq.toArray // To prevent O(n^2) operations for List etc
        positions(array.length, numSlices).map { case (start, end) =>
            array.slice(start, end).toSeq
        }.toSeq
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)
      }
    }

0 until numSlices 也就是 [0,3)

0 =>  [0,1)=>1

1 =>  [1,3 ) =>2 ,3 

2 =>  [3,5) => 4,5

positions(array.length, numSlices).map { case (start, end) =>
            array.slice(start, end).toSeq
        }.toSeq

切分数组

override def getPartitions: Array[Partition] = {
    val slices = ParallelCollectionRDD.slice(data, numSlices).toArray
    slices.indices.map(i => new ParallelCollectionPartition(id, i, slices(i))).toArray
  }

4.3:分区数据存储原理-磁盘

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val fileRDD: RDD[String] = sc.textFile("datas/word1.txt",2)
    fileRDD.saveAsTextFile("output")
    sc.stop()

  }

TODO Hadoop中分区的计算规则和分区数据存储规则不一样。
计算分区是按照字节计算

7 => 3 (按照偏移量 预计的偏移量为:)
[0, 3]
[3, 6]
[6, 7]

读取数据不能按照字节算,按行算,一读就是一行
读取数据是按照偏移量计算
相同的偏移量不会重复读取

 

五:算子

区分方法如果返回是一个RDD,则是转换算子,如果转换的是某个结果,则是行动算子。

 

 5.1:算子转换

RDD根据数据处理方式的不同将算子整体上分为单Value类型、双Value类型(比如scala中的zip和zipwithIndex)和Key-Value类型

 

 5.1:map()算子

 

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    //算子  转换
    // 所谓的转换算子,其实就是通过调用RDD对象的方法,将旧的RDD转换为新的RDD
    // 通过转换,将多个功能组合在一起.
    // TODO map -> 转换,映射 (Key => Value)
    // rdd的map算子可以将数据集中的每一条数据进行转换后返回
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))

    // rdd的map算子可以将数据集中的每一条数据进行转换后返回
    // map算子需要传递一个参数,参数类型为 : Int => U(不确定)
    def mapFunction(num : Int) : Int = {
        num * 2
    }
    rdd.map(mapFunction)
    val newRDD: RDD[Int] = rdd.map(_ * 2)
    newRDD.collect().foreach(println)
    sc.stop()
  }
}

 

  1. 函数签名   def map[U: ClassTag](f: T => U): RDD[U]

  2. 这里的转换可以是类型的转换,也可以是值的转换

 flatmap()与map()的主要区别:

        flatmap()旨在把一条数据的整体,拆成个体,所有的数据还保留;

        map()旨在把一条长数据转成短数据;

 5.2:map()算子  分区

 

 

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val dataRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
    val resultRDD: RDD[Int] = dataRDD.map(
      num => {
        println("*******" + num)
        num * 2
      }
    )
    resultRDD.collect().foreach(println)
    sc.stop()
  }
def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val dataRDD: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
    val resultRDD: RDD[Int] = dataRDD.map(
      num => {
        println("*******" + num)
        num * 2
      }
    )
    val lastRDD: RDD[Int] = resultRDD.map(
      num => {
        println("$$$$$$$$" + num)
        num * 2
      }
    )


    lastRDD.collect()
    sc.stop()
  }

5.3:性能比较

public class TestString {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        for (String s : list) {
            test(s);
        }
    }
    public static void test(String s){
        System.out.println("获取数据库连接");
        System.out.println(s);
        System.out.println("关闭数据库连接");
    }
    public static void testList(List<String> list){
        System.out.println("获取数据库连接");
        for (String s : list) {
            test(s);
        }
        System.out.println("关闭数据库连接");
    }
}

 

 

一个栈空间默认是1M,一个栈中有许多栈帧,如果一个栈帧1K, 那么改栈最多能放1024个

栈帧,如果我们设置栈为5M=》5*1024

一个线程对应一个栈  如果500个线程,每个线程10M则会给栈分配5G,内存被占的越多,可用的就越少,因此服务器被访问的太多,性能就会下降,资源不足等等。栈

栈内存溢出,如果栈大小10M,来一个线程new Thread() 线程多了没办法创建了,此时是资源不够了,

栈内存溢出,一般与线程相关  VS  栈溢出  一般与压栈相关

 

5.3:算子转换 mappartitions()

 Map算子是分区内一个数据一个数据的执行,类似于串行操作()。

一个一个操作数据

而mapPartitions算子是以分区为单位进行批处理操作。

 

 功能的角度

Map算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。

 

MapPartitions算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,所以可以增加或减少数据

    public static void main(String[] args) {
        User user = new User();

        ArrayList<User> userList = new ArrayList<User>();
        userList.add(user);
        ArrayList<User> userList1 = (ArrayList<User>)userList.clone();
        User user1 = userList1.get(0);
        user1.name = "lisi";

        System.out.println(userList);
        System.out.println(userList1);
    }

}
class User {
    public String name;

    @Override
    public String toString() {
        return "User["+name+"]";
    }
}

 案例一 mappartitions()(求每个分区的最大值)

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    // mapPartitions算子需要传递一个参数,类型为函数类型: Iterator => Iterator
    rdd.mapPartitions(
      list =>{
        //包装成一个迭代器
        List(list.max).iterator
      }
    )
    sc.stop()
  }

  案例二 mapPartitionsWithIndex()(只保留某一个分区)

mapPartitionsWithIndex算子需要传递参数,参数的一个值为分区索引,从0开始

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    // mapPartitions算子需要传递一个参数,类型为函数类型: Iterator => Iterator

    rdd.mapPartitionsWithIndex(
      (index,list) => {
        if (index == 1){
          list
        }else{
          Nil.iterator
        }
      }
    )
    sc.stop()
  }

 案例三 groupBy()

def main(args: Array[String]): Unit = {
    val spakConf = new SparkConf().setMaster("local").setAppName("WordCount").set("spark.testing.memory","2147480000")
    val sc =  new SparkContext(spakConf)

    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
    // groupBy算子需要传递一个参数,参数类型为函数类型:Int => K
    /*val groupRDD: RDD[(String, Iterable[Int])] = rdd.groupBy(
      num => {
        if (num % 2 == 0) {
          "偶数"
        } else {
          "奇数"
        }
      }
    )*/
    /*val resRDD: RDD[(Int, Iterable[Int])] = rdd.groupBy(
      num => {
        if (num % 2 == 0) {
          0
        } else {
          1
        }
      }
    )*/

    /*val groupRDD: RDD[(Int, Iterable[Int])] = rdd.groupBy(
      num => num % 2
    )*/
    val groupRDD: RDD[(Int, Iterable[Int])] = rdd.groupBy(_ % 2)
    groupRDD.collect().foreach(println)
    sc.stop()
  }

 

 案例三 Shuffle

 

所有包含shuffle的算子都有改变分区的能力,默认是不改变。

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值