Spark学习笔记(二)

2023-11-11

以下笔记基于对尚硅谷spark教程的学习,Spark版本3.0

目录

什么是 RDD

RDD核心属性

执行原理

基础编程

  创建环境

  RDD 创建

    从集合中创建RDD

    从文件中创建RDD

  RDD 转换算子

    Value 类型

    双Value类型

    Key-Value类型

  RDD 行动算子

  RDD 序列化

    闭包检查

    Kryo 序列化框架

  RDD 依赖关系

  RDD 持久化

    RDD Cache 缓存

    RDD CheckPoint 检查点

    缓存和检查点区别

  RDD 分区器

  RDD 文件读取与保存

累加器

  系统累加器

  自定义累加器

  广播变量


什么是 RDD


  RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据处理模型。代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。
  ➢ 弹性
    ⚫ 存储的弹性:内存与磁盘的自动切换;
    ⚫ 容错的弹性:数据丢失可以自动恢复;
    ⚫ 计算的弹性:计算出错重试机制;
    ⚫ 分片的弹性:可根据需要重新分片。
  ➢ 分布式:数据存储在大数据集群不同节点上
  ➢ 数据集:RDD 封装了计算逻辑,并不保存数据
  ➢ 数据抽象:RDD 是一个抽象类,需要子类具体实现
  ➢ 不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在新的 RDD 里面封装计算逻辑
  ➢ 可分区、并行计算

RDD核心属性


  分区列表
    RDD 数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。
    protected def getPartitions: Array[Partition]
  分区计算函数
    Spark 在计算时,是使用分区函数对每一个分区进行计算
    @DeveloperApi
    def compute(split: Partition, context: TaskContext): Iterator[T]
  RDD之间的依赖关系
    RDD 是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个 RDD 建立依赖关系
    protected def getDependencies: Seq[Dependency[_]] = deps
  分区器(可选)
    当数据为 KV 类型数据时,可以通过设定分区器自定义数据的分区
    @transient val partitioner: Option[Partitioner] = None
  首选位置(可选)
    计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算
    protected def getPreferredLocations(split: Partition): Seq[String] = Nil

执行原理


  Spark 框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计算。最后得到计算结果。
  在 Yarn 环境中,RDD的工作原理:
    1) 启动 Yarn 集群环境
    2) Spark 通过申请资源创建调度节点和计算节点
    3) Spark 框架根据需求将计算逻辑根据分区划分成不同的任务
    4) 调度节点将任务根据计算节点状态发送到对应的计算节点进行计算

基础编程


  创建环境

val sparkConf = new SparkConf().setMaster("local[*]").setAppName("spark")
val sparkContext = new SparkContext(sparkConf)


  RDD 创建


    从集合中创建RDD

val seq = Seq[Int](1,2,3,4)
// 第二个参数为数据切片/分区的数量,第二个参数不给时,使用默认值defaultParallelism(默认并行度)
//   spark在默认情况下,从配置对象中获取配置参数:spark.default.parallelism
//   如果获取不到,那么使用totalCores属性,这个属性取值为当前运行环境的最大可用核数
val rdd1: RDD[Int] = sparkContext.parallelize(seq, 2)
// makeRDD方法在底层实现时其实就是调用了rdd对象的parallelize方法
val rdd2: RDD[Int] = sparkContext.makeRDD(seq, 2)

// 集合读取数据时,每个切片/分区分配的数据地址为
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)
  }
}

    从文件中创建RDD

// 可以是具体文件,第二个参数为数据切片/分区的最小分区数量,第二个参数不给时,使用默认值math.min(defaultParallelism, 2)
val rdd1: RDD[String] = sparkContext.textFile("datas/1.txt", 3)
// 可以是目录
val rdd2: RDD[String] = sparkContext.textFile("datas")
// 可以使用通配符 *
val rdd3: RDD[String] = sparkContext.textFile("datas/1*.txt")
// 可以是分布式存储系统路径:HDFS
val rdd4: RDD[String] = sparkContext.textFile("hdfs://linux1:8020/test.txt")
// 以文件为单位读取数据,读取的结果表示为元组,第一个元素表示文件路径,第二个元素表示文件内容
val rdd5: RDD[(String, String)] = sparkContext.wholeTextFiles("datas")

      读取文件数据时,数据是按照 Hadoop 文件读取的规则进行切片分区
      文件读取数据时实际切片/分区数 >=最小分区数
      单个文件时
        分区数的计算方式:
          每个分区字节数 = 文件总字节数 / 最小分区数
          分区数 ≈ 文件总字节数 / 每个分区字节数
            余数部分>每个分区字节数*0.1时,分区数+1
        数据分区的划分:
          1.数据以行为单位进行读取,一行数据只会属于一个分区
          2.数据分区偏移量计算
            例如:文件总字节数为7,最小分区数设为2,每个分区的字节数为3,实际分区数为3
              分区1的偏移量: [0, 3]
              分区2的偏移量: [3, 6]
              分区3的偏移量: [7]
          3.以每行数据的第一个字节的偏移量来决定属于哪个分区
      如果数据源为多个文件,那么计算分区时以文件为单位进行分区


  RDD 转换算子


    RDD 根据数据处理方式的不同将算子整体上分为 Value 类型、双 Value 类型和 Key-Value类型


    Value 类型


      map 一进一出

// 将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
def map[U](f: (T) ⇒ U)(implicit arg0: ClassTag[U]): RDD[U]
  // 1. rdd的计算一个分区内的数据是一个一个执行逻辑
  //    只有前面一个数据全部的逻辑执行完毕后,才会执行下一个数据。
  //    分区内数据的执行是有序的。
  // 2. 不同分区数据计算是无序的。

 
      mapPartitions 多进多出

