Spark之RDD学习笔记

一、Spark简介

  Apache Spark 是一个快速的, 多用途的集群计算系统, 相对于 Hadoop MapReduce 将中间结果保存在磁盘中, Spark 使用了内存保存中间结果, 能在数据尚未写入硬盘时在内存中进行运算,比hadoop快100倍
  Spark 只是一个计算框架, 不像 Hadoop 一样包含了分布式文件系统和完备的调度系统, 如果要使用 Spark, 需要搭载其它的文件系统和更成熟的调度系统
优点,速度快,易用,通用,兼容

二、spark组件

1、Spark-Core 和 弹性分布式数据集(RDDs)

  Spark-Core 是整个 Spark 的基础, 提供了分布式任务调度和基本的 I/O 功能
  Spark 的基础的程序抽象是弹性分布式数据集(RDDs), 是一个可以并行操作, 有容错的数据集合
  RDDs 可以通过引用外部存储系统的数据集创建(如HDFS, HBase), 或者通过现有的 RDDs 转换得到
  RDDs 抽象提供了 Java, Scala, Python 等语言的API
  RDDs 简化了编程复杂性, 操作 RDDs 类似通过 Scala 或者 Java8 的 Streaming 操作本地数据集合

2、Spark SQL

  Spark SQL 在 spark-core 基础之上带出了一个名为 DataSet 和 DataFrame 的数据抽象化的概念
  Spark SQL 提供了在 Dataset 和 DataFrame 之上执行 SQL 的能力
  Spark SQL 提供了 DSL, 可以通过 Scala, Java, Python 等语言操作 DataSet 和 DataFrame
  它还支持使用 JDBC/ODBC 服务器操作 SQL 语言

3、Spark Streaming

  Spark Streaming 充分利用 spark-core 的快速调度能力来运行流分析
  它截取小批量的数据并可以对之运行 RDD Transformation
  它提供了在同一个程序中同时使用流分析和批量分析的能力

4、MLlib

  MLlib 是 Spark 上分布式机器学习的框架. Spark分布式内存的架构 比 Hadoop磁盘式 的 Apache Mahout 快上 10 倍, 扩展性也非常优良
  MLlib 可以使用许多常见的机器学习和统计算法, 简化大规模机器学习
  汇总统计, 相关性, 分层抽样, 假设检定, 随即数据生成
  支持向量机, 回归, 线性回归, 逻辑回归, 决策树, 朴素贝叶斯
  协同过滤, ALS
  K-means
  SVD奇异值分解, PCA主成分分析
  TF-IDF, Word2Vec, StandardScaler
  SGD随机梯度下降, L-BFGS

5、GraphX

  GraphX 是分布式图计算框架, 提供了一组可以表达图计算的 API, GraphX 还对这种抽象化提供了优化运行

三、Spark core之RDD理解

  RDD全称为 Resilient Distributed Datasets, 是一个容错的, 并行的数据结构, 可以让用户显式地将数据存储到磁盘和内存中, 并能控制数据的分区。

1、特点

  1>、RDD 是一个编程模型
    RDD 允许用户显式的指定数据存放在内存或者磁盘
    RDD 是分布式的, 用户可以控制 RDD 的分区
    RDD 提供了丰富的操作
    RDD 提供了 map, flatMap, filter 等操作符, 用以实现 Monad 模式
    RDD 提供了 reduceByKey, groupByKey 等操作符, 用以操作 Key-Value 型数据
    RDD 提供了 max, min, mean 等操作符, 用以操作数字型的数据
  2>、RDD 是混合型的编程模型, 可以支持迭代计算, 关系查询, MapReduce, 流计算
  3>、RDD 是只读的
  4>、RDD 之间有依赖关系, 根据执行操作的操作符的不同, 依赖关系可以分为宽依赖和窄依赖
  5>、RDD 是可以容错的
    RDD 的容错有两种方式
      保存 RDD 之间的依赖关系, 以及计算函数, 出现错误重新计算
      直接将 RDD 的数据存放在外部存储系统, 出现错误直接读取, Checkpoint

2、创建RDD

  程序入口 SparkContext

val conf = new SparkConf().setMaster("local[2]")
val sc: SparkContext = new SparkContext(conf)

  RDD有三种创建方式

