Spark简介
快如闪电的统一分析引擎.
Apache Spark™是用于大规模数据处理的统一分析引擎。
发展历史
2009 年,Spark 诞生于伯克利大学的 AMPLab 实验室
2010 年,伯克利大学正式开源了 Spark 项目
2013 年 6 月,Spark 成为了 Apache 基金会下的项目
2014 年 2 月,Spark 以飞快的速度成为了 Apache 的顶级项目
2015 年至今,Spark 变得愈发火爆,大量的国内公司开始重点部署或者使用 Spark
核心模块及功能
Spark Core
Spark Core 中提供了 Spark 最基础与最核心的功能,Spark 其他的功能如:Spark SQL,
Spark Streaming,GraphX, MLlib 都是在 Spark Core 的基础上进行扩展的
Spark SQL
Spark SQL 是 Spark 用来操作结构化数据的组件。通过 Spark SQL,用户可以使用 SQL
或者 Apache Hive 版本的 SQL 方言(HQL)来查询数据。
Spark Streaming
Spark Streaming 是 Spark 平台上针对实时数据进行流式计算的组件,提供了丰富的处理
数据流的 API。
Spark MLlib
MLlib 是 Spark 提供的一个机器学习算法库。MLlib 不仅提供了模型评估、数据导入等
额外的功能,还提供了一些更底层的机器学习原语。
Spark GraphX
GraphX 是 Spark 面向图计算提供的框架与算法库。
环境准备
安装Hadoop\Spark环境
1.将Hadoop安装包和Spark安装包分别解压至合适的目录
2.在环境变量中配置HADOOP_HOME
3.将HADOOP_HOME引入Path
4.测试
在Spark安装目录中的bin目录下找到spark-shell.cmd双击运行
5.访问控制台
浏览器访问localhost:4040
创建Maven项目
打开IDEA创建Maven项目
更新pom文件,加入以下坐标,并更新maven配置等待下载完成.
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
邮件项目,选择Add Framework Support…
添加Scala代码模板
在main中创建scala目录,并标记为Sources Root目录
创建HelloSpark Object进行测试
package cn.tedu.spark.hello
import org.apache.spark.SparkContext
object HelloSpark {
def main(args: Array[String]): Unit = {
println("Hello Spark: " + SparkContext)
}
}
导入log4j.properties
resources目录下创建log4j.properties文件
log4j.rootCategory=ERROR, console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd
HH:mm:ss} %p %c{1}: %m%n
# Set the default spark-shell log level to ERROR. When running the spark-shell,
the
# log level for this class is used to overwrite the root logger's log level, so
that
# the user can have different defaults for the shell and regular Spark apps.
log4j.logger.org.apache.spark.repl.Main=ERROR
# Settings to quiet third party logs that are too verbose
log4j.logger.org.spark_project.jetty=ERROR
log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR
log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=ERROR
log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=ERROR
log4j.logger.org.apache.parquet=ERROR
log4j.logger.parquet=ERROR
# SPARK-9183: Settings to avoid annoying messages when looking up nonexistent
UDFs in SparkSQL with Hive support
log4j.logger.org.apache.hadoop.hive.metastore.RetryingHMSHandler=FATAL
log4j.logger.org.apache.hadoop.hive.ql.exec.FunctionRegistry=ERROR
WordCount
使用Spark实现WordCount
package cn.tedu.spark.core.wc
/**
* Spark入门案例_WordCound
*/
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object WordCount {
def main(args: Array[String]): Unit = {
//1.初始化配置
val sparkConf: SparkConf = new SparkConf().setMaster("local").setAppName("WordCount")
//2.创建Spark执行环境
val sc = new SparkContext(sparkConf)
//3.读取数据文件
val lines = sc.textFile("{datas/*}")
//4.拆分单词,扁平化处理,转化为Tuple
val wordTuple = lines.flatMap(_.split(" ")).map((_, 1))
//5.分组聚合
val result = wordTuple.reduceByKey(_ + _)
//6.从内存中收集结果并打印
result.collect().foreach(println)
//7.释放资源
sc.stop()
}
}
SparkCore
Spark 计算框架为了能够进行高并发和高吞吐的数据处理,封装了三大数据结构,用于
处理不同的应用场景。三大数据结构分别是:
RDD : 弹性分布式数据集
累加器:分布式共享只写变量
广播变量:分布式共享只读变量
RDD
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据
处理模型。代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行
计算的集合。
- 弹性:
存储的弹性:内存与磁盘的自动切换;
容错的弹性:数据丢失可以自动恢复;
计算的弹性:计算出错重试机制;
分片的弹性:可根据需要重新分片。 - 分布式:数据存储在大数据集群不同节点上
- 数据集:RDD 封装了计算逻辑,并不保存数据
- 数据抽象:RDD 是一个抽象类,需要子类具体实现
- 不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在
新的 RDD 里面封装计算逻辑 - 可分区、并行计算
RDD的创建/数据读取
package cn.tedu.spark.core.rdd
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 读取数据
*/
object SourceTest {
def main(args: Array[String]): Unit = {
//1.初始化配置
val conf = new SparkConf().setMaster("local[*]").setAppName("RDDSourceTest")
//2.创建Spark环境
val sc = new SparkContext(conf)
//3.从List中读取数据
val list = List(1,2,3,4,5)
// val source1: RDD[Int] = sc.parallelize(list)
val source1: RDD[Int] = sc.makeRDD(list)
//3.从文件中读取数据
val source2: RDD[String] = sc.textFile("{datas/*}")
//5.收集结果
source1.collect().foreach(s => println("source1: "+ s))
source2.collect().foreach(s => println("source2: "+ s))
//6.释放资源
sc.stop()
}
}
分区和并行度
package cn.tedu.spark.core.rdd
import org.apache.spark.{SparkConf, SparkContext}
/**
* 分区,并行度
*/
object RDDParTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("RDDParTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.处理数据
val list = List(1,2,3,4,5)
// val memSource = sc.makeRDD(list)
val memSource = sc.makeRDD(list,3)
// memSource.saveAsTextFile("result")
memSource.saveAsTextFile("result3")
//3.释放资源
sc.stop()
}
}
算子
所谓算子简言之就是将整个事件处理的整个过程拆分成多个阶段,每个阶段就是一个算子.Spark中的算子被分为两类,包括Transformations和Actions.
Transformations
map
map(func):返回一个新的分布式数据集,该数据集是通过将源的每个元素传递给函数func形成的。
package cn.tedu.spark.core.rdd.operator
import org.apache.spark.{SparkConf, SparkContext}
/**
* map
*/
object TransformationsTest {
def main(args: Array[String]): Unit = {
/*
map
进一出一
*/
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
//2.1.获取数据源
val list = List(1,2,3,4)
val source = sc.makeRDD(list)
//2.2.map
val result = source.map(_ * 2)
//2.3.收集数据并打印
result.collect().foreach(println)
//3.释放资源
sc.stop()
}
}
分区并行测试
package cn.tedu.spark.core.rdd.operator
/**
* map并行
*/
import org.apache.spark.{SparkConf, SparkContext}
object TransformationParTest {
def main(args: Array[String]): Unit = {
/*
map
进一出一
*/
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
//2.1.获取数据源
val list = List(1, 2, 3, 4)
/*
1.RDD的计算一个分区内数据是一个一个串行执行的,前边的数据完全执行完之后,后边的数据才会开始执行.
2.不同的分区之间数据计算是无序的(并行)
*/
// val source = sc.makeRDD(list)
// val source = sc.makeRDD(list,1)
val source = sc.makeRDD(list,2)
//2.2.map
val result = source.map(
num => {
println("A: " + num)
num
}
)
val result1 = result.map(
num => {
println("B: " + num)
}
)
//2.3.收集数据
result1.collect()
//3.释放资源
sc.stop()
}
}
mapPartitions
mapPartitions(func): 与map相似,但是分别在RDD的每个分区(块)上运行,因此func在类型T的RDD上运行时必须为Iterator => Iterator 类型。
功能上与map相同,但是性能上高与map,原因是读取整个分区数据之后统一计算,相当于批处理.
缺点是如果数据量大,内存资源小可能出错(内存资源不足).
package cn.tedu.spark.core.rdd.operator
/**
* mapPartitions
*/
import org.apache.spark.{SparkConf, SparkContext}
object TransformationTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val mapPartitionsList = List(1, 2, 3, 4)
val mapPartitionsSource = sc.makeRDD(mapPartitionsList,3)
val mapPartitionsResult = mapPartitionsSource.mapPartitions(
iter => {
println("这是一个单独的分区")
iter.map(_ * 10)
}
)
mapPartitionsResult.collect().foreach(println)
//3.释放资源
sc.stop()
}
}
mapPartitions和map的区别
➢ 数据处理角度
Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子
是以分区为单位进行批处理操作。
➢ 功能的角度
Map 算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。
MapPartitions 算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,
所以可以增加或减少数据
➢ 性能的角度
Map 算子因为类似于串行操作,所以性能比较低,而是 mapPartitions 算子类似于批处
理,所以性能较高。但是 mapPartitions 算子会长时间占用内存,那么这样会导致内存可能
不够用,出现内存溢出的错误。所以在内存有限的情况下,不推荐使用。使用 map 操作。
flatMap
flatMap(func): 与map相似,但是每个输入项都可以映射到0个或多个输出项(因此func应该返回Seq而不是单个项)。
package cn.tedu.spark.core.rdd.operator
import org.apache.spark.{SparkConf, SparkContext}
/**
* flatMap
*/
object TransformationsTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
val flatMapList = List("hello scala", "hello spark")
val flatMapSource = sc.makeRDD(flatMapList)
val flatMapResult = flatMapSource.flatMap(_.split(" "))
flatMapResult.collect().foreach(println)
//3.释放资源
sc.stop()
}
}
glom
glom(): 将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
package cn.tedu.spark.core.rdd.operator
import org.apache.spark.{SparkConf, SparkContext}
/**
* glom
*/
object TransformationGlomTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(1, 2, 3, 4), 2)
val value = source.glom()
value.collect().foreach(value => println(value.mkString(",")))
//3.释放资源
sc.stop()
}
}
filter
filter(func): 返回一个新的数据集,该数据集是通过选择func在其上返回true的源中的那些元素形成的。
package cn.tedu.spark.core.rdd.operator.transformation
import org.apache.spark.{SparkConf, SparkContext}
/**
* filter过滤器
*/
object FilterTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(1, 2, 3, 4), 2)
val value = source.filter(
_ % 2 == 0
)
value.collect().foreach(println)
//3.释放资源
sc.stop()
}
}
groupBy
groupBy(func): 返回分组项目的RDD。每个组由一个键和映射到该键的一系列元素组成。不能保证每个组中元素的顺序,甚至每次评估生成的RDD时都可能会有所不同。
package cn.tedu.spark.core.rdd.operator.transformation
import org.apache.spark.{SparkConf, SparkContext}
/**
* groupBy 分组
*/
object GroupByTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(1, 2, 3, 4), 2)
val value = source.groupBy(num => num % 2)
value.collect().foreach(println)
//3.释放资源
sc.stop()
}
}
reduceByKey
reduceByKey(func): 在(K,V)对的数据集上调用时,返回(K,V)对的数据集,其中每个键的值使用给定的reduce函数func(其类型必须为(V,V)=>)进行汇总V.与in一样groupByKey,reduce任务的数量可以通过可选的第二个参数进行配置。
package cn.tedu.spark.core.rdd.operator.transformation
import org.apache.spark.{SparkConf, SparkContext}
/**
* reduceByKey 分组聚合
*/
object ReduceByKeyTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(("spark",4), ("scala",2), ("spark",1), ("scala",2)), 2)
val value = source.reduceByKey(_ + _)
value.collect().foreach(println)
//3.释放资源
sc.stop()
}
}
cogroup
cogroup(otherDataset, [numPartitions]): 在(K,V)和(K,W)类型的数据集上调用时,返回(K,(Iterable ,Iterable ))元组的数据集。此操作也称为groupWith。
package cn.tedu.spark.core.rdd.operator.transformation
import org.apache.spark.{SparkConf, SparkContext}
/**
* cogroup
*/
object CogroupTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source1 = sc.makeRDD(List(("spark",4), ("scala",2), ("spark",1), ("scala",2)), 2)
val source2 = sc.makeRDD(List(("spark","k"), ("scala","a"), ("spark","k"), ("scala","a")), 2)
val source3 = sc.makeRDD(List(("spark",2), ("scala",1), ("hello",2)), 2)
val value = source1.cogroup(source2,source3)
//val value = source1.groupWith(source2,source3)
value.collect().foreach(println)
//3.释放资源
sc.stop()
}
}
Actions
reduce
reduce(func): 使用函数func(该函数接受两个参数并返回一个)来聚合数据集的元素。该函数应该是可交换的和关联的,以便可以并行正确地计算它。
package cn.tedu.spark.core.rdd.operator.actions
/**
* reduce 聚合
*/
import org.apache.spark.{SparkConf, SparkContext}
object ReduceTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(1, 2, 3, 4), 2)
val value = source.reduce(_ + _)
println(value)
//3.释放资源
sc.stop()
}
}
collect
collect(): 在驱动程序中将数据集的所有元素作为数组返回。这通常在返回足够小的数据子集的过滤器或其他操作之后很有用。
package cn.tedu.spark.core.rdd.operator.actions
/**
* collect 收集
*/
import org.apache.spark.{SparkConf, SparkContext}
object CollectTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(1, 2, 3, 4), 2)
val value = source.collect()
println(value.mkString(","))
//3.释放资源
sc.stop()
}
}
count
count(): 返回数据集中的元素数。
package cn.tedu.spark.core.rdd.operator.actions
/**
* count 计数
*/
import org.apache.spark.{SparkConf, SparkContext}
object CountTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(1, 2, 3, 4), 2)
val value = source.count()
println(value)
//3.释放资源
sc.stop()
}
}
first
first(): 返回数据集的第一个元素(类似于take(1))。
package cn.tedu.spark.core.rdd.operator.actions
/**
* first 取第一个元素
*/
import org.apache.spark.{SparkConf, SparkContext}
object FirstTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(1, 2, 3, 4), 2)
val value = source.first()
println(value)
//3.释放资源
sc.stop()
}
}
take
take(n): 返回具有数据集的前n个元素的数组。
package cn.tedu.spark.core.rdd.operator.actions
/**
* take 取前n个数
*/
import org.apache.spark.{SparkConf, SparkContext}
object TakeTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(1, 2, 3, 4), 2)
val value = source.take(3)
println(value.mkString("|"))
//3.释放资源
sc.stop()
}
}
takeOrdered
takeOrdered(n,[ordering]) : 使用自然顺序或自定义比较器返回RDD的前n个元素。
package cn.tedu.spark.core.rdd.operator.actions
/**
* takeOrdered 排序后取前n个元素
*/
import org.apache.spark.{SparkConf, SparkContext}
object TakeOrderedTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(5,2,3,1,4), 2)
val value = source.takeOrdered(3)(Ordering[Int].reverse)
println(value.mkString("|"))
//3.释放资源
sc.stop()
}
}
RDD依赖关系
RDD血缘关系
RDD 只支持粗粒度转换,即在大量记录上执行的单个操作。将创建 RDD 的一系列 Lineage(血统)记录下来,以便恢复丢失的分区。RDD 的 Lineage 会记录 RDD 的元数据信息和转换行为,当该 RDD 的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。
窄依赖
窄依赖表示每一个父(上游)RDD 的 Partition 最多被子(下游)RDD 的一个 Partition 使用,窄依赖我们形象的比喻为独生子女。
宽依赖
宽依赖表示同一个父(上游)RDD 的 Partition 被多个子(下游)RDD 的 Partition 依赖,会引起 Shuffle,总结:宽依赖我们形象的比喻为多生。
累加器和广播变量
累加器
累加器Accumulator用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量,在Executor 端的每个 Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后,传回 Driver 端进行 merge。
package cn.tedu.spark.core.accumulator
import org.apache.spark.{SparkConf, SparkContext}
/**
* 累加器
*/
object AccumulatorTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("TransformationsTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(1, 2, 3, 4))
val value = source.reduce(_ + _)
println(value)
var sum = 0 //sum = 0 定义在Driver中
source.foreach(sum += _) //计算在Executor中,数据没有回收到Driver
println(sum) //0
//累加器
val acc = sc.longAccumulator
source.foreach(acc.add(_))
println(acc.value)
//3.释放资源
sc.stop()
}
}
广播变量
广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个 Spark 操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是 Spark 会为每个任务分别发送。
package cn.tedu.spark.core.broadcast
import org.apache.spark.{SparkConf, SparkContext}
/**
* BroadcastTest 广播变量
*/
object BroadcastTest {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setAppName("BroadcastTest").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.构建DAG
val source = sc.makeRDD(List(("a", 1), ("b", 2), ("c", 3), ("d", 4)))
val list = List(("a", 1), ("b", 2), ("c", 3), ("d", 4))
// 声明广播变量
val broadcast = sc.broadcast(list)
val resultRDD = source.map {
case (key, num) => {
var num2 = 0
// 使用广播变量
for ((k, v) <- broadcast.value) {
if (k == key) {
num2 = v
}
}
(key, (num, num2))
}
}
resultRDD.collect().foreach(println)
//3.释放资源
sc.stop()
}
}
SparkSQL
Spark SQL是用于结构化数据处理的Spark模块。与基本的Spark RDD API不同,Spark SQL提供的接口为Spark提供了有关数据结构和正在执行的计算的更多信息。在内部,Spark SQL使用这些额外的信息来执行额外的优化。与Spark SQL交互的方法有多种,包括SQL和Dataset API。计算结果时,将使用相同的执行引擎,而与要用来表达计算的API /语言无关。这种统一意味着开发人员可以轻松地在不同的API之间来回切换,从而提供最自然的方式来表达给定的转换。
pom文件,在此前基础上增加以下坐标
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-yarn_2.12</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.12</artifactId>
<version>3.0.0</version>
</dependency>
示例代码
package cn.tedu.spark.sql
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
/**
* SparkSQL
*/
object SparkSqlTest {
def main(args: Array[String]): Unit = {
//1.初始化执行环境
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkSqlTest")
val ss = SparkSession.builder().config(sparkConf).getOrCreate()
//2.构建DAG
val df = ss.read.json("datas/user.json")
// df.show()
df.createOrReplaceTempView("user")
val resultDF = ss.sql("select age,name from user where age > 19")
resultDF.show()
//3.释放资源
ss.close()
}
}
SparkStreaming
Spark Streaming是核心Spark API的扩展,可实现实时数据流的可伸缩,高吞吐量,容错流处理。数据可以从像Kafka或TCP套接字许多来源摄入,并且可以使用与像高级别功能表达复杂的算法来处理map,reduce,join和window。最后,可以将处理后的数据推送到文件系统,数据库或实时展示。并且,您还可以在数据流上应用Spark的 机器学习和 图形处理算法。
添加pom依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.12</artifactId>
<version>3.0.0</version>
</dependency>
安装NetCat
代码示例
package cn.tedu.spark.streaming
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* SparkStreaming
*/
object WordCount {
def main(args: Array[String]): Unit = {
//1.初始化执行环境
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
//定义采集周期(微批)
val ssc = new StreamingContext(sparkConf, Seconds(3))
//2.构建DAG
val streamSource = ssc.socketTextStream("localhost", 9999)
streamSource.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).print()
//3.释放资源
// ssc.stop()
ssc.start()
ssc.awaitTermination()
}
}
项目实战
统计热销商品TOP10
1.统计商品点击量排名
2.统计商品订单量排名
3.统计商品支付量排名
4.统计综合排名: 热度 = 点击量 * 20% + 订单量 * 30% + 支付量 * 50%
数据准备及字段解释
上面的数据图是从数据文件中截取的一部分内容,表示为电商网站的用户行为数据,主
要包含用户的 4 种行为:搜索,点击,下单,支付。数据规则如下:
➢ 数据文件中每行数据采用下划线分隔数据
➢ 每一行数据表示用户的一次行为,这个行为只能是 4 种行为的一种
➢ 如果搜索关键字为 null,表示数据不是搜索数据
➢ 如果点击的品类 ID 和产品 ID 为-1,表示数据不是点击数据
➢ 针对于下单行为,一次可以下单多个商品,所以品类 ID 和产品 ID 可以是多个,id 之
间采用逗号分隔,如果本次不是下单行为,则数据采用 null 表示
➢ 支付行为和下单行为类似
业务实现
pom文件
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
核心代码
package cn.tedu.project
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 项目实战
* 统计TOP10热门商品
*/
object HotSaleTop10 {
def main(args: Array[String]): Unit = {
//1.初始化环境
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("HotCategoryTop10")
val sc = new SparkContext(sparkConf)
//2.构建DAG
//2.1 读取日志数据
val logsRDD = sc.textFile("datas/user_visit_action.txt")
//2.2 统计商品的点击数量
//2.2.1 过滤
val clickRDD = logsRDD.filter(log => {
val fields = log.split("_")
fields(7) != "-1"
})
//2.2.2 转化数据为Tuple
val tupledClickRDD = clickRDD.map(
click => {
val fields = click.split("_")
(fields(7), 1)
}
)
//2.2.3 统计点击量
val clickCountRDD = tupledClickRDD.reduceByKey(_ + _)
clickCountRDD.sortBy(_._2, false)
.coalesce(1).saveAsTextFile("top10/click")
//2.3 统计商品的下单数量
//2.3.1 过滤数据
val orderRDD = logsRDD.filter(_.split("_")(9) != "null")
//2.3.2 转化数据为Tuple
val tupledOrderRDD = orderRDD.flatMap(
log => {
val fields = log.split("_")
val productIds = fields(9).split(",")
productIds.map(
(_, 1)
)
}
)
//2.3.3 统计下单数量
val orderCountRDD = tupledOrderRDD.reduceByKey(_ + _)
// orderCountRDD.collect().foreach(println)
orderCountRDD.sortBy(_._2,false).coalesce(1).saveAsTextFile("top10/order")
//2.4 统计商品的支付数量
//2.4.1 过滤数据
val payRDD = logsRDD.filter(_.split("_")(11) != "null")
//2.4.2 转化数据为Tuple
val tupledPayRDD = payRDD.flatMap(
log => {
val fields = log.split("_")
val productIds = fields(11).split(",")
productIds.map(
(_, 1)
)
}
)
//2.4.3 统计支付数量
val payCountRDD = tupledPayRDD.reduceByKey(_ + _)
// payCountRDD.collect().foreach(println)
payCountRDD.sortBy(_._2).repartition(1).saveAsTextFile("top10/pay")
//2.5 将商品点击\下单\成交量进行权重排序,并且取TOP10
//热度 = 点击量 * 20% + 下单量 * 30% + 成交量 * 50%
//2.5.1 整合数据,cogroup
val cogroupRDD: RDD[(String, (Iterable[Int], Iterable[Int], Iterable[Int]))] =
clickCountRDD.cogroup(orderCountRDD, payCountRDD)
//2.5.2 权重转换
val countedRDD = cogroupRDD.mapValues {
info => {
info._1.sum * 0.2 + info._2.sum * 0.3 + info._3.sum * 0.5
}
}
//2.5.3 排序并获取TOP10
val resultRDD = countedRDD.sortBy(_._2, false).take(10)
//2.6 收集输出结果
// resultRDD.foreach(println)
sc.makeRDD(resultRDD,1).saveAsTextFile("top10/top10")
//3.释放资源
sc.stop()
}
}