// 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据。
def mapPartitions[U](f: (Iterator[T]) ⇒ Iterator[U], preservesPartitioning: Boolean = false)(implicit arg0: ClassTag[U]): RDD[U]

      map 和 mapPartitions 的区别
        ➢ 数据处理角度
          Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子是以分区为单位进行批处理操作。
        ➢ 功能的角度
          Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。
          MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,所以可以增加或减少数据
        ➢ 性能的角度
          Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处理,所以性能较高。
          但是 mapPartitions 算子会长时间占用内存,那么这样会导致内存可能不够用,出现内存溢出的错误,所以在内存有限的情况下,不推荐使用。

      mapPartitionsWithIndex 多进多出

// 将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
def mapPartitionsWithIndex[U](f: (Int, Iterator[T]) ⇒ Iterator[U], preservesPartitioning: Boolean = false)(implicit arg0: ClassTag[U]): RDD[U]

      flatMap 一进多出

// 将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
def flatMap[U](f: (T) ⇒ TraversableOnce[U])(implicit arg0: ClassTag[U]): RDD[U]

      glom

// 将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
def glom(): RDD[Array[T]]

      groupBy

// 将数据根据指定的规则进行分组, 分区数量默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为 shuffle。
// 极限情况下,数据可能被分在同一个分区中,一个组的数据在一个分区中,但是并不是说一个分区中只有一个组
def groupBy[K](f: (T) ⇒ K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]

      filter

// 将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
// 当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
def filter(f: (T) ⇒ Boolean): RDD[T]

      sample

// 根据指定的规则从数据集中抽取数据
def sample(withReplacement: Boolean, fraction: Double, seed: Long = Utils.random.nextLong): RDD[T]
  // 抽取数据不放回(伯努利算法)
    // 第一个参数:抽取的数据是否放回,false:不放回
    // 第二个参数:抽取的几率,范围在[0,1]之间,0:全不取;1:全取
    // 第三个参数:随机数种子
  // 抽取数据放回(泊松算法)
    // 第一个参数:抽取的数据是否放回,true:放回
    // 第二个参数:重复数据的几率,范围大于等于0.表示每一个元素被期望抽取到的次数
    // 第三个参数:随机数种子

      distinct

// 将数据集中重复的数据去重,如果传入参数numPartitions,表示去重后的分区数
// 在未指定分区器partitioner时,实现方式是:map(x => (x, null)).reduceByKey((x, _) => x, numPartitions).map(_._1)
def distinct(): RDD[T]
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]

      coalesce

// 根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
// 默认情况下不会将分区的数据打乱重新组合,这种情况下的缩减分区可能会导致数据不均衡,出现数据倾斜,如果想要让数据均衡,可以进行shuffle处理
// coalesce算子可以扩大分区的,但是如果不进行shuffle操作,是没有意义,不起作用。所以如果想要实现扩大分区的效果,需要使用shuffle操作
def coalesce(numPartitions: Int, shuffle: Boolean = false, partitionCoalescer: Option[PartitionCoalescer] = Option.empty)(implicit ord: Ordering[T] = null): RDD[T]

      repartition

// 该操作内部其实执行的是 coalesce 操作,参数 shuffle 的默认值为 true。
// 无论是将分区数多的RDD 转换为分区数少的 RDD,还是将分区数少的 RDD 转换为分区数多的 RDD,repartition操作都可以完成,因为无论如何都会经 shuffle 过程。
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]

      sortBy

// 该操作用于排序数据。在排序之前,可以将数据通过 f 函数进行处理,之后按照 f 函数处理的结果进行排序,默认为升序排列。
// 排序后新产生的 RDD 的分区数与原 RDD 的分区数一致。中间存在 shuffle 的过程
def sortBy[K](f: (T) ⇒ K, ascending: Boolean = true, numPartitions: Int = this.partitions.length)(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]

    双Value类型


      intersection

// 对源 RDD 和参数 RDD 求交集后返回一个新的 RDD,要求数据类型一致
def intersection(other: RDD[T], numPartitions: Int): RDD[T]
def intersection(other: RDD[T], partitioner: Partitioner)(implicit ord: Ordering[T] = null): RDD[T]
def intersection(other: RDD[T]): RDD[T]

      union

// 对源 RDD 和参数 RDD 求并集后返回一个新的 RDD,要求数据类型一致
def union(other: RDD[T]): RDD[T]

      subtract

// 以一个 RDD 元素为主,去除两个 RDD 中重复元素,将其他元素保留下来。求差集,要求数据类型一致
def subtract(other: RDD[T], p: Partitioner)(implicit ord: Ordering[T] = null): RDD[T]
def subtract(other: RDD[T], numPartitions: Int): RDD[T]
def subtract(other: RDD[T]): RDD[T]

      zip

// 拉链,将两个 RDD 中的元素,以键值对的形式进行合并。其中,键值对中的 Key 为第 1 个 RDD中的元素,Value 为第 2 个 RDD 中的相同位置的元素。
// 要求分区数量相等,每个分区中的数据个数相等
def zip[U](other: RDD[U])(implicit arg0: ClassTag[U]): RDD[(T, U)]

    Key-Value类型


      partitionBy

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// 将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitioner
def partitionBy(partitioner: Partitioner): RDD[(K, V)]
  // 如果重分区的分区器和当前 RDD 的分区器一样,什么都不做

      reduceByKey

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// 将数据按照相同的 Key 对 Value 进行聚合
// scala语言中一般的聚合操作都是两两聚合,spark基于scala开发的,所以它的聚合也是两两聚合
// reduceByKey中如果key的数据只有一个,是不会参与运算的。
def reduceByKey(func: (V, V) ⇒ V): RDD[(K, V)]
// 需要重分区的时候
def reduceByKey(func: (V, V) ⇒ V, numPartitions: Int): RDD[(K, V)]
def reduceByKey(partitioner: Partitioner, func: (V, V) ⇒ V): RDD[(K, V)]

