简单的先运行一下
object Hello {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("wc")
val sc=new SparkContext(conf)
sc.stop()
}
}
运气不好直接报错
这个应该是scala版本不对
当前idea引入的scala运行环境版本与idea默认的scala版本不一样
查看本项目的Project Structure,点击Global Libraries选项查看,显示版本一致。就是导入pom.xml的spark那个依赖版本和你导入scala那个版本不对映
数据存放位置
sc.textfile 时,他默认读取的数据的位置是
放在untiled3下面不能放到spark下,放在spark下需要配置
object Hello {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("wc")
val sc=new SparkContext(conf)
val lines : RDD[String] = sc.textFile("datas")
val word = lines.flatMap(_.split(" "))
val wordGroup =word.groupBy(word=>word)
wordGroup.collect().foreach(println)
val wordCount = wordGroup.map {
case (word, list) => {
(word, list.size)
}
}
sc.stop()
}
}
wordCount的计算,从这可以看出,真的比hadoop方便编程,哪还用map,recuce ,driver?
log4j配置
log4j.rootLogger=ERROR,CONSOLE
log4j.addivity.org.apache=true
# console
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=INFO
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.Encoding=UTF-8
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[demo] %-5p %d{yyyy-MM-dd HH\:mm\:ss} - %C.%M(%L)[%t] - %m%n
# all
log4j.logger.com.demo=INFO, DEMO
log4j.appender.DEMO=org.apache.log4j.RollingFileAppender
log4j.appender.DEMO.File=${catalina.base}/logs/demo.log
log4j.appender.DEMO.MaxFileSize=50MB
log4j.appender.DEMO.MaxBackupIndex=3
log4j.appender.DEMO.Encoding=UTF-8
log4j.appender.DEMO.layout=org.apache.log4j.PatternLayout
log4j.appender.DEMO.layout.ConversionPattern=[demo] %-5p %d{yyyy-MM-dd HH\:mm\:ss} - %C.%M(%L)[%t] - %m%n
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
这个网址可以看运行spark的程序执行情况
把程序jar包在本地运行
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master local[2] \
./examples/jars/spark-examples_2.12-3.1.1.jar \
10
这里说明一下啊, --class 表示那个要运行的类
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode cluster \
./examples/jars/spark-examples_2.12-3.1.1.jar \
10
Standlone模式和Yarn模式和本地模式区别
本地就是一台计算机,不分布式,standlone 就是Spark自身提供计算资源,无需其他框架提供资源,这种方式降低了和其它第三方资源框架的耦合性,独立性非常强,但是spark主要是计算框架,而不是资源调度框架,所以一般都是用yarn模式。
Standlone 是自己调度 Master 管两个worker,而Yarn则是RM管NM
配置YARN环境
修改Hadoop中的YARN-site.xml
#把物理内存过大会自动关闭给关了
<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false<value>
</property>
#把虚拟内存过大会自动关闭给关了
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false<value>
</property>
修改spark conf中的spark-env.sh.templete 为 spark-env.sh并增加以下代码
export JAVA_HOME=/opt/SoftWare/Java/jdk1.8.0_212
YARN_CONF_DIR=/opt/SoftWare/Hadoop/hadoop-2.7.7/etc/hadoop
配置历史服务器
修改spark-defalults.conf.template为spark-defaults,conf
修改 spark-defalut.conf路径
mapred-site.xml
<!--Spark Yarn-->
<property>
<name>mapreduce.jobhistory.address</name>
<value>node001:10020</value>
</property>
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>node001:19888</value>
</property>
yarn-site.xml
<!--Spark Yarn-->
<!-- 是否开启聚合日志 -->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<!-- 配置日志服务器的地址,work节点使用 -->
<property>
<name>yarn.log.server.url</name>
<value>http://node001:19888/jobhistory/logs/</value>
</property>
<!-- 配置日志过期时间,单位秒 -->
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>86400</value>
</property>
1)修改配置文件spark-default.conf,添加信息
spark.yarn.historyServer.address=hadoop100:18080
spark.history.ui.port=18080
这里把历史日志地址选在了hadoop100这台主机上,端口是18080
- 在spark-env.sh中添加
export JAVA_HOME=/opt/SoftWare/Java/jdk1.8.0_212
YARN_CONF_DIR=/opt/SoftWare/Hadoop/hadoop-2.7.7/etc/hadoop
HADOOP_CONF_DIR=/opt/SoftWare/Hadoop/hadoop-2.7.7/etc/hadoop
export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080
-Dspark.history.fs.logDirectory=hdfs://node001:9000/directory
-Dspark.history.retainedApplications=30"
(3)重启Spark的历史服务
[kevin@hadoop100 spark]$ sbin/stop-history-server.sh
[kevin@hadoop100 spark]$ sbin/start-history-server.sh
Yarn 的工作流程
1)客户端提交应用程序,SparkSubmit
(2)让RM启动Spark的ApplicationMaster程序,用于Spark与Yarn之间资源交互
(3)AM向RM申请资源,用于启动Executor
(4)RM获取集群的资源信息(NM)
(5)RM将资源信息发送给AM,由AM中的Driver判断任务调度的地址
(6)Driver划分任务,分配任务task发送给Executor执行
(7)Executor执行任务,执行完毕后,通知Driver
(8)Driver和AM交互通知RM回收资源
(9)Executor、Container、Driver、ApplicationMaster就都释放资源消失
(10)最终留下Yarn的RM和NM,在client端打印结果。
端口号
RDD
rdd是最小逻辑运算单元,driver里包含逻辑和数据
RDD是spark中最基本的数据处理模型,代码中是一个抽象类,他代表一个弹性的(存储的弹性,磁盘和和内存直接可以自动切换,容错的弹性,数据丢失,知道从哪个文件读取,计算的弹性,计算出错重试机制,分片的弹性,可根据需要重新分片),不可变,可分区,里边元素可并行计算的集合。RDD封装了计算逻辑,并不能保存数据
RDD 流程
Yarn启动,假如1个RM,2个NM,RM(driver)NM(excuator) rdd经过组合,在driver中形成task ,多个task 组成taskpoll,然后传输给最优(存储数据)的excuator,
rdd内存中创建
object rddMemory {
def main(args: Array[String]): Unit = {
val sparkConf=new SparkConf().setMaster("local[*]").setAppName("")
val sc=new SparkContext(sparkConf)
val seq= Seq(1,2)
//parallelize 并行的,比如说2核,既1对1关系,如果1核则并发关系
val rdd= sc.parallelize(seq)
//makeRDD底层调用了parallelize,可能是为了方便程序员记忆吧
sc.makeRDD(seq)
rdd.collect().foreach(println)
sc.stop()
}
}
rdd文件中创建
sc.textfile(hdfs://node001:9000/文件夹) //以行为单位,不管哪个文件
sc.wholeTextFile(路径) 以文件为单位,识别在哪个文件下
RDD的分区
RDD不保存数据,但是他们之间的关系会保存。
sc.makeRdd(list(1,2),5) //5个分区,如果不写5的话,默认为val sparkConf=new SparkConf().setMaster("local[*]").setAppName("")中local[*]中的最大核数。
sc.textfiel(文件,分区数)
/*
分区数默认为2 ,如果比2小,则采用更小的,如果比2大就进行下面的运算。实际上,底层用的是hadoop,的Textinputformat 读取数据,按照任务进行分区
4个文件长度分别为100 100 100 1400字节,默认最小分区为2
首先计算全部文件总长度totalSize=100+100+100+1400=1700
goalSize=totalSize/最小分区数即2 =850
blockSize=128M换算成字节为134217728
minSize=1
goalSize与blockSize取最小 值为850
850 与minSize取最大 值为850
即splitSize为850
然后 每个文件长度除以850 判断是否大于1.1
文件1,2,3都是100所以各生成1个分区,
文件4位1400,除以850>1.1 切分一个分区,剩余
(1400-850)/850 >1.1不再成立 又生成一个分区.
所以举例中的四个文件 共生成5个分区
*/
转换算子与行动算子
map() 怎么说呢,这个算子如果一个分区的话,他会一个一个的算,如果两个分区的话,它在分区内有序,但是两个分区之间无序。
**mapPatition()**他其实就是相当于一个缓冲区,一个分区算一次,以分区为单位,但是会将整个分区的数据加载到内存进行引用,就是处理完的数据也不会释放掉,因为存在对象的引用,所以说,在面对分区数据大,运行内存小,不可以的哦!例子:比如说取分区中最大的数。里边传的是迭代器,传出的也是迭代器。
mapPartitonsWithIndex
val rdd=RDD.mapPartionWithIndex(
(index,iter)=>(
if(index==1){
iter
}
else{
Nil.iterator
}
)
FlatMap 返回值为一个可迭代的集合,扁平化操作。
val conf=new SparkConf().setAppName("").setMaster("local[*]")
val sc=new SparkContext(conf)
val rdd=sc.makeRDD(List(List(1,2),4,List(3,5)))
val res = rdd.flatMap(data =>
data match {
case list: List[Int] => list
case int => List(int)
}
)
res.collect.foreach(println)
glom把一个分区的数据以数组Array的方式封装起来。将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
val conf=new SparkConf().setAppName("").setMaster("local[*]")
val sc=new SparkContext(conf)
val rdd=sc.makeRDD(List(List(1,2),4,List(3,5)))
rdd.glom().foreach(data=>println(data.mkString(",")))
groupBy(fun) fun是分组的格式如(%2),返回值类型是(分组号,ComPactBuffer(数据)) 再比如首字母放到一块 groupBy(.charAt(0))
filter
sample 三个参数(是否放回,每条数据被抽取的概率,抽取随机数种子)
distinct 去重
coalesce rdd.colesce(3,true)重新分区,平均分配。第一个参数是分区数,第二个分区数是是否shuffle
,一般用于缩小分区
repartition 扩大分区,底层是coalesce
sortby 排序,中间存在shuffle操作,就是分区中的数据变化了 sortBy(_._2)(Ordering.Int.reverse).take(3)倒序取前三个
交并差 rdd1.intersection(rdd2)
rdd1.union(rdd2)
rdd1.substact(rdd2)
rdd1.zip(rdd2)拉链操作 注意分区数量一直才能拉链,并且分区内元素数量也得相同
partitionBy() 把数据修改分区,该数据必须是key-value 并且rdd.partitionBy(new HashPartitioner(2))
groupbykey和reduceBykey 后者在shuffle之前先预聚合了一下,可以减少落盘时的数据量,所以效率高,但是不需要聚合的话,一般用groupBykey
aggregateBykey
val rdd=sc.makeRDD(List(("a",1),("a",2),("a",3),("a",4)),2)
//第一个值为初始值0
//求两个区间最大值相加
//第二个值为每个分区内的计算,(x,y)x表示初始值,y表示Value
//第三个值分区间的计算 (x,y) 表示初始值形式的分区数据
rdd.aggregateByKey(0)((x,y)=>math.max(x,y),(x,y)=>(x+y)).collect.foreach(print)
如果分区内和分区间计算规则相同,rdd.foldByKey(0)(+)
combineByKey 和aggregateByKey的区别就是第一个参数表示将Value的值转换为想要的数据。
join 相同的key笛卡尔乘积,依此匹配
leftOuterJoin
cogroup 可以理解为connect group
算子以外的代码都是在driver运行,算子里边的代码都是在Executor端执行,因此类一定要注意序列化啊
Kyro 序列化
血缘关系
RDD不保存数据,但是保存血缘 rdd.todebugString 看其血缘
**
窄依赖宽依赖
RDD只有一个孩子,叫窄依赖 onebyone
RDD任务划分
Application 初始化一个SparkContext就生成一个Application
Job 一个行动算子就会生成一个job
stage 一个宽依赖既一个Shuffle就能生成一个阶段
Task,一个stage中,最后一个rdd分区个数就是任务数
cache persist checkpoint持久化操作
区别,cache保存到内存中,persist(StorageLevel.级别)cache底层是默认的persist,其实就是persist的Memory-only模式,而checkpoint存到磁盘中
package com.zhenghaozhe
import com.esotericsoftware.kryo.serializers.DefaultSerializers.KryoSerializableSerializer
import com.esotericsoftware.kryo.serializers.JavaSerializer
import org.apache.commons.configuration.Configuration
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Hello {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("ChickPoint").setMaster("local[*]")
val sc = new SparkContext(conf)
//设置检查点目录,可以是HDFS等文件系统
sc.setCheckpointDir("E:/checkpoint")
val rdd1 = sc.makeRDD(Array("Spark"))
val rdd2_source = rdd1.map(_+":"+System.currentTimeMillis())
val rdd2_cache = rdd1.map(_+":"+System.currentTimeMillis())
val rdd2_check = rdd1.map(_+":"+System.currentTimeMillis())
println("-----------没有设置 Checkpoint-----------")
rdd2_source.foreach(println)
rdd2_source.foreach(println)
rdd2_source.foreach(println)
println("-----------设置 Cache 后-----------")
rdd2_cache.cache()
rdd2_cache.foreach(println)
rdd2_cache.foreach(println)
rdd2_cache.foreach(println)
println("-----------设置 Checkpoint 后-----------")
rdd2_check.checkpoint()
rdd2_check.foreach(println)
rdd2_check.foreach(println)
rdd2_check.foreach(println)
rdd2_check.foreach(println)
rdd2_check.foreach(println)
sc.stop()
}
}
**
自定义分区
继承Partitoner
通过RDD.partionBy(new MyPartion)来指定分区
累加器
累加器是把excutor聚合给driver端,本来只能从driver传给excutor
package com.zhenghaozhe.rdd
import java.util.concurrent.atomic.LongAccumulator
import org.apache.spark.{SparkConf, SparkContext}
object rddAcc {
def main(args: Array[String]): Unit = {
val conf=new SparkConf().setMaster("local[*]").setAppName("")
val sc=new SparkContext(conf)
var rdd=sc.makeRDD(List(1,2,3,4))
var rddAcc=sc.longAccumulator("累加器")
//注意一个问题,累加器它是由sc掉用的,累加器必须要有而且只能有一个行动算子,如果两个的话,结果就会多加、
//没有行动算子的话就会少加
rdd.foreach(
sum=>{
rddAcc.add(sum)
sum
}
)
println(rddAcc.value)
}
}
广播变量
val conf = new SparkConf()
conf.setMaster("local").setAppName("brocast")
val sc = new SparkContext(conf)
val list = List("hello hadoop")
val broadCast = sc.broadcast(list)
val lineRDD = sc.textFile("./words.txt")
lineRDD.filter { x => broadCast.value.contains(x) }.foreach { println}
sc.stop()
driver中的广播变量发送给每个excutor,作为公共部分,每一个task都可以读取到,这样节省了内存空间,多个task共同读取一个。
大数据三层架构
dao(持久层链接数据库) service(服务层) control(控制层)
MVC(model view Control) 最早是servlet(HTML CSS)后来jsp,后来发现太混乱 MVC
SparkSQL
Hive是把MR简化,Sparksql是把RDD简化。
DataFrame就是带schema信息,而RDD则是冰冷的数据,dataSet则是针对于对象
rdd df ds 三者的转换
package com.zhenghaozhe.sparksql
import org.apache.spark.sql.SparkSession
import org.apache.spark.{SparkConf, SparkContext}
object sql {
def main(args: Array[String]): Unit = {
val sc=new SparkConf().setMaster("local[*]").setAppName("")
val ss=SparkSession.builder().config(sc).getOrCreate()
//DataFrame
val df = ss.read.json("datas/1.txt")
df.show()
///
df.createTempView("user")
ss.sql("select age from user").show
///
//在使用DataFrame时,如果涉及到转换操作,需要引入转换规则
import ss.implicits._
df.select("age").show
df.select($"age"+1).show
df.select('age+1).show
//DataSet
val sea=Seq(1,2,30)
sea.toDS().show
//RDD<=>DataFrame
val rdd1=ss.sparkContext.makeRDD(List((1,"asd"),(2,"cff")))
val df1=rdd1.toDF("age","name")
//DataFrame<=>DataSet
val DS =df1.as[User].show
//RDD<=>DataSet
rdd1.map{
case(age,name)=>{
User(age , name )
}
}.toDS()
//TODO关闭环境
ss.close()
}
case class User(age:Int,name:String)
}
udAf spark3.0之前自定义函数用UserDefinedAggregateFunction,之后推荐使用Aggregator
val sc = new SparkConf().setAppName("").setMaster("local[*]")
val ss = SparkSession.builder().config(sc).getOrCreate()
val DF = ss.read.json("E:\\work\\untitled3\\datas\\1.txt")
DF.createTempView("user")
ss.udf.register("prefix",(name:String)=>{
"name:"+name
}
)
ss.sql("select prefix(name) from user").show
读取和保存
ss.read.load(路径) ss.write.save(路径)默认保存和读取都是parquet类型
SparkStrming
windows 下nc不会安装啊,老是说nc.exe有病毒,没法复制,最后只好用虚拟机上的了。
val sc=new SparkConf().setMaster("local[*]").setAppName("")
val ssc=new StreamingContext(sc,Seconds(3))
val line= ssc.socketTextStream("192.168.18.101",9999)
line.flatMap(_.split(" ")).print
ssc.start()
ssc.awaitTermination()
自定义数据采集器
def main(args: Array[String]): Unit = {
val sc=new SparkConf().setMaster("local[*]").setAppName("")
val ssc=new StreamingContext(sc,Seconds(3))
val value = ssc.receiverStream(new MyReceiver())
value.print()
ssc.start()
ssc.awaitTermination()
}
//自定义数据采集器
class MyReceiver extends Receiver(StorageLevel.MEMORY_ONLY){
private var flag=true
override def onStart(): Unit ={
new Thread(new Runnable {
override def run(): Unit = {
while(flag){
val message="采集数据为"+new Random().nextInt(10).toByte
store(message)
Thread.sleep(500)
}
}
}).start()
}
override def onStop(): Unit = {
flag=false
}
}
有状态转换和无状态转换