Spark 快速入门
来源:互联网 发布:windows解压war包 编辑:程序博客网 时间:2024/06/06 21:43
Apache Spark是新兴的一种快速通用的大规模数据处理引擎。它的优势有三个方面:
- 通用计算引擎 能够运行MapReduce、数据挖掘、图运算、流式计算、SQL等多种框架;
- 基于内存 数据可缓存在内存中,特别适用于需要迭代多次运算的场景;
- 与Hadoop集成 能够直接读写HDFS中的数据,并能运行在YARN之上。
Spark是用Scala语言编写的,所提供的API也很好地利用了这门语言的特性。它也可以使用Java和Python编写应用。本文将用Scala进行讲解。
安装Spark和SBT
- 从官网上下载编译好的压缩包,解压到一个文件夹中。下载时需注意对应的Hadoop版本,如要读写CDH4 HDFS中的数据,则应下载Pre-built for CDH4这个版本。
- 为了方便起见,可以将spark/bin添加到$PATH环境变量中:
export SPARK_HOME=/path/to/sparkexport PATH=$PATH:$SPARK_HOME/bin
- 在练习例子时,我们还会用到SBT这个工具,它是用来编译打包Scala项目的。Linux下的安装过程比较简单:
- 下载sbt-launch.jar到$HOME/bin目录;
- 新建$HOME/bin/sbt文件,权限设置为755,内容如下:
SBT_OPTS="-Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M"java $SBT_OPTS -jar `dirname $0`/sbt-launch.jar "$@"
日志分析示例
假设我们有如下格式的日志文件,保存在/tmp/logs.txt文件中:
2014-12-11 18:33:52 INFO Java some message2014-12-11 18:34:33 INFO MySQL some message2014-12-11 18:34:54 WARN Java some message2014-12-11 18:35:25 WARN Nginx some message2014-12-11 18:36:09 INFO Java some message
每条记录有四个字段,即时间、级别、应用、信息,使用制表符分隔。
Spark提供了一个交互式的命令行工具,可以直接执行Spark查询:
$ spark-shellWelcome to ____ __ / __/__ ___ _____/ /__ _\ \/ _ \/ _ `/ __/ '_/ /___/ .__/\_,_/_/ /_/\_\ version 1.1.0 /_/Spark context available as sc.scala>
加载并预览数据
scala> val lines = sc.textFile("/tmp/logs.txt")lines: org.apache.spark.rdd.RDD[String] = /tmp/logs.txt MappedRDD[1] at textFile at <console>:12scala> lines.first()res0: String = 2014-12-11 18:33:52 INFO Java some message
- sc是一个SparkContext类型的变量,可以认为是Spark的入口,这个对象在spark-shell中已经自动创建了。
- sc.textFile()用于生成一个RDD,并声明该RDD指向的是/tmp/logs.txt文件。RDD可以暂时认为是一个列表,列表中的元素是一行行日志(因此是String类型)。这里的路径也可以是HDFS上的文件,如hdfs://127.0.0.1:8020/user/hadoop/logs.txt。
- lines.first()表示调用RDD提供的一个方法:first(),返回第一行数据。
解析日志
为了能对日志进行筛选,如只处理级别为ERROR的日志,我们需要将每行日志按制表符进行分割:
scala> val logs = lines.map(line => line.split("\t"))logs: org.apache.spark.rdd.RDD[Array[String]] = MappedRDD[2] at map at <console>:14scala> logs.first()res1: Array[String] = Array(2014-12-11 18:33:52, INFO, Java, some message)
- lines.map(f)表示对RDD中的每一个元素使用f函数来处理,并返回一个新的RDD。
- line => line.split(“\t”)是一个匿名函数,又称为Lambda表达式、闭包等。它的作用和普通的函数是一样的,如这个匿名函数的参数是line(String类型),返回值是Array数组类型,因为String.split()函数返回的是数组。
- 同样使用first()方法来看这个RDD的首条记录,可以发现日志已经被拆分成四个元素了。
过滤并计数
我们想要统计错误日志的数量:
scala> val errors = logs.filter(log => log(1) == "ERROR")errors: org.apache.spark.rdd.RDD[Array[String]] = FilteredRDD[3] at filter at <console>:16scala> errors.first()res2: Array[String] = Array(2014-12-11 18:39:42, ERROR, Java, some message)scala> errors.count()res3: Long = 158
- logs.filter(f)表示筛选出满足函数f的记录,其中函数f需要返回一个布尔值。
- log(1) == “ERROR”表示获取每行日志的第二个元素(即日志级别),并判断是否等于ERROR。
- errors.count()用于返回该RDD中的记录。
缓存
由于我们还会对错误日志做一些处理,为了加快速度,可以将错误日志缓存到内存中,从而省去解析和过滤的过程:
scala> errors.cache()
errors.cache()函数会告知Spark计算完成后将结果保存在内存中。所以说Spark是否缓存结果是需要用户手动触发的。在实际应用中,我们需要迭代处理的往往只是一部分数据,因此很适合放到内存里。
需要注意的是,cache函数并不会立刻执行缓存操作,事实上map、filter等函数都不会立刻执行,而是在用户执行了一些特定操作后才会触发,比如first、count、reduce等。这两类操作分别称为Transformations和Actions。
显示前10条记录
scala> val firstTenErrors = errors.take(10)firstTenErrors: Array[Array[String]] = Array(Array(2014-12-11 18:39:42, ERROR, Java, some message), Array(2014-12-11 18:40:23, ERROR, Nginx, some message), ...)scala> firstTenErrors.map(log => log.mkString("\t")).foreach(line => println(line))2014-12-11 18:39:42 ERROR Java some message2014-12-11 18:40:23 ERROR Nginx some message...
errors.take(n)方法可用于返回RDD前N条记录,它的返回值是一个数组。之后对firstTenErrors的处理使用的是Scala集合类库中的方法,如map、foreach,和RDD提供的接口基本一致。所以说用Scala编写Spark程序是最自然的。
按应用进行统计
我们想要知道错误日志中有几条Java、几条Nginx,这和常见的Wordcount思路是一样的。
scala> val apps = errors.map(log => (log(2), 1))apps: org.apache.spark.rdd.RDD[(String, Int)] = MappedRDD[15] at map at <console>:18scala> apps.first()res20: (String, Int) = (Java,1)scala> val counts = apps.reduceByKey((a, b) => a + b)counts: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[17] at reduceByKey at <console>:20scala> counts.foreach(t => println(t))(Java,58)(Nginx,53)(MySQL,47)
errors.map(log => (log(2), 1))用于将每条日志转换为键值对,键是应用(Java、Nginx等),值是1,如("Java", 1)
,这种数据结构在Scala中称为元组(Tuple),这里它有两个元素,因此称为二元组。
对于数据类型是二元组的RDD,Spark提供了额外的方法,reduceByKey(f)就是其中之一。它的作用是按键进行分组,然后对同一个键下的所有值使用f函数进行归约(reduce)。归约的过程是:使用列表中第一、第二个元素进行计算,然后用结果和第三元素进行计算,直至列表耗尽。如:
scala> Array(1, 2, 3, 4).reduce((a, b) => a + b)res23: Int = 10
上述代码的计算过程即((1 + 2) + 3) + 4
。
counts.foreach(f)表示遍历RDD中的每条记录,并应用f函数。这里的f函数是一条打印语句(println)。
打包应用程序
为了让我们的日志分析程序能够在集群上运行,我们需要创建一个Scala项目。项目的大致结构是:
spark-sandbox├── build.sbt├── project│ ├── build.properties│ └── plugins.sbt└── src └── main └── scala └── LogMining.scala
你可以直接使用这个项目作为模板。下面说明一些关键部分:
配置依赖
build.sbt
libraryDependencies += "org.apache.spark" %% "spark-core" % "1.1.1"
程序内容
src/main/scala/LogMining.scala
import org.apache.spark.SparkContextimport org.apache.spark.SparkContext._import org.apache.spark.SparkConfobject LogMining extends App { val conf = new SparkConf().setAppName("LogMining") val sc = new SparkContext(conf) val inputFile = args(0) val lines = sc.textFile(inputFile) // 解析日志 val logs = lines.map(_.split("\t")) val errors = logs.filter(_(1) == "ERROR") // 缓存错误日志 errors.cache() // 统计错误日志记录数 println(errors.count()) // 获取前10条MySQL的错误日志 val mysqlErrors = errors.filter(_(2) == "MySQL") mysqlErrors.take(10).map(_ mkString "\t").foreach(println) // 统计每个应用的错误日志数 val errorApps = errors.map(_(2) -> 1) errorApps.countByKey().foreach(println)}
打包运行
$ cd spark-sandbox$ sbt package$ spark-submit --class LogMining --master local target/scala-2.10/spark-sandbox_2.10-0.1.0.jar data/logs.txt
参考资料
- Spark Programming Guide
- Introduction to Spark Developer Training
- Spark Runtime Internals
- Spark快速入门指南
- Spark快速入门指南
- Spark快速入门
- Spark快速入门指南
- spark快速入门
- spark快速入门
- Spark快速入门指南
- Spark快速入门指南
- Spark快速入门指南
- spark快速入门
- spark快速入门
- Spark 快速入门
- Spark基本概念快速入门
- Spark基本概念快速入门
- spark快速入门
- Apache Spark - Quick Start (Spark 快速入门)
- Spark快速入门指南(Quick Start Spark)
- Spark快速入门指南(Quick Start Spark)
- 810A. Straight «A»
- 河南省多校联萌(四)
- 【剖析tinyhttpd源码】
- 排序--快速排序
- ATP 和淡水工厂(factory)
- Spark 快速入门
- HDU
- 河南省多校联萌(四)
- Windows常用命令
- (poj 2449 Remmarguts' Date)<A*-K短路模板>
- HDU
- HDU
- SPSS--独立样本t检验与配对样本t检验
- C#整体总结