val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.reduceByKey(_+_)
val dataRDD3 = dataRDD1.reduceByKey(_+_, 2)

      groupByKey

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// 将数据源中相同key的数据分在一个组中,形成一个对偶元组,元组中的第一个元素就是key,第二个元素就是相同key的value的集合
def groupByKey(): RDD[(K, Iterable[V])]
// 需要重分区的时候
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]

val dataRDD1 = sparkContext.makeRDD(List(("a",1),("b",2),("c",3)))
val dataRDD2 = dataRDD1.groupByKey()
val dataRDD3 = dataRDD1.groupByKey(2)
val dataRDD4 = dataRDD1.groupByKey(new HashPartitioner(2))

      reduceByKey 和 groupByKey 的区别
        从 shuffle 的角度:
           reduceByKey 和 groupByKey 都存在 shuffle 的操作,都会落盘。
           但是reduceByKey可以在 shuffle 前对分区内相同 key 的数据进行预聚合(combine)功能,这样会减少落盘的数据量,
           而 groupByKey 只是进行分组,不存在数据量减少的问题,reduceByKey 性能比较高。
        从功能的角度:
           reduceByKey 其实包含分组和聚合的功能。groupByKey 只能分组,不能聚合。
           所以在分组聚合的场合下,推荐使用 reduceByKey,
           如果仅仅是分组而不需要聚合,那么还是只能使用 groupByKey

      aggregateByKey

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// 将数据根据不同的规则进行分区内计算和分区间计算
def aggregateByKey[U](zeroValue: U)(seqOp: (U, V) ⇒ U, combOp: (U, U) ⇒ U)(implicit arg0: ClassTag[U]): RDD[(K, U)]
  // aggregateByKey存在函数柯里化,有两个参数列表
  // 1. 第一个参数列表中的参数表示初始值
  //   主要用于当碰见第一个key的时候,和value进行分区内计算
  // 2. 第二个参数列表中含有两个参数
  //   2.1 第一个参数表示分区内的计算规则
  //   2.2 第二个参数表示分区间的计算规则
// 需要重分区的时候
def aggregateByKey[U](zeroValue: U, numPartitions: Int)(seqOp: (U, V) ⇒ U, combOp: (U, U) ⇒ U)(implicit arg0: ClassTag[U]): RDD[(K, U)]
def aggregateByKey[U](zeroValue: U, partitioner: Partitioner)(seqOp: (U, V) ⇒ U, combOp: (U, U) ⇒ U)(implicit arg0: ClassTag[U]): RDD[(K, U)]

val rdd = sc.makeRDD(List(
    ("a", 1), ("a", 2), ("a", 3), ("a", 4)
),2)
rdd.aggregateByKey(0)(
    (x, y) => math.max(x, y),
    (x, y) => x + y
).collect.foreach(println)

      foldByKey

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// 当分区内计算规则和分区间计算规则相同时,aggregateByKey 就可以简化为 foldByKey
def foldByKey(zeroValue: V)(func: (V, V) ⇒ V): RDD[(K, V)]
// 需要重分区的时候
def foldByKey(zeroValue: V, numPartitions: Int)(func: (V, V) ⇒ V): RDD[(K, V)]
def foldByKey(zeroValue: V, partitioner: Partitioner)(func: (V, V) ⇒ V): RDD[(K, V)]

      combineByKey

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// 最通用的对 key-value 型 rdd 进行聚集操作的聚集函数(aggregation function)。类似于aggregate(),combineByKey()允许用户返回值的类型与输入不一致。
def combineByKey[C](createCombiner: (V) ⇒ C, mergeValue: (C, V) ⇒ C, mergeCombiners: (C, C) ⇒ C): RDD[(K, C)]
  // 第一个参数表示:将相同key的第一个数据进行结构的转换,实现操作
  // 第二个参数表示:分区内的计算规则
  // 第三个参数表示:分区间的计算规则
// 需要重分区的时候
def combineByKey[C](createCombiner: (V) ⇒ C, mergeValue: (C, V) ⇒ C, mergeCombiners: (C, C) ⇒ C, numPartitions: Int): RDD[(K, C)]
def combineByKey[C](createCombiner: (V) ⇒ C, mergeValue: (C, V) ⇒ C, mergeCombiners: (C, C) ⇒ C, partitioner: Partitioner, mapSideCombine: Boolean = true, serializer: Serializer = null): RDD[(K, C)]
def combineByKeyWithClassTag[C](createCombiner: (V) ⇒ C, mergeValue: (C, V) ⇒ C, mergeCombiners: (C, C) ⇒ C)(implicit ct: ClassTag[C]): RDD[(K, C)]

      reduceByKey、foldByKey、aggregateByKey、combineByKey 的区别
        reduceByKey: 相同 key 的第一个数据不进行任何计算,分区内和分区间计算规则相同,数据格式不变
        foldByKey: 相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则相同,数据格式不变
        aggregateByKey:相同 key 的第一个数据和初始值进行分区内计算,分区内和分区间计算规则可以不相同,数据格式可变
        combineByKey:当计算时,发现数据结构不满足要求时,可以让第一个数据转换结构,分区内和分区间计算规则不相同,数据格式可变

        四个函数底层都是通过调用下面的函数来实现的:
          def combineByKeyWithClassTag[C](createCombiner: (V) ⇒ C, mergeValue: (C, V) ⇒ C, mergeCombiners: (C, C) ⇒ C, partitioner: Partitioner, mapSideCombine: Boolean = true, serializer: Serializer = null)(implicit ct: ClassTag[C]): RDD[(K, C)]

      join

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// 在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素连接在一起的(K,(V,W))的 RDD
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
// 需要重分区的时候
def join[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, W))]
def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))]

      leftOuterJoin/rightOuterJoin/fullOuterJoin

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// 类似于 SQL 语句的左外连接/右外连接/全外连接
def leftOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (V, Option[W]))]
def rightOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (Option[V], W))]
def fullOuterJoin[W](other: RDD[(K, W)]): RDD[(K, (Option[V], Option[W]))]
// 需要重分区的时候
def leftOuterJoin[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, Option[W]))]
def leftOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, Option[W]))]
def rightOuterJoin[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (Option[V], W))]
def rightOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (Option[V], W))]
def fullOuterJoin[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (Option[V], Option[W]))]
def fullOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (Option[V], Option[W]))]

      cogroup
 

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// connect + group,在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的 RDD
def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))]
def cogroup[W1, W2](other1: RDD[(K, W1)], other2: RDD[(K, W2)]): RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2]))]
def cogroup[W1, W2, W3](other1: RDD[(K, W1)], other2: RDD[(K, W2)], other3: RDD[(K, W3)]): RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2], Iterable[W3]))]
// 需要重分区的时候
def cogroup[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W]))]
def cogroup[W1, W2](other1: RDD[(K, W1)], other2: RDD[(K, W2)], numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2]))]
def cogroup[W1, W2, W3](other1: RDD[(K, W1)], other2: RDD[(K, W2)], other3: RDD[(K, W3)], numPartitions: Int): RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2], Iterable[W3]))]
def cogroup[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (Iterable[V], Iterable[W]))]
def cogroup[W1, W2](other1: RDD[(K, W1)], other2: RDD[(K, W2)], partitioner: Partitioner): RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2]))]
def cogroup[W1, W2, W3](other1: RDD[(K, W1)], other2: RDD[(K, W2)], other3: RDD[(K, W3)], partitioner: Partitioner): RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2], Iterable[W3]))]

  RDD 行动算子


    所谓的行动算子,其实就是触发作业(Job)执行的方法

    reduce