//1、通过本地集合直接创建 RDD
val conf = new SparkConf().setMaster("local[2]")
val sc = new SparkContext(conf)
val list = List(1, 2, 3, 4, 5, 6)
val rddParallelize = sc.parallelize(list, 2)
val rddMake = sc.makeRDD(list, 2)

//2、通过读取外部文件创建 RDD
val conf = new SparkConf().setMaster("local[2]")
val sc = new SparkContext(conf)
val source: RDD[String] = sc.textFile("hdfs://node01:8020/dataset/wordcount.txt")
/*注:sc.textFile访问方式
 *    支持访问文件夹, 例如 sc.textFile("hdfs:///dataset")
 *    支持访问压缩文件, 例如 sc.textFile("hdfs:///dataset/words.gz")
 *    支持通过通配符访问, 例如 sc.textFile("hdfs:///dataset/*.txt")
 *注:分区
 *    默认情况下读取 HDFS 中文件的时候, 每个 HDFS 的 block 对应一个 RDD 的 partition, block 的默认是128M
 *    通过第二个参数, 可以指定分区数量, 例如 sc.textFile("hdfs://node01:8020/dataset/wordcount.txt", 2)
 *    如果通过第二个参数指定了分区, 这个分区数量一定不能小于'block'数
 */
//3、通过其它的 RDD 衍生新的 RDD
val conf = new SparkConf().setMaster("local[2]")
val sc = new SparkContext(conf)
val source: RDD[String] = sc.textFile("hdfs://node01:8020/dataset/wordcount.txt", 2)
val words = source.flatMap { line => line.split(" ") }

3、RDD算子

  RDD 中的算子从功能上分为两大类
    1、Transformation(转换) 它会在一个已经存在的 RDD 上创建一个新的 RDD, 将旧的 RDD 的数据转换为另外一种形式后放入新的 RDD
    2、Action(动作) 执行各个分区的计算任务, 将的到的结果返回到 Driver 中
      ---执行 RDD 的时候, 在执行到转换操作的时候, 并不会立刻执行, 直到遇见了 Action 操作, 才会触发真正的执行, 这个特点叫做 惰性求值
      ---默认情况下, 每一个 Action 运行的时候, 其所关联的所有 Transformation RDD 都会重新计算, 但是也可以使用 presist 方法将 RDD 持久化到磁盘或者内存中. 这个时候为了下次可以更快的访问,     会把数据保存到集群上.

  Transformations 算子

  1、map算子-------------------------------常用算子

//map(T => U)算子,把 RDD 中的数据 一对一 的转为另一种形式
sc.parallelize(Seq(1, 2, 3)).map( num => num * 10 ).collect()

  2、flatMap算子-------------------------------常用算子

//flatMap(T => List[U])算子,FlatMap 算子和 Map 算子类似, 但是 FlatMap 是一对多,flatMap 其实是两个操作, 是 map + flatten, 也就是先转换, 后把转换而来的 List 展开
sc.parallelize(Seq("Hello lily", "Hello lucy", "Hello tim")).flatMap(line => line.split(" ")).collect()

  3、filter算子-------------------------------常用算子

//filter(T => Boolean)算子,Filter 算子的主要作用是过滤掉不需要的内容
sc.parallelize(Seq(1, 2, 3)).filter(value => value >= 3).collect()

  4、mapPartitions(List[T] => List[U]),RDD[T] => RDD[U] 和 map 类似, 但是针对整个分区的数据转换

  5、mapPartitionsWithIndex,和 mapPartitions 类似, 只是在函数中增加了分区的 Index

  6、mapValues算子

//mapValues,MapValues 只能作用于 Key-Value 型数据, 和 Map 类似, 也是使用函数按照转换数据, 不同点是 MapValues 只转换 Key-Value 中的 Value
sc.parallelize(Seq(("a", 1), ("b", 2), ("c", 3))).mapValues(value => value * 10).collect()

  7、sample算子

//sample(withReplacement, fraction, seed),Sample 算子可以从一个数据集中抽样出来一部分, 常用作于减小数据集以保证运行速度, 并且尽可能少规律的损失
/*    参数:
    Sample 接受第一个参数为`withReplacement`, 意为是否取样以后是否还放回原数据集供下次使用, 简单的说, 如果这个参数的值为 true, 则抽样出来的数据集中可能会有重复
    Sample 接受第二个参数为`fraction`, 意为抽样的比例
    Sample 接受第三个参数为`seed`, 随机数种子, 用于 Sample 内部随机生成下标, 一般不指定, 使用默认值
*/
sc.parallelize(Seq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).sample(withReplacement = true, 0.6, 2).collect()

  8、union算子

