Spark快速入门
文章目录
一. Spark概述(了解)
(一)Spark简介
Apache Spark是用于大规模数据处理的统一分析引擎。
Spark基于内存计算,提高了在大数据环境下数据处理的实时性,同时保证了高容错性和高可伸缩性,允许用户将Spark部署在大量硬件之上,形成集群。
(二)Spark vs Hadoop
- MapReduce框架采用非循环式的数据流模型, 中间计算结果存在HDFS磁盘上, 带来了大量的数据复制、磁盘IO和序列化开销, 延迟大, Task以进程方式维护, 任务启动慢;
- RDD中间运算结果存在内存中 , 延迟小, Task以线程方式维护, 任务启动快,Spark主要用于替代Hadoop中的MapReduce计算模型,存储依然可以使用HDFS。
(三)Spark的优点
-
快
Spark基于内存的运算比MapReduce要快100倍以上,基于硬盘的运算也要快10倍以上。Spark实现了高效的DAG执行引擎,可以通过基于内存来高效处理数据流。
-
易用
Spark支持Java、Python、R和Scala的API,还支持超过80种高级算法,用户可快速构建不同的应用。Spark支持交互式的Python和Scala的shell。
-
通用
Spark提供了统一的解决方案。Spark可以用于批处理、交互式查询(Spark SQL)、实时流处理(Spark Streaming)、机器学习(Spark MLlib)和图计算(GraphX)。
-
兼容性
Spark可以非常方便地与其他的开源产品进行融合。比如,Spark可用Hadoop的YARN和Apache Mesos作为它的资源管理和调度器,可处理所有Hadoop支持的数据,包括HDFS、HBase和Cassandra等。Spark实现了Standalone作为内置的资源管理和调度框架。此外,Spark还提供了在EC2上部署Standalone的Spark集群的工具。
(四)Spark组件
-
Spark Core
实现了 Spark 的基本功能,包含RDD、任务调度、内存管理、错误恢复、与存储系统交互等模块。
-
Spark SQL
是 Spark 用来操作结构化数据的程序包。通过 Spark SQL,我们可以使用 SQL操作数据。
-
Spark Streaming
是 Spark 提供的对实时数据进行流式计算的组件。提供了用来操作数据流的 API。
-
Spark MLlib
提供常见的机器学习(ML)功能的程序库。包括分类、回归、聚类、协同过滤等,还提供了模型评估、数据导入等额外的支持功能。
-
GraphX(图计算)
GraphX是Spark中用于图计算的API,性能好,拥有丰富的功能和运算符,能在海量数据上运行复杂的图算法。
-
集群管理器
Spark 设计为可以高效地在一个计算节点到数千个计算节点之间伸缩计算。
(五)Spark运行模式
- local本地模式(单机) —> 开发测试使用
- standalone独立集群模式 —> 开发测试使用
- standalone-HA高可用模式 —> 生产环境使用
- on yarn集群模式 —> 生产环境使用
二、Spark环境搭建(掌握)
我们选择目前企业中使用最多的是2017年07月发布的Spark2.2.0稳定版, 下载地址spark官网:http://spark.apache.org/downloads.html
- 解压目录说明:
-
- bin 可执行脚本
- conf 配置文件
- data 示例程序使用数据
- examples 示例程序
- jars 依赖 jar 包
- LICENSE licenses
- python pythonAPI
- R R 语言 API
- sbin 集群管理命令
- yarn 整合yarn需要的东西
(一)local本地模式
集群启动
#启动进入
cd /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/
#使用当前机器上所有可用的资源(可简写为bin/spark-shell)
bin/spark-shell --master local[*]
#或者 bin/spark-shell --master spark://node01:7077
体验: 读取本地文件
val textFile = sc.textFile("file:///export/words.txt")
val counts = textFile.flatMap(line=>line.split(" ")).map(word=>(word,1)).reduceByKey((a,b)=>a+b)
counts.collect//收集结果
体验: 读取HDFS文件
sc.textFile("hdfs://node01:8020/spark/day01/words.txt").flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).saveAsTextFile("hdfs://node01:8020/spark/day01/output1")
(二)standalone集群模式
修改spark-env.sh文件
cd /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/conf/
mv spark-env.sh.template spark-env.sh
vim spark-env.sh
#在文件后追加 配置java环境变量
export JAVA_HOME=/export/servers/jdk1.8.0_141
#指定spark老大Master的IP
export SPARK_MASTER_HOST=node01
#指定spark老大Master的端口
export SPARK_MASTER_PORT=7077
修改slaves文件(配置从节点worker)
mv slaves.template slaves
vim slaves
#删掉localhost,并在文件内后追加
node02
node03
通过scp 命令将配置文件分发到其他机器上
cd /export/servers/
scp -r spark-2.2.0-bin-2.6.0-cdh5.14.0 node02:$PWD
scp -r spark-2.2.0-bin-2.6.0-cdh5.14.0 node03:$PWD
#在node01主节点上启动spark集群
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/sbin/start-all.sh
#在node01主节点上停止spark集群
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/sbin/stop-all.sh
- 查看spark的web界面: http://node01:8080
测试:
#集群模式启动spark-shell SparkContext页面: http://192.168.1.101:4040
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/bin/spark-shell --master spark://node01:7077
#运行程序
sc.textFile("hdfs://node01:8020/spark/day01/words.txt").flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).saveAsTextFile("hdfs://node01:8020/spark/day01/output3")
注意:
集群模式下程序是在集群上运行的,读取hdfs上的上的文件, 不能直接读取本地文件。因为程序运行在集群上,其他节点可能并没有那个数据文件。
(三)standalone-HA模式
修改spark-env.sh文件
cd /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/conf/
vim spark-env.sh
#注释掉Master配置
#export SPARK_MASTER_HOST=node01
#添加SPARK_DAEMON_JAVA_OPTS, 在文件后追加
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=node01:2181,node02:2181,node03:2181 -Dspark.deploy.zookeeper.dir=/spark"
参数说明:
1. spark.deploy.recoveryMode:恢复模式(Master重新启动的模式)有三种:
(1)ZooKeeper (2) FileSystem (3)NONE
2. spark.deploy.zookeeper.url:ZooKeeper的Server地址
3. spark.deploy.zookeeper.dir:保存集群元数据信息的文件、目录。包括Worker、Driver、Application信息。
通过scp 命令将配置文件分发到其他机器上
cd /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/conf/
scp -r spark-env.sh node02:$PWD
scp -r spark-env.sh node03:$PWD
启动集群
#在node01上启动Spark集群
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/sbin/start-all.sh
#在node02上再单独只起个master:
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/sbin/start-master.sh
测试一:
#集群模式启动spark-shell
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/bin/spark-shell --master spark://node01:7077,node02:7077
#运行程序
sc.textFile("hdfs://node01:8020/spark/day01/words.txt").flatMap(_.split(" ")).map((_, 1)).reduceByKey(_ + _).saveAsTextFile("hdfs://node01:8020/spark/day01/output6")
(四)on yarn集群模式
注意:
把Spark程序提交给YARN运行本质是把字节码给YARN集群上的JVM运行, 但是要有一个东西帮我去把任务提交给YARN, 所以需要一个单机版的Spark,使用里面的spark-shell命令,spark-submit命令。
on yarn集群模式启动zookeeper和Hadoop集群即可, 不需启动Spark集群
修改spark-env.sh文件
cd /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/conf/
vim spark-env.sh
#在文件后追加, 添加HADOOP_CONF_DIR配置,指明hadoop的配置文件的位置
export HADOOP_CONF_DIR=/export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop
配置历史日志服务器
#修改文件名
cd /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/conf/
cp spark-defaults.conf.template spark-defaults.conf
vim spark-defaults.conf
#修改spark-defaults.conf文件
spark.eventLog.enabled true
spark.eventLog.dir hdfs://node01:8020/user/sparklog
#hdfs上的目录需要手动先创建
hdfs dfs -mkdir -p /user/sparklog/
#修改spark-env.sh文件
vim spark-env.sh
#追加以下内容
export SPARK_HISTORY_OPTS="-Dspark.history.ui.port=4000 -Dspark.history.retainedApplications=3 -Dspark.history.fs.logDirectory=hdfs://node01:8020/user/sparklog"
#通过scp 命令将配置文件分发到其他机器上
scp -r spark-env.sh node02:$PWD
scp -r spark-defaults.conf node02:$PWD
scp -r spark-env.sh node03:$PWD
scp -r spark-defaults.conf node03:$PWD
#重启集群
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/sbin/stop-all.sh
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/sbin/start-all.sh
#在master上启动日志服务器 http://node01:4000/
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/sbin/start-history-server.sh
#关闭日志服务器
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/sbin/stop-history-server.sh
测试一: cluster模式
Spark On YARN的Cluster模式指的是Driver程序运行在YARN集群上, 应用的运行结果不能在客户端显示, 运行应用程序的main()函数并创建SparkContext的进程, 在企业生产环境中更多都是cluster部署模式运行Spark 应用
#使用spark-submit提交打成jar包的任务 http://node01:8088/cluster
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode cluster \
--executor-cores 2 \
--queue default \
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/jars/spark-examples_2.11-2.2.0.jar \
10
测试二: client模式(了解)
Spark On YARN的Client模式 指的是Driver程序运行在提交任务的客户端, 应用程序运行结果会在客户端显示, 学习测试时使用,开发不用,了解即可,可以略过
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode client \
--driver-memory 1g \
--executor-memory 1g \
--executor-cores 2 \
--queue default \
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/jars/spark-examples_2.11-2.2.0.jar \
10
(五)Spark命令详解(了解)
-
spark-shell是Spark自带的交互式Shell程序,方便用户进行交互式编程,用户可以在该命令行下可以用scala编写spark程序,适合学习测试时使用!
#spark-shell可以携带参数 spark-shell --master local[N] #数字N表示在本地模拟N个线程来运行当前任务 # *表示使用当前机器上所有可用的资源(默认不携带参数就是--master local[*]) spark-shell --master local[*] #如果搭建了HA, Master地址可以指定多个 spark-shell --master spark://node01:7077,node02:7077
-
spark-submit命令用来帮我们提交jar包给spark集群/YARN去执行, 实际开发中常用
案例: 蒙特·卡罗算法求圆周率PI
#提交到spark集群的node01和node02上的master, 要开启Spark的standalone-HA模式 /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/bin/spark-submit \ --class org.apache.spark.examples.SparkPi \ --master spark://node01:7077,node02:7077 \ --executor-memory 1g \ --total-executor-cores 2 \ /export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/examples/jars/spark-examples_2.11-2.2.0.jar \ 10
-
总结: Master的参数形式
Master形式 解释 local 本地以一个worker线程运行(例如非并行的情况). local[N] 本地以K worker 线程 (理想情况下, N设置为你机器的CPU核数). local[*] 本地以本机同样核数的线程运行. spark://HOST:PORT 连接到指定的Spark standalone cluster master的端口 7077. mesos://HOST:PORT 连接到指定的Mesos 集群, 默认端口 5050, 或使用ZK: mesos://zk://… yarn-client 以client模式连接到YARN cluster. yarn-cluster 以cluster模式连接到YARN cluster.
三、IDEA编写Spark程序
(一)pom.xml
创建Maven项目并补全目录、配置pom.xml
<!-- 指定仓库位置,依次为aliyun、cloudera和jboss仓库 -->
<repositories>
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>
<repository>
<id>jboss</id>
<url>http://repository.jboss.com/nexus/content/groups/public</url>
</repository>
</repositories>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<encoding>UTF-8</encoding>
<scala.version>2.11.8</scala.version>
<scala.compat.version>2.11</scala.compat.version>
<hadoop.version>2.7.4</hadoop.version>
<spark.version>2.2.0</spark.version>
</properties>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-hive_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-hive-thriftserver_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<!-- <dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-8_2.11</artifactId>
<version>${spark.version}</version>
</dependency>-->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql-kafka-0-10_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<!--<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.6.0-mr1-cdh5.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.2.0-cdh5.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.2.0-cdh5.14.0</version>
</dependency>-->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<!--<testSourceDirectory>src/test/scala</testSourceDirectory>-->
<plugins>
<!-- 指定编译java的插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
</plugin>
<!-- 指定编译scala的插件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
<configuration>
<useFile>false</useFile>
<disableXmlReport>true</disableXmlReport>
<includes>
<include>**/*Test.*</include>
<include>**/*Suite.*</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass></mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
(二)本地运行
//单词计数统计
object WordCount {
/**
* Author Devin Kim
* Date 2019/8/4 15:30
* Desc 演示使用Spark编写WordCount
*/
def main(args: Array[String]): Unit = {
//1.创建SparkContext
val config = new SparkConf().setAppName("wc").setMaster("local[*]")
val sc = new SparkContext(config)
sc.setLogLevel("WARN")
//2.读取文件
//A Resilient Distributed Dataset (RDD)弹性分布式数据集
//可以简单理解为分布式的集合,但是spark对它做了很多的封装,
//让程序员使用起来就像操作本地集合一样简单
val fileRDD: RDD[String] = sc.textFile(""D:\\1_my_study_work\\spark_day01\\words.txt"")
//3.处理数据
//3.1对每一行按空切分并压平形成一个新的集合中装的一个个的单词
//flatMap是对集合中的每一个元素进行操作,再进行压平
val wordRDD: RDD[String] = fileRDD.flatMap(_.split(" "))
//3.2每个单词记为1
val wordAndOneRDD: RDD[(String, Int)] = wordRDD.map((_,1))
//3.3根据key进行聚合,统计每个单词的数量
//wordAndOneRDD.reduceByKey((a,b)=>a+b)
//第一个_:之前累加的结果
//第二个_:当前进来的数据
val wordAndCount: RDD[(String, Int)] = wordAndOneRDD.reduceByKey(_+_)
//4.收集结果
val result: Array[(String, Int)] = wordAndCount.collect()
result.foreach(println)
sc.stop()
}
}
(三)集群运行
/**
* Desc 演示使用Spark编写WordCount
*/
object WordCount {
def main(args: Array[String]): Unit = {
//1.创建SparkContext
val config = new SparkConf().setAppName("wc")//.setMaster("local[*]") 等下使用spark-submit提交需要指定master地址
val sc = new SparkContext(config)
sc.setLogLevel("WARN")
//2.读取文件
//A Resilient Distributed Dataset (RDD)弹性分布式数据集
//可以简单理解为分布式的集合,但是spark对它做了很多的封装,
//让程序员使用起来就像操作本地集合一样简单,这样大家就很happy了
val fileRDD: RDD[String] = sc.textFile(args(0)) //文件输入路径
//3.处理数据
//3.1对每一行按空切分并压平形成一个新的集合中装的一个个的单词
//flatMap是对集合中的每一个元素进行操作,再进行压平
val wordRDD: RDD[String] = fileRDD.flatMap(_.split(" "))
//3.2每个单词记为1
val wordAndOneRDD: RDD[(String, Int)] = wordRDD.map((_,1))
//3.3根据key进行聚合,统计每个单词的数量
//wordAndOneRDD.reduceByKey((a,b)=>a+b)
//第一个_:之前累加的结果
//第二个_:当前进来的数据
val wordAndCount: RDD[(String, Int)] = wordAndOneRDD.reduceByKey(_+_)
wordAndCount.saveAsTextFile(args(1))//文件输出路径
//4.收集结果
//val result: Array[(String, Int)] = wordAndCount.collect()
//result.foreach(println)
}
}
打包后将jar包提交到集群上运行(集群不用启动)
/export/servers/spark-2.2.0-bin-2.6.0-cdh5.14.0/bin/spark-submit \
--class cn.baicdt.demo01.WordCount \
--master spark://node01:7077,node02:7077 \
--executor-memory 1g \
--total-executor-cores 2 \
/root/wc.jar \
hdfs://node01:8020/spark/day01/words.txt \
hdfs://node01:8020/spark/output6
(四)Java8版[了解]
public class WordCount_Java {
public static void main(String[] args){
SparkConf conf = new SparkConf().setAppName("wc").setMaster("local[*]");
JavaSparkContext jsc = new JavaSparkContext(conf);
JavaRDD<String> fileRDD = jsc.textFile("D:\\1_my_study_work\\spark_day01\\words.txt");
JavaRDD<String> wordRDD = fileRDD.flatMap(s -> Arrays.asList(s.split(" ")).iterator());
JavaPairRDD<String, Integer> wordAndOne = wordRDD.mapToPair(w -> new Tuple2<>(w, 1));
JavaPairRDD<String, Integer> wordAndCount = wordAndOne.reduceByKey((a, b) -> a + b);
//wordAndCount.collect().forEach(t->System.out.println(t));
wordAndCount.collect().forEach(System.out::println);
//函数式编程的核心思想:行为参数化!
}
}
补充:
① Java8中的函数式编程的语法, lambda表达式 (参数)->{函数体}
② Scala中函数的本质是对象
③ Java8中函数的本质可以理解为匿名内部类对象,即Java8中的函数本质也是对象
//案例
public class Test {
public static void main(String[] args){
new Thread(
new Runnable() {
@Override
public void run() {
System.out.println("java8");
}
}
).start();
//接下来使用Java8的lambda表达式(函数式编程)
new Thread(
()->System.out.println("java8")
).start();
}
}
四.案例:网络日志分析
import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
import org.apache.spark.{SparkConf, SparkContext}
//演示使用Spark统计网站常见运营指标
object WebLog_T {
def main(args: Array[String]): Unit = {
//1.创建SC
val conf: SparkConf = new SparkConf().setAppName("wc").setMaster("local[*]")
val sc: SparkContext = new SparkContext(conf)
sc.setLogLevel("WARN")
//2.加载数据
val fileRDD: RDD[String] = sc.textFile("D:\\1_my_study_work\\spark_day03\\access.log")
//3.处理数据每一行按空格切分
val linesRDD: RDD[Array[String]] = fileRDD.map(_.split(" "))
//频繁使用的RDD可以进行缓存或持久化
linesRDD.persist(StorageLevel.MEMORY_AND_DISK)
//设置CheckpointDir
sc.setCheckpointDir("./ckp")//实际开发中写HDFS
//将RDD进行Checkpoint
linesRDD.checkpoint()
/*===================================统计指标===================================*/
//4.1统计网站pv, page view 网站页面浏览量(访问一次算一次)
val pv: Long = linesRDD.count()
println("pv: " + pv)
val pvAndCount: RDD[(String, Int)] = linesRDD.map(line => ("pv",1)).reduceByKey(_+_)
pvAndCount.collect().foreach(println)
//4.2统计网站uv, user view 独立用户访问量(一个用户算一次访问,可以使用ip/Sessionid)
val ipRDD: RDD[String] = linesRDD.map(line => line(0))
val uv: Long = ipRDD.distinct().count()
println("uv: " + uv)
//4.3统计网站用户来源topN, 即统计refurl,表示来自于哪里?
val filteredLines: RDD[Array[String]] = linesRDD.filter(_.length > 10)//数据预处理
val refurlRDD: RDD[String] = filteredLines.map(_(10))//取出refUrl
val refurlAndOne: RDD[(String, Int)] = refurlRDD.map((_,1))//每个refurl记为1
val refurlAndCount: RDD[(String, Int)] = refurlAndOne.reduceByKey(_+_)//按照key聚合
/* val top5: Array[(String, Int)] = refurlAndCount.top(5)
注意:top默认按照key排序,我们需要按照value次数排序
并且top是把数据拉回Driver再排序 */
val result = refurlAndCount.sortBy(_._2,false)//按照次数降序/逆序
val top5: Array[(String, Int)] = result.take(5)
top5.foreach(println)
sc.stop()
}
}