// 聚集 RDD 中的所有元素,先聚合分区内数据,再聚合分区间数据
def reduce(f: (T, T) ⇒ T): T

    collect

// 将不同分区的数据按照分区顺序采集到Driver端内存中,形成数组
def collect(): Array[T]
// 收集的同时做类型转换
def collect[U](f: PartialFunction[T, U])(implicit arg0: ClassTag[U]): RDD[U]

    count

// 返回 RDD 中元素的个数
def count(): Long

    first

// 返回 RDD 中的第一个元素
def first(): T

    take

// 返回一个由 RDD 的前 n 个元素组成的数组
def take(num: Int): Array[T]

    takeOrdered

// 返回该 RDD 排序后的前 n 个元素组成的数组,默认升序,可以通过传入第二个参数自定义排序
def takeOrdered(num: Int)(implicit ord: Ordering[T]): Array[T]

    takeSample

// 随机抽样算子
// withReplacement:是否可以多次抽样(true可以,false不可以)
// num:返回的样本的大小
// seed:随机数生成器的种子(一般都是默认不会指定)
def takeSample(withReplacement: Boolean, num: Int, seed: Long = Utils.random.nextLong): Array[T]

    top

// 返回该 RDD 排序后的前 n 个元素组成的数组,默认降序,可以通过传入第二个参数自定义排序
def top(num: Int)(implicit ord: Ordering[T]): Array[T]

    aggregate

// 分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合
//   不同:aggregateByKey的初始值只参与分区内计算
def aggregate[U](zeroValue: U)(seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)(implicit arg0: ClassTag[U]): U

    fold

// 折叠操作,aggregate 的简化版操作
def fold(zeroValue: T)(op: (T, T) ⇒ T): T

    countByKey

// 类PairRDDFunctions的方法,存在RDD->PairRDDFunctions的隐式转换
// 统计每种 key 的个数
def countByKey(): Map[K, Long]

    countByValue

// 统计每种 value 的个数
def countByValue()(implicit ord: Ordering[T] = null): Map[T, Long]

    save 相关算子

// 将数据保存到不同格式的文件中,每个分区生成一个文件
def saveAsTextFile(path: String): Unit
def saveAsTextFile(path: String, codec: Class[_ <: CompressionCodec]): Unit
def saveAsObjectFile(path: String): Unit
// 类SequenceFileRDDFunctions的方法,存在RDD->SequenceFileRDDFunctions的隐式转换,数据必须为(key, value)类型
def saveAsSequenceFile(path: String, codec: Option[Class[_ <: CompressionCodec]] = None): Unit

    foreach

// 分布式遍历 RDD 中的每一个元素,调用指定函数
def foreach(f: (T) ⇒ Unit): Unit

// 这里的foreach其实是Scala的Array的方法,在Driver端内存集合的循环遍历方法
rdd.collect().foreach(println)

// 这里的foreach其实是Executor端内存数据打印
rdd.foreach(println)

  RDD 序列化


    闭包检查


      从计算的角度, 算子以外的代码都是在 Driver 端执行, 算子里面的代码都是在 Executor端执行。
      那么在 scala 的函数式编程中,就会导致算子内经常会用到算子外的数据,这样就形成了闭包的效果,如果使用的算子外的数据无法序列化,就意味着无法传值给 Executor端执行,就会发生错误,
      所以需要在执行任务计算前,检测闭包内的对象是否可以进行序列化,这个操作我们称之为闭包检测。
      Scala2.12 版本后闭包编译方式发生了改变

    Kryo 序列化框架


      Java 的序列化能够序列化任何的类。但是比较重(字节多),序列化后,对象的提交也比较大。
      Spark 出于性能的考虑,Spark2.0 开始支持另外一种 Kryo 序列化机制,Kryo 速度是 Serializable 的 10 倍。
      当 RDD 在 Shuffle 数据的时候,简单数据类型、数组和字符串类型已经在 Spark 内部使用 Kryo 来序列化。