//union(other)算子,该算子是取得两个RDD的并集
val rdd1 = sc.parallelize(Seq(1, 2, 3))
val rdd2 = sc.parallelize(Seq(4, 5, 6))
rdd1.union(rdd2).collect()

  9、intersection算子

//intersection(other)算子,该算子是取得两个RDD的交集
val rdd1 = sc.parallelize(Seq(1, 2, 3, 4, 5))
val rdd2 = sc.parallelize(Seq(4, 5, 6, 7, 8))
rdd1.intersection(rdd2).collect()

  10、subtract(other, numPartitions)算子,差集可设置分区数

  11、distinct算子

//distinct(numPartitions)算子,去重,是一个需要 Shuffled 的操作,本质上 Distinct 就是一个 reductByKey, 把重复的合并为一个
sc.parallelize(Seq(1, 1, 2, 2, 3)).distinct().collect()

  12、reduceByKey算子---------------------------------常用算子

//reduceByKey(func: (V, V) => V): RDD[(K, V)]算子,首先按照 Key 分组生成一个 Tuple, 然后针对每个组执行 reduce 算子
/*参数:
    func → 执行数据处理的函数, 传入两个参数, 一个是当前值, 一个是局部汇总, 这个函数需要有一个输出, 输出就是这个 Key 的汇总结果
  注:
    ReduceByKey 只能作用于 Key-Value 型数据, Key-Value 型数据在当前语境中特指 Tuple2
    ReduceByKey 是一个需要 Shuffled 的操作
    和其它的 Shuffled 相比, ReduceByKey是高效的, 因为类似 MapReduce 的, 在 Map 端有一个 Cominer, 这样 I/O 的数据便会减少
*/
sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1))).reduceByKey( (curr, agg) => curr + agg ).collect()

  13、groupByKey算子---------------------------------常用算子

//groupByKey()算子,GroupByKey 算子的主要作用是按照 Key 分组, 和 ReduceByKey 有点类似, 但是 GroupByKey 并不求聚合, 只是列举 Key 对应的所有 Value
/*注:
    GroupByKey 是一个 Shuffled
    GroupByKey 和 ReduceByKey 不同, 因为需要列举 Key 对应的所有数据, 所以无法在 Map 端做 Combine, 所以 GroupByKey 的性能并没有 ReduceByKey 好
*/
sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1))).groupByKey().collect()

  14、combineByKey算子

//combineByKey(createCombiner, mergeValue, mergeCombiners, [partitioner], [mapSideCombiner], [serializer])算子,对数据集按照 Key 进行聚合
/*参数:
    createCombiner 将 Value 进行初步转换
    mergeValue 在每个分区把上一步转换的结果聚合
    mergeCombiners 在所有分区上把每个分区的聚合结果聚合
    partitioner 可选, 分区函数
    mapSideCombiner 可选, 是否在 Map 端 Combine
    serializer 序列化器
 注:
    combineByKey 的要点就是三个函数的意义要理解
    groupByKey, reduceByKey 的底层都是 combineByKey
*/
val rdd = sc.parallelize(Seq(
  ("zhangsan", 99.0),
  ("zhangsan", 96.0),
  ("lisi", 97.0),
  ("lisi", 98.0),
  ("zhangsan", 97.0))
)
val combineRdd = rdd.combineByKey(
  score => (score, 1),
  (scoreCount: (Double, Int),newScore) => (scoreCount._1 + newScore, scoreCount._2 + 1),
  (scoreCount1: (Double, Int), scoreCount2: (Double, Int)) =>
    (scoreCount1._1 + scoreCount2._1, scoreCount1._2 + scoreCount2._2)
)
val meanRdd = combineRdd.map(score => (score._1, score._2._1 / score._2._2))
meanRdd.collect()

  15、aggregateByKey算子

