Spark
WordCount
package com.bigdata
import org.apache.spark.{SparkConf, SparkContext}
object WordCount {
def main(args: Array[String]): Unit = {
//创建上下文对象
val conf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
val sc = new SparkContext(conf)
//读取数据
val inputRDD = sc.textFile("hdfs://192.168.40.131:9000/datas/wc.txt")
//分析数据
val resultRDD = inputRDD.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
//数据存储
resultRDD.foreach(println)
resultRDD.saveAsTextFile("hdfs://192.168.40.131:9000/out_datas/")
}
}
- 创建上下文对象
- 数据处理
- 读取数据
- 分析数据
- 存储数据
排序
- sortByKey(),参数:ascending :true 升序,false 降序
- sortby(),参数 1:指定排序字段,参数 2:ascending :true 升序,false 降序
- top(n):默认降序,参数:取前n个
案例:
package com.bigdata
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object SparkTopKey {
def main(args: Array[String]): Unit = {
//创建上下文对象
val conf = new SparkConf().setMaster("local[*]").setAppName("SparkTopKey")
val sc = new SparkContext(conf)
//读取数据
val inputRdd = sc.textFile("hdfs://192.168.40.131:9000/datas/wc.txt")
//分析数据,将数据进行排序(降序)
val resultRDD = inputRdd.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
.sortBy(_._2, false)
.take(3)
//打印
resultRDD.foreach(println)
}
}
Spark应用提交
#spark/bin/目录下
spark-submit
# Usage: spark-submit [options] <app jar | python file> [app arguments]
options参数:
- 基本参数
- Driver Program 相关参数
- Executor 相关参数
案例:
package bigdata
import org.apache.spark.{SparkConf, SparkContext}
object SparkSubmit {
def main(args: Array[String]): Unit = {
if (args.length < 2){
println("Usage: SparkSubmit <input> <output> ......")
System.exit(1)
}
//创建上下文对象
val conf = new SparkConf().setAppName("WordCount")
val sc = new SparkContext(conf)
//读取数据
val inputRDD = sc.textFile(args(0))
//分析数据
val resultRDD = inputRDD.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
//数据存储
resultRDD.foreach(println)
resultRDD.saveAsTextFile(args(1))
}
}
- 命令行指定master
- 命令行指定输入输出路径
提交代码:
spark-submit \
--master local[2] \
--class bigdata.SparkSubmit \
file:///root/spark_jars/spark_scala-1.0-SNAPSHOT.jar \
hdfs://192.168.40.131:9000/user/spark/datas/WordCount.txt hdfs://192.168.40.131:9000/user/spark/output_data/
- –master :指定master
- –class :第一个参数:指定全类名,第二个参数:指定jar路径,第三个参数:输入路径,第四个参数:输出路径
注意:当jar在hdfs上时,启用本地模式可能存在错误
CORE
- 弹性数据集:RDD
- 共享变量:Shared Variables
- 累加器
- 广播变量
RDD创建
-
本地集合创建(Scala中集合对象)数据存储到RDD中(主要用与测试)
-
本地文件系统数据
如:HDFS、LocalFS、kafak等
本地集合创建代码:
package bigdata
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* Spark 采用并行化的方式构建Scala集合Seq中的数据为RDD
*/
object RDD_establish {
def main(args: Array[String]): Unit = {
//构建SparkContext对象,(模板)
val sc:SparkContext = {
// 创建SparkConf对象
val sparkconf = new SparkConf()
.setMaster("local[*]")
.setAppName(this.getClass.getSimpleName.stripSuffix("$"))
// 传递SparkConf对象,创建实例
val context = SparkContext.getOrCreate(sparkconf) //有就选取,没有就创建
// 返回实例对象
context
}
//TODO:创建本地集合
val seq = Seq(1,2,3,4,5,6,7,8)
//并行化本地集合,创建RDD
//两种方式,效果相同,makeRDD底层调用parallelize实现
val RDD: RDD[Int] = sc.makeRDD(seq,2)
val rdd1 = sc.parallelize(seq,2)
sc.stop()
}
}
外部存储系统创建代码:
package bigdata
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object RDD_establish {
def main(args: Array[String]): Unit = {
//构建SparkContext对象
val sc:SparkContext = {
// 创建SparkConf对象
val sparkconf = new SparkConf()
.setMaster("local[*]")
.setAppName(this.getClass.getSimpleName.stripSuffix("$"))
// 传递SparkConf对象,创建实例
val context = SparkContext.getOrCreate(sparkconf) //有就选取,没有就创建
// 返回实例对象
context
}
//外部存储创建,第一个参数:文件路径,第二个:分区数
//可以指定文件名称,可以指定文件目录,可以使用通配符
val rdd = sc.textFile("",2)
//读取小文件数据
//返回K-V形式数据,K:文件路径 V:数据
/**
* Read a directory of text files from HDFS, a local file system (available on all nodes), or any
* Hadoop-supported file system URI. Each file is read as a single record and returned in a
* key-value pair, where the key is the path of each file, the value is the content of each file.
*
* <p> For example, if you have the following files:
* {{{
* hdfs://a-hdfs-path/part-00000
* hdfs://a-hdfs-path/part-00001
* ...
* hdfs://a-hdfs-path/part-nnnnn
* }}}
*
* Do `val rdd = sparkContext.wholeTextFile("hdfs://a-hdfs-path")`,
*
* <p> then `rdd` contains
* {{{
* (a-hdfs-path/part-00000, its content)
* (a-hdfs-path/part-00001, its content)
* ...
* (a-hdfs-path/part-nnnnn, its content)
* }}}
*/
val rdd2: RDD[(String, String)] = sc.wholeTextFiles("", 2)
sc.stop()
}
}
分区数目
获取分区的两种方式:
rdd.getNumPartitions
rdd.partitions.length
注:1. 从 HDFS 上加载海量数据时,RDD分区数目为block数目
2. 从HBase表加载数据,RDD分区数目为Region数目
RDD函数
1. Transformation
注:所有Transformation函数都是懒惰的,不会立刻执行,需要Action函数触发
算子:
map(func)
:通过将函数应用于此RDD的所有元素来返回新RDD
//创建数据集
val rdd = sc.makeRDD(List(1,2,3,4,5,6,7,8),2)
//将集合中的元素乘2
//map
rdd.map(_*2).foreach(println)
filter(func)
:返回判断条件为true的元素
//创建数据集
val rdd = sc.makeRDD(List(1,2,3,4,5,6,7,8),2)
//filter 偶数
rdd.filter(_ % 2 == 0).foreach(println)
flatmap(func)
:首先将函数应用于所有元素,再将结果展平,返回新的RDD
//创建数据集
val rdd = sc.makeRDD(List("hello spark","hello hadoop","with you"))
//flatmap
rdd.flatMap(_.split(" ")).foreach(println)
mapPartitions(func)
:将函数独立应用在每一个分区内的元素,返回一个新的RDD(函数类型必须是:Iterator[T] => Iterator[U])
//创建数据集
val rdd = sc.makeRDD(List(1,2,3,4,5,6,7,8),2)
//mapPartitions
rdd.mapPartitions(
iter => iter.map(_*2)
).foreach(println)
mapPartitionsWithIndex(func)
:功能与mapPartitions相似,但func多了一个参数表示分区索引,因此func类型必须是:(Int, Interator[T]) => Iterator[U]
//创建数据集
val rdd = sc.makeRDD(List(1,2,3,4,5,6,7,8),2)
//mapPartitionsWithIndex 原数据加上分区号,返回字符串形式
rdd.mapPartitionsWithIndex(
(index, iter) => {
iter.map(_ +" 分区号:"+ index)
}
).foreach(println)
groupBy(func)
:按照函数的返回值进行分组,将相同key对应的值放到一个迭代器中,返回值类型为: RDD[(K, Iterable[T])]
sortBy(func)
:按照函数返回值进行排序,默认升序,ascending:true为升序,false为降序
sample
:抽样,参数 1. withReplacement:true数据又放回的抽取,false不放回抽取;2. fraction:随机抽样出数量;3. seed:指定随机数生 成器种子
randomSplit
:按照指定权重抽样,参数1:权重[Array]集合,总和需为1;参数2:随机数生成种子
unoin
:返回此RDD和另一个的并集,包含两个集合的所有元素
intersection
:返回两个RDD的交集,无重复元素
subtract
:RDD1.subtract(RDD2),返回RDD1独有的元素
distinct
:去除RDD中重复的元素
cartesian
:笛卡尔积
zip
:将两个RDD一一对应的组合成K-V形式的RDD,这里两个RDD的分区及元素数量都要相同,否则报错
coalesce
:缩减分区,不产生shuffle
repartition
:增加分区,产生shuffle
mapValue
:通过将函数应用于K-V类型RDD的Value元素,返回新的RDD
flatmapValue
:首先将函数应用于K-V类型RDD的Value元素,再将结果展平,返回新的RDD
groupByKey
:按K-V类型RDD的K进行分组,将相同Key对应的值放到一个Seq中,返回新的RDD
redyceBykey
:在一个(K,V)的 RDD 上调用,返回一个(K,V)的 RDD,使用指定的 reduce 函数,将相同 key 的值聚合到一起,reduce 任务的个数可以通过第二个可选的参数来设置
foldByKey
:与reduceByKey相似,多了一个参数可以指定初始值
aggregateByKey
:参数:(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
:指定分区内计算规则,再指定分区间计算规则
sortByKey
:按照K进行排序
combineByKey
:与aggreateByKey相似,第一个参数不同,第一个参数将数据的第一个值进行结构转换作为初始值
join
:在类型为(K,V)和(K,W)的 RDD 上调用,返回一个相同 key 对应的所有元素对在 一起的(K,(V,W))的 RDD
cogroup
:在类型为(K,V)和(K,W)的 RDD 上调用,返回一个(K,(Iterable,Iterable))类 型的 RDD
partitionBy:
:对RDD进行重新分区,默认使用new org.apache.spark.HashPartitioner(2)分区规则
2. Action
注:所有Action函数立即执行
算子:
reduce
:对RDD内所有元素进行聚合,先聚合分区内,再聚合分区间
collect
:以数组的形式返回数据集的所有元素
aggregate
:与aggregateByKey相似
fold
:与foldByKey相似
first
:取出RDD中第一个元素
take
:取出指定数量的RDD的前N个元素
forEach
:在数据集的每一个元素上,运行函数 func 进行更新。
takeOrdered(n)
:返回该 RDD 排序后的前 n 个元素组成的数组
collectAsMap
:将K-V类型的RDD转换为Map
count
:返回 RDD 中元素的个数
max、min、sum、mean、stdev:
:最大值、最小值、总和、平均值、标准差
Keys、values
:获取K-V类型RDD的所有K值、获取K-V类型RDD的所有V值
countByKey
:统计K-V类型RDD所有的K出现的次数,返回collections.map类型
countByValue
:统计RDD中元素出现的次数,底层将数据转换为value =>(value,null)的形式再调用countByKey,返回collections.map类型
saveAsTextFile
:将数据集的元素以 textfile 的形式保存到 HDFS 文件系统或者其他支持的文件系统, 对于每个元素,Spark 将会调用 toString 方法,将它装换为文件中的文本
saveAsSequenceFile(path)
:将数据集中的元素以 Hadoop sequencefile 的格式保存到指定的目录下,可以使 HDFS 或者其他 Hadoop 支持的文件系统。
saveAsObjectFile(path)
:用于将 RDD 中的元素序列化成对象,存储到文件中。
自定义分区
要实现自定义的分区器,你需要继承 org.apache.spark.Partitioner 类并实现下面三个 方法。
(1)numPartitions: Int:返回创建出来的分区数。
(2)getPartition(key: Any): Int:返回给定键的分区编号(0 到 numPartitions-1)。
(3)equals():Java 判断相等性的标准方法。这个方法的实现非常重要,Spark 需要用这个 方法来检查你的分区器对象是否和其他分区器实例相同,这样 Spark 才可以判断两个 RDD 的分区方式是否相同。
数据读取和保存
- Text文件
- 读取:textFile
- 保存:saveAsTextFile
- json文件
- 导入:
import scala.util.parsing.json.JSON
- 读取文件:
val json = textFile
- 解析:
json.map(JSON.parseFull)
- 导入:
- Sequence文件
- 读取:
sc.sequenceFile[Int,Int]("file:///opt/module/spark/seqFile")
- 保存:
saveAsSequenceFile("file:///opt/module/spark/seqFile")
- 读取:
- 对象文件
- 读取:
sc.objectFile[(Int)]("file:///opt/module/spark/objectFile")
- 保存:
saveAsObjectFile("file:///opt/module/spark/objectFile")
- 读取:
文件系统数据读取
-
Mysql
支持通过 Java JDBC 访问关系型数据库。需要通过 JdbcRDD 进行,示例如下:
(1)添加依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.27</version> </dependency>
(2)Mysql 读取:
package com.atguigu import java.sql.DriverManager import org.apache.spark.rdd.JdbcRDD import org.apache.spark.{SparkConf, SparkContext} object MysqlRDD { def main(args: Array[String]): Unit = { //1.创建 spark 配置信息 val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("JdbcRDD") //2.创建 SparkContext val sc = new SparkContext(sparkConf) //3.定义连接 mysql 的参数 val driver = "com.mysql.jdbc.Driver" val url = "jdbc:mysql://hadoop102:3306/rdd" val userName = "root" val passWd = "000000" //创建 JdbcRDD val rdd = new JdbcRDD(sc, () => { Class.forName(driver) DriverManager.getConnection(url, userName, passWd) }, "select * from `rddtable` where `id`>=?;", 1, 10, 1, r => (r.getInt(1), r.getString(2)) ) //打印最后结果 println(rdd.count()) rdd.foreach(println) sc.stop() } }
Mysql 写入:
def main(args: Array[String]) { val sparkConf = new SparkConf().setMaster("local[2]").setAppName("HBaseApp") val sc = new SparkContext(sparkConf) val data = sc.parallelize(List("Female", "Male","Female")) data.foreachPartition(insertData) } def insertData(iterator: Iterator[String]): Unit = { Class.forName ("com.mysql.jdbc.Driver").newInstance() val conn = java.sql.DriverManager.getConnection("jdbc:mysql://master01:3306/rdd", "root", "hive") iterator.foreach(data => { val ps = conn.prepareStatement("insert into rddtable(name) values (?)") ps.setString(1, data) ps.executeUpdate() }) }
累加器
-
获取系统累加器
val sumAcc = sc.longAccumulator(name="sum")
-
使用累加器
sumAcc.add(num)
-
获取累加器结果
sumAcc.value
自定义累加器
package bigdata
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.util.AccumulatorV2
import scala.collection.mutable
object Acc {
def main(args: Array[String]): Unit = {
val sc:SparkContext = {
val conf:SparkConf = new SparkConf()
.setMaster("local[*]")
.setAppName(this.getClass.getSimpleName.stripSuffix("$"))
SparkContext.getOrCreate(conf)
}
val rdd = sc.makeRDD(List("hello","word","spark","hello"))
//创建累加器对象
val wcAcc = new MyAccumulator()
//向Spark进行注册
sc.register(wcAcc, "wordCountAcc")
rdd.foreach(
word => {
wcAcc.add(word)
}
)
//获取累加器结果
println(wcAcc.value)
}
/**
* 自定义累加器
* IN:累加器输入的数据类型
* OUT:累加器返回的数据类型
*/
class MyAccumulator extends AccumulatorV2[String,mutable.Map[String,Long]]{
private var wcMap = mutable.Map[String,Long]()
//判断是否为初始状态
override def isZero: Boolean = {
wcMap.isEmpty
}
//赋值累加器
override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
new MyAccumulator()
}
//重置累加器
override def reset(): Unit = {
wcMap.clear()
}
//获取累加器需要计算的值
override def add(word: String): Unit = {
val newCnt = wcMap.getOrElse(word,0L) + 1
wcMap.update(word,newCnt)
}
//合并多个累加器
override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {
val map1 = this.wcMap
val map2 = other.value
map2.foreach{
case (word ,count) => {
val newCount = map1.getOrElse(word,0L) + count
map1.update(word,newCount)
}
}
}
//累加器结果
override def value: mutable.Map[String, Long] = {
wcMap
}
}
}