// 设定其他类型也使用Kryo序列化
val conf: SparkConf = new SparkConf()
  .setAppName("SerDemo")
  .setMaster("local[*]")
  // 替换默认的序列化机制
  .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
  // 注册需要使用 kryo 序列化的自定义类
  .registerKryoClasses(Array(classOf[Searcher]))

  RDD 依赖关系


    RDD 血缘关系
      将创建 RDD 的一系列 Lineage(血统)记录下来,以便恢复丢失的分区。
      RDD 的 Lineage 会记录 RDD 的元数据信息和转换行为,当该 RDD 的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。
    RDD 依赖关系
      这里所谓的依赖关系,其实就是两个相邻 RDD 之间的关系
    RDD 窄依赖
      窄依赖表示每一个父(上游)RDD 的 Partition 最多被子(下游)RDD 的一个 Partition 使用,窄依赖我们形象的比喻为独生子女。
        class OneToOneDependency[T] extends NarrowDependency[T]
    RDD 宽依赖
      宽依赖表示同一个父(上游)RDD 的 Partition 被多个子(下游)RDD 的 Partition 依赖,会引起 Shuffle,宽依赖我们形象的比喻为多生。
        class ShuffleDependency[K, V, C] extends Dependency[Product2[K, V]]
    RDD 阶段划分
      RDD中存在宽依赖(ShuffleDependency)时,阶段会增加一个,阶段的数量 = 宽依赖(ShuffleDependency)的数量 + 1
    RDD 任务划分
      RDD 任务切分中间分为:Application、Job、Stage 和 Task
        Application:初始化一个 SparkContext 即生成一个 Application;
        Job:一个 Action 算子就会生成一个 Job;
        Stage:Stage 等于宽依赖(ShuffleDependency)的个数加 1;
        Task:一个 Stage 阶段中,最后一个 RDD 的分区个数就是 Task 的个数。
        注意:Application->Job->Stage->Task 每一层都是 1 对 n 的关系。

  RDD 持久化


    RDD Cache 缓存


      RDD中不存储数据,默认情况下如果一个RDD需要重复使用,那么需要从头执行来获取数据

      RDD 通过 Cache 或者 Persist 方法将前面的计算结果缓存,默认情况下会把数据以缓存在 JVM 的堆内存中。
      但是并不是这两个方法被调用时立即缓存,而是触发后面的 action 算子时,该 RDD 将会被缓存在计算节点的内存中,并供后面重用。

      缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD 的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。
      通过基于 RDD 的一系列转换,丢失的数据会被重算,由于 RDD 的各个 Partition 是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部 Partition。

      Spark 会自动对一些 Shuffle 操作的中间数据做持久化操作(比如:reduceByKey)。这样做的目的是为了当一个节点 Shuffle 失败了避免重新计算整个输入。
      但是,在实际使用的时候,如果想重用数据,仍然建议调用 persist 或 cache。

// cache默认持久化的操作,只能将数据保存到内存中,如果想要保存到磁盘文件,需要更改存储级别
//mapRDD.cache()
// 持久化操作必须在行动算子执行时完成的。
mapRDD.persist(StorageLevel.DISK_ONLY)

mapRDD.reduceByKey(_+_).collect().foreach(println)
// 如果前面mapRDD没有调用cache或者persist方法,第二次使用时会从头执行来获取数据
mapRDD.groupByKey().collect().foreach(println)

    RDD CheckPoint 检查点


      所谓的检查点其实就是通过将 RDD 中间结果写入磁盘
      由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
      对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发。
      
      检查点路径保存的文件,当作业执行完毕后,不会被删除,一般保存路径都是在分布式存储系统:HDFS

// 设置检查点路径
sc.setCheckpointDir("./checkpoint1")
// 创建一个 RDD,读取指定位置文件:hello atguigu atguigu
val lineRdd: RDD[String] = sc.textFile("input/1.txt")
// 业务逻辑
val wordRdd: RDD[String] = lineRdd.flatMap(line => line.split(" "))
val wordToOneRdd: RDD[(String, Long)] = wordRdd.map {
 word => {
 (word, System.currentTimeMillis())
 }
}
// 增加缓存,避免再重新跑一个 job 做 checkpoint
wordToOneRdd.cache()
// 数据检查点:针对 wordToOneRdd 做检查点计算
wordToOneRdd.checkpoint()
// 触发执行逻辑
wordToOneRdd.collect().foreach(println)

    缓存和检查点区别


      cache : 将数据临时存储在内存中进行数据重用
              会在血缘关系中添加新的依赖。一旦,出现问题,可以重头读取数据
      persist : 将数据临时存储在磁盘文件中进行数据重用
                涉及到磁盘IO,性能较低,但是数据安全
                如果作业执行完毕,临时保存的数据文件就会丢失
      checkpoint : 将数据长久地保存在磁盘文件中进行数据重用
                涉及到磁盘IO,性能较低,但是数据安全
                为了保证数据安全,所以一般情况下,会独立执行作业
                为了能够提高效率,一般情况下,是需要和cache联合使用
                执行过程中,会切断血缘关系。重新建立新的血缘关系
                checkpoint等同于改变数据源

  RDD 分区器


    Spark 目前支持 Hash 分区和 Range 分区,和用户自定义分区。Hash 分区为当前的默认分区。
    分区器直接决定了 RDD 中分区的个数、RDD 中每条数据经过 Shuffle 后进入哪个分区,进而决定了 Reduce 的个数。

    只有 Key-Value 类型的 RDD 才有分区器,非 Key-Value 类型的 RDD 分区的值是 None
    每个 RDD 的分区 ID 范围:0 ~ (numPartitions - 1),决定这个值是属于那个分区的

    Hash 分区:对于给定的 key,计算其 hashCode,并除以分区个数取余
    Range 分区:将一定范围内的数据映射到一个分区中,尽量保证每个分区数据均匀,而且分区间有序

  RDD 文件读取与保存


    文件格式分为:text 文件、csv 文件、sequence 文件以及 Object 文件
    文件系统分为:本地文件系统、HDFS、HBASE 以及数据库

    text 文件