//aggregateByKey(zeroValue)(seqOp, combOp)算子,按照 Key 聚合 Value
/*参数:
    zeroValue 初始值
    seqOp 转换每一个值的函数
    comboOp 将转换过的值聚合的函数
 注:
    和 reduceByKey 的区别::
        aggregateByKey 最终聚合结果的类型和传入的初始值类型保持一致
        reduceByKey 在集合中选取第一个值作为初始值, 并且聚合过的数据类型不能改变
*/
val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.aggregateByKey(0.8)(seqOp = (zero, price) => price * zero,combOp = (curr, agg) => curr + agg).collect()
println(result)

  16、foldByKey算子

//foldByKey(zeroValue)(func)算子,和 ReduceByKey 是一样的, 都是按照 Key 做分组去求聚合, 但是 FoldByKey 的不同点在于可以指定初始值
/*参数:
    zeroValue 初始值
    func seqOp 和 combOp 相同, 都是这个参数
 注:
    FoldByKey 是 AggregateByKey 的简化版本, seqOp 和 combOp 是同一个函数
    FoldByKey 指定的初始值作用于每一个 Value
*/
sc.parallelize(Seq(("a", 1), ("a", 1), ("b", 1))).foldByKey(zeroValue = 10)( (curr, agg) => curr + agg ).collect()

  17、join算子

//join(other, numPartitions)算子,将两个 RDD 按照相同的 Key 进行连接
val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("b", 1)))
val rdd2 = sc.parallelize(Seq(("a", 10), ("a", 11), ("a", 12)))
rdd1.join(rdd2).collect()

  18、cogroup算子

//cogroup(rdd1, rdd2, rdd3, [partitioner or numPartitions])算子,多个 RDD 协同分组, 将多个 RDD 中 Key 相同的 Value 分组
/*参数:
    rdd…​ 最多可以传三个 RDD 进去, 加上调用者, 可以为四个 RDD 协同分组
    partitioner or numPartitions 可选, 可以通过传递分区函数或者分区数来改变分区
 注:
    对 RDD1, RDD2, RDD3 进行 cogroup, 结果中就一定会有三个 List, 如果没有 Value 则是空 List, 这一点类似于 SQL 的全连接, 返回所有结果, 即使没有关联上
    CoGroup 是一个需要 Shuffled 的操作
*/
val rdd1 = sc.parallelize(Seq(("a", 1), ("a", 2), ("a", 5), ("b", 2), ("b", 6), ("c", 3), ("d", 2)))
val rdd2 = sc.parallelize(Seq(("a", 10), ("b", 1), ("d", 3)))
val rdd3 = sc.parallelize(Seq(("b", 10), ("a", 1)))
val result1 = rdd1.cogroup(rdd2).collect()
val result2 = rdd1.cogroup(rdd2, rdd3).collect()

  19、cartesian(other)(RDD[T], RDD[U]) => RDD[(T, U)] 生成两个 RDD 的笛卡尔积

  20、sortBy算子

//sortBy(func, ascending, numPartitions)算子,排序相关的算子有两个, 一个是sortBy, 另外一个是sortByKey
/*参数:
    func 通过这个函数返回要排序的字段
    ascending 是否升序
    numPartitions 分区数
 注:
    普通的 RDD 没有sortByKey, 只有 Key-Value 的 RDD 才有
    sortBy可以指定按照哪个字段来排序, sortByKey直接按照 Key 来排序
*/
val rdd1 = sc.parallelize(Seq(("a", 3), ("b", 2), ("c", 1)))
val sortByResult = rdd1.sortBy( item => item._2 ).collect()
val sortByKeyResult = rdd1.sortByKey().collect()
println(sortByResult)
println(sortByKeyResult)

  21、partitionBy(partitioner)算子,使用用传入的 partitioner 重新分区, 如果和当前分区函数相同, 则忽略操作

  22、coalesce算子

//coalesce(numPartitions, shuffle)算子,一般涉及到分区操作的算子常见的有两个, repartitioin 和 coalesce, 两个算子都可以调大或者调小分区数量
/*参数:
    numPartitions 新的分区数
    shuffle 是否 shuffle, 如果新的分区数量比原分区数大, 必须 Shuffled, 否则重分区无效
 注:
    repartition 和 coalesce 的不同就在于 coalesce 可以控制是否 Shuffle
    repartition 是一个 Shuffled 操作
*/
val rdd = sc.parallelize(Seq(("a", 3), ("b", 2), ("c", 1)))
val oldNum = rdd.partitions.length
val coalesceRdd = rdd.coalesce(4, shuffle = true)
val coalesceNum = coalesceRdd.partitions.length
val repartitionRdd = rdd.repartition(4)
val repartitionNum = repartitionRdd.partitions.length
print(oldNum, coalesceNum, repartitionNum)

  23、repartition(numPartitions)算子,重新分区

  24、repartitionAndSortWithinPartitions算子,重新分区的同时升序排序, 在partitioner中排序, 比先重分区再排序要效率高, 建议使用在需要分区后再排序的场景使用

  Action 算子

  1、reduce算子----------------------常用算子