// 读取输入文件
val inputRDD: RDD[String] = sc.textFile("input/1.txt")
// 保存数据
inputRDD.saveAsTextFile("output")

    sequence 文件
      SequenceFile 文件是 Hadoop 用来存储二进制形式的 key-value 对而设计的一种平面文件(Flat File)

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

    object 对象文件
      对象文件是将对象序列化后保存的文件,采用 Java 的序列化机制。

// 保存数据
dataRDD.saveAsObjectFile("output")
// 读取数据
sc.objectFile[Int]("output").collect().foreach(println)

累加器


  累加器用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量,在Executor 端的每个 Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后,传回 Driver 端进行 merge。

  系统累加器

    val rdd = sc.makeRDD(List(1,2,3,4,5))
    // 声明累加器
    var sum = sc.longAccumulator("sum");
    rdd.foreach(
       num => {
         // 使用累加器
         sum.add(num)
       }
    )
    // 获取累加器的值
    println("sum = " + sum.value)

  自定义累加器

    // 自定义累加器
    // 1. 继承 AccumulatorV2,并设定输入和输出泛型
    // 2. 重写累加器的抽象方法
    class WordCountAccumulator extends AccumulatorV2[String, mutable.Map[String, Long]]{
      var map : mutable.Map[String, Long] = mutable.Map()
      // 累加器是否为初始状态
      override def isZero: Boolean = {
        map.isEmpty
      }
      // 复制累加器
      override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
        new WordCountAccumulator
      }
      // 重置累加器
      override def reset(): Unit = {
        map.clear()
      }
      // 向累加器中增加数据 (In)
      override def add(word: String): Unit = {
        // 查询 map 中是否存在相同的单词
        // 如果有相同的单词,那么单词的数量加 1
        // 如果没有相同的单词,那么在 map 中增加这个单词
        map(word) = map.getOrElse(word, 0L) + 1L
      }
      
      // 合并累加器
      override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
        val map1 = map
        val map2 = other.value
        // 两个 Map 的合并
        map = map1.foldLeft(map2)(( innerMap, kv ) => {
          innerMap(kv._1) = innerMap.getOrElse(kv._1, 0L) + kv._2
          innerMap
        })
      }
      // 返回累加器的结果 (Out)
      override def value: mutable.Map[String, Long] = map
    }

    // 使用自定义累加器
    // 创建累加器对象
    val wcAcc = new WordCountAccumulator()
    // 向Spark进行注册
    sc.register(wcAcc, "wordCountAcc")
    rdd.foreach(
        word => {
            // 数据的累加(使用累加器)
            wcAcc.add(word)
        }
    )
    // 获取累加器累加的结果
    println(wcAcc.value)

  广播变量


    广播变量用来高效分发较大的对象。向所有工作节点(Executor)发送一个较大的只读值,以供一个或多个 Spark 操作使用。
    在多个并行操作中使用同一个变量,但是 Spark 会为每个任务分别发送。

    val rdd1 = sc.makeRDD(List( ("a",1), ("b", 2), ("c", 3), ("d", 4) ),4)
    val list = List( ("a",4), ("b", 5), ("c", 6), ("d", 7) )
    // 声明广播变量
    val broadcast: Broadcast[List[(String, Int)]] = sc.broadcast(list)
    val resultRDD: RDD[(String, (Int, Int))] = rdd1.map {
      case (key, num) => {
        var num2 = 0
        // 使用广播变量
        for ((k, v) <- broadcast.value) {
          if (k == key) {
            num2 = v
          }
        }
        (key, (num, num2))
      }
    }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spark学习笔记(二) 的相关文章

  • 在职状态下继续学习的心得体会

    本来平时记录的都是一些技术点的学习和使用 今天打算记录一下学习方法 当然不一定适合所有人 因人而异 仅供参考 学习这件事 对于IT行业来说 真的是活到老学到老 技术的更新迭代速度非常快 而且总是有那么一些公司特别的卷 没办法 改变不了外因
  • 天猫数据分析工具推荐(天猫第三方数据平台)

    在电商迅速发展的大背景下 做好天猫数据分析能够在多方面帮助品牌商家更好地运营店铺 塑造品牌 如通过数据分析了解消费者的需求 购买偏好 这有利于品牌商家及时调整商品结构 产品推广 商品宣传等等 灵活制定品牌的销售策略 那么 天猫平台行业 品牌
  • 从外卖员到程序员,自学3年终于转行成功,三面“拿下”拼多多

    前言 先来自我介绍 老家农村 家里好不容易把我送到大城市读书 大学非985 211 但在我们老家 能出一个本科大学生也是非常不容易的 因为农村信息的相对闭塞 我对大学专业一无所知 加上分数并非前茅 最后被调剂一个我并不喜欢的专业 这里就不透
  • Java 学习路线 2024 最新版!

    又对上次分享的 Java 学习路线进行了简单修改完善 并增加了免登录下载和黑夜模式 这里重发一下 花了一个月零碎的时间 我根据当下 Java 后端求职和招聘的最新要求 对之前写的 Java 后端学习路线进行了全面的优化和改进 添加图片注释
  • 医疗机构如何释放数据要素价值 推动数据资产化

    在智慧医院建设加速的今天 数据已经成为医疗机构核心的资产之一 无论是基于数据的智慧运营决策 还是医疗AI模型训练与推理 都需要大规模数据的利用 在近日国家数据局等17部门联合印发的 数据要素 三年行动计划 2024 2026年 中 要求医疗
  • API接口:技术、应用与实践

    随着数字化时代的到来 API接口在软件开发和数据交互中扮演着越来越重要的角色 本文深入探讨了API接口的基本概念 技术原理 设计方法 最佳实践以及在各行业的应用案例 关键词 API接口 软件开发 数据交互 技术原理 设计方法 一 引言 随着
  • 阿里技术官亲笔力作:Kafka限量笔记,一本书助你掌握Kafka的精髓

    前言 分布式 堪称程序员江湖中的一把利器 无论面试还是职场 皆是不可或缺的技能 而Kafka 这款分布式发布订阅消息队列的璀璨明珠 其魅力之强大 无与伦比 对于Kafka的奥秘 我们仍需继续探索 要论对Kafka的熟悉程度 恐怕阿里的大佬们
  • 心灵与大脑的沟通:如何让大脑更好地理解我们的情感

    1 背景介绍 心理学和人工智能之间的界限已经不断模糊化 尤其是在情感智能方面 情感智能是一种新兴的人工智能技术 旨在让计算机更好地理解和回应人类的情感 这篇文章将探讨如何让大脑更好地理解我们的情感 以及在这个过程中涉及的核心概念 算法原理
  • spark相关

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 提示 这里可以添加本文要记录的大概内容 例如 随着人工智能的不断发展 机器学习这门
  • 扬帆证券:产业化破题在即 人形机器人超预期演进

    大模型助力下的拐点 特斯拉A股产业链上 两笔重磅出资几乎一起现身 总规划超百亿元 1月4日 拓普集团公告 与宁波经济技能开发区办理委员会签署了 机器人电驱系统研发生产基地项目出资协议书 公司拟出资50亿元 建设机器人核心部件生产基地 此次出
  • 白帽子如何快速挖到人生的第一个漏洞 | 购物站点挖掘商城漏洞

    本文针对人群 很多朋友们接触安全都是通过书籍 网上流传的PDF 亦或是通过论坛里的文章 但可能经过了这样一段时间的学习 了解了一些常见漏洞的原理之后 对于漏洞挖掘还不是很清楚 甚至不明白如何下手 可能你通过 sql labs 初步掌握了sq
  • 2024年金三银四网络安全考试试题

    2023年金三银四网络安全考试试题 1 关于数据使用说法错误的是 A 在知识分享 案例中如涉及客户网络数据 应取敏感化 不得直接使用 B 在公开场合 公共媒体等谈论 传播或发布客户网络中的数据 需获得客户书面授权或取敏感化 公开渠道获得的除
  • 什么是充放电振子理论?

    CHAT回复 充放电振子模型 Charging Reversal Oscillator Model 是一种解释ENSO现象的理论模型 这个模型把ENSO现象比喻成一个 热力学振荡系统 在这个模型中 ENSO现象由三个组成部分 充电 Char
  • 扬帆证券:突发利好!外资重大转变,A股收到多份喜报

    A股财报季 利好音讯密集传来 1月16日晚间 A股多家上市公司披露了成绩预告 其间成绩预增 扭亏等利好公告数量占比超80 其间 普瑞眼科公告 估计2023年净赢利同比添加高达1163 98 1285 51 别的 多家上市公司公告称 估计20
  • 扬帆证券:三只松鼠去年扣非净利预增超1.4倍

    在 高端性价比 战略驱动下 三只松鼠 300783 重拾增势 1月15日晚间 三只松鼠发布成绩预告 预计2023年度净赢利为2亿元至2 2亿元 同比增加54 97 至70 47 扣非后净赢利为1亿元至1 1亿元 同比增速达146 9 至17
  • 独家 | 鸿蒙(HarmonyOS)开发详细学习笔记免费分享

    前言 华为宣布 将在1月18日 在北京 上海 杭州 南京 成都 厦门 武汉 长沙 8 大城市同时召开大会 届时将揭秘鸿蒙生态和 HarmonyOS NEXT 进阶新篇章 简单的来说就是 纯血鸿蒙系统 即将彻底揭晓 鸿蒙系统自推出来以来 就一
  • AI在保护环境、应对气候变化中的作用

    对于AI生命周期数据领域的全球领导者而言 暂时搁置我们惯常的AI见解和AI生命周期数据内容产出 来认识诸如世界地球日这样的自然环境类活动日 似乎是个奇怪的事情 我们想要知道 数据是否真的会影响我们的地球环境 简而言之 是 确实如此 但作为一
  • 【产品兼容认证】WhaleStudio 成功兼容TiDB数据库软件

    平凯星辰和白鲸开源宣布成功完成产品兼容认证 北京 2023年12月27日 平凯星辰 北京 科技有限公司 以下简称平凯星辰 旗下的 TiDB 产品与白鲸开源的 WhaleStudio 已成功完成产品兼容性认证 这一重要合作旨在为全球客户提供更
  • 手把手教你使用HarmonyOS本地模拟器

    我们通过下面的动图来回顾下手机本地模拟器的使用效果 本期 我们将为大家介绍HarmonyOS本地模拟器的版本演进 并手把手教大家使用HarmonyOS本地模拟器 一 本地模拟器的版本演进 2021年12月31日 经过一个版本的迭代优化 随D
  • 两个月进口猛增10倍,买近百台光刻机,难怪ASML不舍中国市场

    据统计数据显示 2023年11月和12月 中国从荷兰进口的光刻机设备同比猛增10倍 进口金额超过19亿美元 让ASML赚得盆满钵满 ASML早前表示中国客户在2023年订购的光刻机全数交付 2023年11月中国进口的光刻机达到42台 进口金

随机推荐

  • Java实现从FTP获取文件下载到本地,并读取文件中的内容的成功案例

    package com aof web servlet import java io BufferedReader import java io File import java io FileInputStream import java
  • 考虑virtual函数以外的其他选择——条款35

    假设你正在写一个视频游戏软件 你打算为游戏内的人物设计一个继承体系 你的游戏术语暴力砍杀类型 剧中人物被伤害或因其他因素而降低健康状态的情况并不罕见 你因此决定提供一个成员函数healthValue 它会返回一个整数 表示人物的健康程度 由
  • 15个变态的Google面试题以及答案

    在当前经济形势不景气的情况下 谷歌招聘新员工是一件令人振奋的事 特别是对那些在当前金融风暴中渴望找到安全港的年轻经理们和软件开发商们来说是个好消息 不过 也不要高兴太早 谷歌在招聘新员工时 更加青睐名牌大学的学生 即便你是人到中年 招聘时谷
  • 利用顺序栈判断一个字符串是否是对称串

    利用一个顺序栈 判断一个字符串是否是对称串 所谓对称串是指从左向右读和从右向左读的序列相同 有些类似上一篇博客所说的回文 解题思路 对于字符串str 先将其所有元素进栈 从头开始扫描str 同时出栈元素 将出栈元素与从头开始扫描的str元素
  • 第五章 Linux磁盘与文件系统管理

    目录 认识EXT2文件系统 EXT2 EXT3文件的访问与日志文件系统的功能 VFS 文件系统的简单操作 磁盘与目录的容量 df du 连接文件 ln 硬连接和复制的区别 磁盘的分区 格式化 检验与挂载 磁盘分区 fdisk 磁盘检查 fs
  • Python网络编程:socket包的用法

    持续补充 1 网络编程 网络编程 主要用于两台或多台计算机之间的通信 也可以是同一台计算机内不同进程之间的通信 Socket套接字可以用来实现网络通信 关于Socket套接字 需要注意以下几点 Socket是网络通信中应用层和传输层之间的一
  • dbnet ICPAR2015数据格式json制作

    dbnet json格式制作 说明 json格式 代码 说明 dbnet代码选自https github com WenmuZhou DBNet pytorch 训练时数据集信息以json导入 但是官方ICPAR2015数据集下载数据是tx
  • 20个经典模拟电路(详细图文)

    为电子工程师的你 已经掌握了多少模拟电路呢 还应该掌握多少图纸和原理呢 本文列举了20个最常见的电路 并粗略的推断出不同层次的发烧对线路的不同理解程度 快来对照看看你是哪个程度的电子工程师 一 桥式整流电路 注意要点 1 二极管的单向导电性
  • Django 快速搭建博客 第七节(文章详情页,markdown语法)

    版权声明 更多最新原创文章请访问 最新原创主页 更多最全原创文章请访问 更多原创主页 上一节我们把真正的index html显示出来了 只是一个摘要 接下来我们要做的是当我们点击标题或者继续阅读的时候 会打开详情页 1 base html的
  • 页面之间传递值方法

    方式1 在接收页 的html代码里加上一行 WebForm1 fp WebForm1 Context Handler this TextBox1 Text fp name name 是第一页的public变量 Context 提供对整个当前
  • Kotlin中的字符串比较

    在java中 执行比较运算的操作一般有 和 equals 两个操作符 而且在java中 比较的是两个字符串的引用是否相同 而 equals 则是比较两个字符串的内容是否相同 我们可以通过一个例子来进行一下验证 String a1 ab St
  • devops理论梳理

    devops理论梳理 是什么 维基百科解释 DevOps 开发 Development 与运维 Operations 的组合词 是一种文化 一场运动或实践 强调在自动化软件交付流程及基础设施变更过程中 软件 开发人员与其他信息技术 IT 专
  • 寄存器、缓存、内存之间的区别

    简单的讲 寄存器就是CPU直接拿他里面寄存的东西直接来进行计算操作的 它就是CPU的一部分 寄存器是有限存贮容量的高速存贮部件 它们可用来暂存指令 数据和位址 在中央处理器的控制部件中 包含的寄存器有指令寄存器 IR 和程序计数器 PC 在
  • Web3创新者之夜,与其他开发者一同畅谈波卡生态

    Token2049在即 许多开发者都将在9月中齐聚新加坡 一同讨论区块链生态发展及未来 届时将会有超过1万名与会者 并有超过300个赞助商和项目协助支持本次大会 波卡作为跨链互操作性的龙头生态也将参与至本次盛会之中 为了让波卡社区的成员 贡
  • vue中表单验证特殊字符、数据大小长度、经纬度、数组

    vue中表单验证特殊字符 数据大小长度 经纬度 数组 验证不可输入特殊字符 param rule param value param callback export function checkInputString rule value
  • SQL语言中的连接表

    连接表 包括 内连接 左连接 右连接 全外连接 交叉连接 1 内连接 内连接是SQL Server中最常用的连接之一 内部联接子句用于查询来自两个或多个相关表的数据 语法 在from子句中指定主表 t1 在 inner join 子句和连接
  • fork函数全解析

    从最简单 基础 的一个例子说起 应该说是最基础而不是简单 下面的这个最基础的例子其实并不简单 因为有很多细节 我们需要从fork函数的定义开始说起 man 手册官方定义 this function creates a new process
  • 建立安全连接失败连接到 120.79.9.99:9200 时发生错误。SSL 接收到一个超出最大准许长度的记录。错误代码:SSL_ERROR_RX_RECORD_TOO_LONG

    建立安全连接失败 连接到 120 79 9 99 9200 时发生错误 SSL 接收到一个超出最大准许长度的记录 错误代码 SSL ERROR RX RECORD TOO LONG 因为我是用https访问的需要安全协议只要改为http就行
  • docker运行有数据库连接的springboot项目无法访问

    docker run name tsb p 8808 8080 d testspringboot 8808 8888 前一个数值是访问路径的端口号 后一个数值是上传的jar包中项目设定的端口号 默认8080 访问测试 测试地址 http 1
  • Spark学习笔记(二)

    以下笔记基于对尚硅谷spark教程的学习 Spark版本3 0 目录 什么是 RDD RDD核心属性 执行原理 基础编程 创建环境 RDD 创建 从集合中创建RDD 从文件中创建RDD RDD 转换算子 Value 类型 双Value类型