//reduce( (currValue[T], agg[T]) => T )算子,对整个结果集规约, 最终生成一条数据, 是整个数据集的汇总
/*注:
    reduce 和 reduceByKey 是完全不同的, reduce 是一个 action, 并不是 Shuffled 操作
    本质上 reduce 就是现在每个 partition 上求值, 最终把每个 partition 的结果再汇总
*/
val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.reduce((curr, agg) => ("总价", curr._2 + agg._2))
println(result)

  2、collect、count、first、take等算子-------------------------------------------------常用算子

//collect()算子,以数组的形式返回数据集中所有元素
//count()算子,返回元素个数
//first()算子,返回第一个元素
//take( N )算子,返回前 N 个元素
val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
// 结果: Array((手机,10.0), (手机,15.0), (电脑,20.0))
println(rdd.collect())
// 结果: Array((手机,10.0), (手机,15.0))
println(rdd.take(2))
// 结果: (手机,10.0)
println(rdd.first())
 //takeSample(withReplacement, fract)算子,类似于 sample, 区别在这是一个Action, 直接返回结果

//fold(zeroValue)( (T, T) => U )算子,指定初始值和计算函数, 折叠聚合整个数据集

//saveAsTextFile(path)算子,将结果存入 path 对应的文件中

//saveAsSequenceFile(path)算子,将结果存入 path 对应的 Sequence 文件中

  3、countByKey算子-------------------------------------------------常用算子

//countByKey()算子,求得整个数据集中 Key 以及对应 Key 出现的次数----------------------------------------------------------------------------常用算子
/*注:
    返回结果为 Map(key → count)
    常在解决数据倾斜问题时使用, 查看倾斜的 Key
*/
val rdd = sc.parallelize(Seq(("手机", 10.0), ("手机", 15.0), ("电脑", 20.0)))
val result = rdd.countByKey()
println(result)

//foreach( T => …​ )算子,遍历每一个元素
  RDD 对不同类型数据的支持

    对于以字符串为代表的基本数据类型是比较基础的一些的操作, 诸如 map, flatMap, filter 等基础的算子
    对于键值对类型的数据, 有额外的支持, 诸如 reduceByKey, groupByKey 等 byKey 的算子
    同样对于数字型的数据也有额外的支持, 诸如 max, min 等

    RDD 对键值对数据的额外支持

      聚合操作
        reduceByKey
        foldByKey
        combineByKey
      分组操作
        cogroup
        groupByKey
      连接操作
        join
        leftOuterJoin
        rightOuterJoin
      排序操作
        sortBy
        sortByKey
        Action
        countByKey
        take
        collect

    RDD 对数字型数据的额外支持
      count      个数
      mean      均值
      sum       求和
      max     最大值
      min      最小值
      variance    方差
      sampleVariance    从采样中计算方差
      stdev         标准差
      sampleStdev    采样的标准差

  RDD 的 Shuffle 和分区

    分区作用

      RDD 使用分区来分布式并行处理数据, 并且要做到尽量少的在不同的 Executor 之间使用网络交换数据, 所以当使用 RDD 读取数据的时候, 会尽量的在物理上靠近数据源, 比如说在读取 Cassandra     或者 HDFS 中数据的时候, 会尽量的保持 RDD 的分区和数据源的分区数, 分区模式等一一对应

    分区和 Shuffle 的关系
      分区的主要作用是用来实现并行计算, 本质上和 Shuffle 没什么关系, 但是往往在进行数据处理的时候, 例如`reduceByKey`, `groupByKey`等聚合操作, 需要把 Key 相同的 Value 拉取到一起进行计算,     这个时候因为这些 Key 相同的 Value 可能会坐落于不同的分区,这时就需要shuffle。

    Spark 中的 Shuffle 操作的特点
      只有 Key-Value 型的 RDD 才会有 Shuffle 操作, 例如 RDD[(K, V)], 但是有一个特例, 就是 repartition 算子可以对任何数据类型 Shuffle

//创建RDD指定分区
scala> val rdd1 = sc.parallelize(1 to 100, 6)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[1] at parallelize at <console>:24

scala> rdd1.partitions.size
res1: Int = 6

scala> val rdd2 = sc.textFile("hdfs:///dataset/wordcount.txt", 6)
rdd2: org.apache.spark.rdd.RDD[String] = hdfs:///dataset/wordcount.txt MapPartitionsRDD[3] at textFile at <console>:24

scala> rdd2.partitions.size
res2: Int = 7
/*注:
    rdd1 是通过本地集合创建的, 创建的时候通过第二个参数指定了分区数量. rdd2 是通过读取 HDFS 中文件创建的, 同样通过第二个参数指定了分区数, 因为是从 HDFS 中读取文件, 
  所以最终的分区数是由 Hadoop 的 InputFormat 来指定的, 所以比指定的分区数大了一个.
*/
  Shuffle作用
val sourceRdd = sc.textFile("hdfs://node01:9020/dataset/wordcount.txt")
val flattenCountRdd = sourceRdd.flatMap(_.split(" ")).map((_, 1))
val aggCountRdd = flattenCountRdd.reduceByKey(_ + _)
val result = aggCountRdd.collect

/*注:
    reduceByKey 这个算子本质上就是先按照 Key 分组, 后对每一组数据进行 reduce, 所面临的挑战就是 Key 相同的所有数据可能分布在不同的 Partition 分区中, 甚至可能在不同的节点中, 但是它们必须被共同计算.
    为了让来自相同 Key 的所有数据都在 reduceByKey 的同一个 reduce 中处理, 需要执行一个 all-to-all 的操作, 需要在不同的节点(不同的分区)之间拷贝数据, 必须跨分区聚集相同 Key 的所有数据, 这个过程叫做 Shuffle.
*/

4、缓存

  1、cache缓存,其实是persist()方法的别名
    RDD.cache()

  2、persist缓存
    RDD.persist(newLevel: StorageLevel) newLevel是缓存的级别,例如RDD.persist(StorageLevel.MEMORY_ONLY)

  3、清理缓存
    RDD.unpersist()

  4、缓存级别的枚举

5、Checkpoint

  作用

    Checkpoint 的主要作用是斩断 RDD 的依赖链, 并且将数据存储在可靠的存储引擎中, 例如支持分布式存储和副本机制的 HDFS

  使用Checkpoint

val conf = new SparkConf().setMaster("local[6]").setAppName("debug_string")
val sc = new SparkContext(conf)
sc.setCheckpointDir("checkpoint")     //指定CheckPoint的存储路径

val interimRDD = sc.textFile("dataset/access_log_sample.txt")
  .map(item => (item.split(" ")(0), 1))
  .filter(item => StringUtils.isNotBlank(item._1))
  .reduceByKey((curr, agg) => curr + agg)
  .cache()   //细节,CheckPoint前先cache,若没有会被计算两次,一次是 checkpoint, 另外一次是 collect

interimRDD.checkpoint()         //开启CheckPoint

interimRDD.collect().foreach(println(_))

sc.stop()

6、worldcount的Demo代码

 

object WordCounts {
  def main(args: Array[String]): Unit = {
    //1、创建 Spark Context,spark-core的入口组件
    val conf = new SparkConf().setMaster("local[2]").setAppName("wordcount")
    val sc: SparkContext = new SparkContext(conf)

    //2、使用sc.textFile方法读取文件,并生成一个RDD
    val source: RDD[String] = sc.textFile("dataset/wordcount.txt", 2)

    //3、使用flatMap算子将读取到的每一行字符串打散成单词,并把每个单词组成新的行
    val words: RDD[String] = source.flatMap { line => line.split(" ") }
    
    //4、使用map算子将每个单词转成(word,1)的元组形式
    val wordsTuple: RDD[(String, Int)] = words.map { word => (word, 1) }
    
    //5、使用reduceByKey算子统计单词对应的频率
    val wordsCount: RDD[(String, Int)] = wordsTuple.reduceByKey(_+_)

    //6、查看执行结果
    println(wordsCount.collect.foreach(println))
  }
}

posted @ 2020-03-24 21:40  卩s丶Eric  阅读(492)  评论(0编辑  收藏  举报