新手福利:Apache Spark 入门攻略

【编者按】时至今日,Spark 已成为大数据领域最火的一个开源项目,具备高性能、易于使用等特性。然而作为一个年轻的开源项目,其使用上存在的挑战亦不可为不大,这里为大家分享 SciSpike 软件架构师 Ashwini Kuntamukkala 在 Dzone 上进行的 Spark 入门总结(虽然有些地方基于的是 Spark 1.0 版本,但仍然值得阅读)—— Apache Spark:An Engine for Large-Scale Data Processing,由 OneAPM工程师翻译。

本文聚焦 Apache Spark 入门,了解其在大数据领域的地位,覆盖 Apache Spark 的安装及应用程序的建立,并解释一些常见的行为和操作。

##一、 为什么要使用 Apache Spark

时下,我们正处在一个“大数据”的时代,每时每刻,都有各种类型的数据被生产。而在此紫外,数据增幅的速度也在显著增加。从广义上看,这些数据包含交易数据、社交媒体内容(比如文本、图像和视频)以及传感器数据。那么,为什么要在这些内容上投入如此多精力,其原因无非就是从海量数据中提取洞见可以对生活和生产实践进行很好的指导。

在几年前,只有少部分公司拥有足够的技术力量和资金去储存和挖掘大量数据,并对其挖掘从而获得洞见。然而,被雅虎 2009 年开源的 Apache Hadoop 对这一状况产生了颠覆性的冲击——通过使用商用服务器组成的集群大幅度地降低了海量数据处理的门槛。因此,许多行业(比如 Health care、Infrastructure、Finance、Insurance、Telematics、Consumer、Retail、Marketing、E-commerce、Media、 Manufacturing 和 Entertainment)开始了 Hadoop 的征程,走上了海量数据提取价值的道路。着眼 Hadoop ,其主要提供了两个方面的功能:

  • 通过水平扩展商用主机,HDFS提供了一个廉价的方式对海量数据进行容错存储。
  • MapReduce 计算范例,提供了一个简单的编程模型来挖掘数据并获得洞见。

下图展示了 MapReduce 的数据处理流程,其中一个 Map-Reduce step 的输出将作为下一个典型 Hadoop job 的输入结果。

p1

在整个过程中,中间结果会借助磁盘传递,因此对比计算,大量的 Map-Reduced 作业都受限于 IO 。然而对于 ETL 、数据整合和清理这样的用例来说,IO 约束并不会产生很大的影响,因为这些场景对数据处理时间往往不会有较高的需求。然而,在现实世界中,同样存在许多对延时要求较为苛刻的用例,比如:

  1. 对流数据进行处理来做近实时分析。举个例子,通过分析点击流数据做视频推荐,从而提高用户的参与度。在这个用例中,开发者必须在精度和延时之间做平衡。
  2. 在大型数据集上进行交互式分析,数据科学家可以在数据集上做 ad-hoc 查询。

p2

毫无疑问,历经数年发展,Hadoop 生态圈中的丰富工具已深受用户喜爱,然而这里仍然存在众多问题给使用带来了挑战:

  1. 每个用例都需要多个不同的技术堆栈来支撑,在不同使用场景下,大量的解决方案往往捉襟见肘。

  2. 在生产环境中机构往往需要精通数门技术。

  3. 许多技术存在版本兼容性问题。

  4. 无法在并行 job 中更快地共享数据。

而通过 Apache Spark,上述问题迎刃而解!Apache Spark 是一个轻量级的内存集群计算平台,通过不同的组件来支撑批、流和交互式用例,如下图。

p3

##二、 关于 Apache Spark

Apache Spark 是个开源和兼容 Hadoop 的集群计算平台。由加州大学伯克利分校的 AMPLabs 开发,作为 Berkeley Data Analytics Stack(BDAS) 的一部分,当下由大数据公司 Databricks 保驾护航,更是 Apache 旗下的顶级项目,下图显示了 Apache Spark 堆栈中的不同组件。

p4

Apache Spark 的5大优势:

  1. 更高的性能,因为数据被加载到集群主机的分布式内存中。数据可以被快速的转换迭代,并缓存用以后续的频繁访问需求。很多对 Spark 感兴趣的朋友可能也会听过这样一句话——在数据全部加载到内存的情况下, Spark 可以比 Hadoop 快 100 倍,在内存不够存放所有数据的情况下快 Hadoop 10 倍。

  2. 通过建立在 Java、Scala、Python、SQL (应对交互式查询)的标准 API 以方便各行各业使用,同时还含有大量开箱即用的机器学习库。 p5

  3. 与现有 Hadoop v1 ( SIMR ) 和 2.x (YARN) 生态兼容,因此机构可以进行无缝迁移。 p6

  4. 方便下载和安装。方便的 shell(REPL: Read-Eval-Print-Loop)可以对 API 进行交互式的学习。

  5. 借助高等级的架构提高生产力,从而可以讲精力放到计算上。

同时, Apache Spark 由 Scala 实现,代码非常简洁。

##三、安装Apache Spark

下表列出了一些重要链接和先决条件:

Current Release1.0.1 @http://d3kbcqa49mib13.cloudfront.net/spark-1.0.1.tgz
Downloads Pagehttps://spark.apache.org/downloads.html
JDK Version (Required)1.6 or higher
Scala Version (Required)2.10 or higher
Python (Optional)[2.6, 3.0)
Simple Build Tool (Required)http://www.scala-sbt.org
Development Versiongit clone git://github.com/apache/spark.git
Building Instructionshttps://spark.apache.org/docs/latest/building-with-maven.html
Maven3.0 or higher

如上图所示,Apache Spark 的部署方式包括 standalone、Hadoop V1 SIMR、Hadoop 2 YARN/Mesos 。Apache Spark 需求一定的 Java、Scala 或 Python 知识。这里,我们将专注 standalone 配置下的安装和运行。

  1. 安装 JDK 1.6+、Scala 2.10+、Python [2.6,3] 和 sbt

  2. 下载 Apache Spark 1.0.1 Release

  3. 在指定目录下 Untar 和 Unzip spark-1.0.1.tgz

     akuntamukkala@localhost~/Downloads$ pwd /Users/akuntamukkala/Downloads akuntamukkala@localhost~/Downloads$ tar -zxvf spark- 1.0.1.tgz -C /Users/akuntamukkala/spark
    
  4. 运行 sbt 建立 Apache Spark

     akuntamukkala@localhost~/spark/spark-1.0.1$ pwd /Users/akuntamukkala/spark/spark-1.0.1 akuntamukkala@localhost~/spark/spark-1.0.1$ sbt/sbt assembly
    
  5. 发布 Scala 的 Apache Spark standalone REPL

     /Users/akuntamukkala/spark/spark-1.0.1/bin/spark-shell
    

如果是 Python

    /Users/akuntamukkala/spark/spark-1.0.1/bin/ pyspark
  1. 查看 SparkUI @ http://localhost:4040

##四、Apache Spark 的工作模式

Spark 引擎提供了在集群中所有主机上进行分布式内存数据处理的能力,下图显示了一个典型 Spark job 的处理流程。 p8

下图显示了 Apache Spark 如何在集群中执行一个作业。 p9

Master 控制数据如何被分割,利用了数据本地性,并在 Slaves 上跟踪所有分布式计算。在某个Slave不可用时,其存储的数据会分配给其他可用的 Slaves 。虽然当下( 1.0.1 版本) Master 还存在单点故障,但后期必然会被修复。

##五、弹性分布式数据集(Resilient Distributed Dataset,RDD)

弹性分布式数据集(RDD,从 Spark 1.3 版本开始已被 DataFrame 替代)是 Apache Spark 的核心理念。它是由数据组成的不可变分布式集合,其主要进行两个操作:transformation 和 action 。Transformation 是类似在 RDD 上做 filter()、map() 或 union() 以生成另一个 RDD 的操作,而 action 则是 count()、first()、take(n)、collect() 等促发一个计算并返回值到 Master 或者稳定存储系统的操作。Transformations 一般都是 lazy 的,直到 action 执行后才会被执行。Spark Master/Driver 会保存 RDD 上的 Transformations 。这样一来,如果某个 RDD 丢失(也就是 salves 宕掉),它可以快速和便捷地转换到集群中存活的主机上。这也就是 RDD 的弹性所在。

下图展示了 Transformation 的 lazy : p10

我们可以通过下面示例来理解这个概念:从文本中发现 5 个最常用的 word 。下图显示了一个可能的解决方案。 p11

在上面命令中,我们对文本进行读取并且建立字符串的 RDD 。每个条目代表了文本中的 1 行。

scala> val hamlet = sc.textFile(“/Users/akuntamukkala/temp/gutenburg.txt”)
hamlet: org.apache.spark.rdd.RDD[String] = MappedRDD[1] at textFile at <console>:12

scala> val topWordCount = hamlet.flatMap(str=>str.split(“ “)). filter(!_.isEmpty).map(word=>(word,1)).reduceByKey(_+_).map{case (word, count) => (count, word)}.sortByKey(false)
topWordCount: org.apache.spark.rdd.RDD[(Int, String)] = MapPartitionsRDD[10] at sortByKey at <console>:14
  1. 通过上述命令我们可以发现这个操作非常简单——通过简单的 Scala API 来连接 transformations 和 actions 。

  2. 可能存在某些 words 被 1 个以上空格分隔的情况,导致有些 words 是空字符串,因此需要使用 filter(!_.isEmpty) 将它们过滤掉。

  3. 每个 word 都被映射成一个键值对:map(word=>(word,1))。

  4. 为了合计所有计数,这里需要调用一个 reduce 步骤—— reduceByKey(+) 。 + 可以非常便捷地为每个 key 赋值。

  5. 我们得到了 words 以及各自的 counts,下一步需要做的是根据 counts 排序。在 Apache Spark ,用户只能根据 key 排序,而不是值。因此,这里需要使用 map{case (word, count) => (count, word)} 将 (word, count) 流转到 (count, word)。

  6. 需要计算最常用的 5 个 words ,因此需要使用 sortByKey(false) 做一个计数的递减排序。

上述命令包含了一个 .take(5) (an action operation, which triggers computation) 和在 /Users/akuntamukkala/temp/gutenburg.txt 文本中输出 10 个最常用的 words 。在 Python shell 中用户可以实现同样的功能。

RDD lineage 可以通过 toDebugString (一个值得记住的操作)来跟踪。

scala> topWordCount.take(5).foreach(x=>println(x)) (1044,the) (730,and) (679,of) (648,to) (511,I)

常用的 Transformations:

Transformation & PurposeExample & Result
filter(func) Purpose: new RDD by selecting those data elements on which func returns truescala> val rdd = sc.parallelize(List(“ABC”,”BCD”,”DEF”)) scala> val filtered = rdd.filter(_.contains(“C”)) scala> filtered.collect() Result:<br> Array[String] = Array(ABC, BCD)
map(func) Purpose: return new RDD by applying func on each data elementscala> val rdd=sc.parallelize(List(1,2,3,4,5)) scala> val times2 = rdd.map(_*2) scala> times2.collect() Result:<br> Array[Int] = Array(2, 4, 6, 8, 10)
flatMap(func) Purpose: Similar to map but func returns a Seq instead of a value. For example, mapping a sentence into a Seq of wordsscala> val rdd=sc.parallelize(List(“Spark is awesome”,”It is fun”)) <br> scala> val fm=rdd.flatMap(str=>str.split(“ “)) scala> fm.collect() Result:<br> Array[String] = Array(Spark, is, awesome, It, is, fun)
reduceByKey(func,[numTasks]) Purpose: To aggregate values of a key using a function. “numTasks” is an optional parameter to specify number of reduce tasks scala> val word1=fm.map(word=>(word,1))scala> val wrdCnt=word1.reduceByKey(+) scala> wrdCnt.collect() <br>Result: <br>Array[(String, Int)] = Array((is,2), (It,1), (awesome,1), (Spark,1), (fun,1))
groupByKey([numTasks]) Purpose: To convert (K,V) to (K,Iterable<V>)scala> val cntWrd = wrdCnt.map { case ( word, count ) => (count, word ) } scala> cntWrd.groupByKey().collect() <br>Result:<br>Array[(Int, Iterable[String])] = Array((1,ArrayBuffer(It, awesome, Spark, fun)), (2,ArrayBuffer(is)))
distinct([numTasks]) Purpose: Eliminate duplicates from RDDscala> fm.distinct().collect()<br> Result:<br> Array[String] = Array(is, It, awesome, Spark, fun)

常用的集合操作:

Transformation and PurposeExample and Result
union() Purpose: new RDD containing all elements from source RDD and argument.Scala> val rdd1=sc.parallelize(List(‘A’,’B’)) scala> val rdd2=sc.parallelize(List(‘B’,’C’)) scala> rdd1.union(rdd2).collect() <br>Result: Array[Char] = Array(A, B, B, C)
intersection() Purpose: new RDD containing only common elements from source RDD and argument.Scala> rdd1.intersection(rdd2).collect() <br>Result: Array[Char] = Array(B)
cartesian() Purpose: new RDD cross product of all elements from source RDD and argumentScala> rdd1.cartesian(rdd2).collect()<br>Result: Array[(Char, Char)] = Array((A,B), (A,C), (B,B), (B,C))
subtract() Purpose: new RDD created by removing data elements in source RDD in common with argumentscala> rdd1.subtract(rdd2).collect() <br>Result: Array[Char] = Array(A)
join(RDD,[numTasks]) Purpose: When invoked on (K,V) and (K,W), this operation creates a new RDD of (K, (V,W))scala> val personFruit = sc.parallelize(Seq((“Andy”, “Apple”), (“Bob”, “Banana”), (“Charlie”, “Cherry”), (“Andy”,”Apricot”))) scala> val personSE = sc.parallelize(Seq((“Andy”, “Google”), (“Bob”, “Bing”), (“Charlie”, “Yahoo”), (“Bob”,”AltaVista”))) scala> personFruit.join(personSE).collect()<br> Result:<br>Array[(String, (String, String))] = Array((Andy,(Apple,Google)), (Andy,(Apricot,Google)), (Charlie,(Cherry,Yahoo)), (Bob,(Banana,Bing)), (Bob,(Banana,AltaVista)))
cogroup(RDD,[numTasks])Purpose: To convert (K,V) to (K,Iterable<V>)scala> personFruit.cogroup(personSe).collect() Result:<br> Array[(String, (Iterable[String], Iterable[String]))] = Array((Andy,(ArrayBuffer(Apple, Apricot),ArrayBuffer(google))), (Charlie,(ArrayBuffer(Cherry),ArrayBuffer(Yahoo))), (Bob,(ArrayBuffer(Banana),ArrayBuffer(Bing, AltaVista))))

更多 transformations 信息,请查看 http://spark.apache.org/docs/latest/programming-guide.html#transformations

常用的 actions

Action & PurposeExample & Result
count() Purpose: get the number of data elements in the RDDscala> val rdd = sc.parallelize(list(‘A’,’B’,’c’)) scala> rdd.count() <br>Result:<br> long = 3
collect()Purpose: get all the data elements in an RDD as an arrayscala> val rdd = sc.parallelize(list(‘A’,’B’,’c’)) scala> rdd.collect() <br>Result: Array[char] = Array(A, B, c)
reduce(func) Purpose: Aggregate the data elements in an RDD using this function which takes two arguments and returns onescala> val rdd = sc.parallelize(list(1,2,3,4)) scala> rdd.reduce(+) <br>Result: Int = 10
take (n) Purpose: fetch first n data elements in an RDD. computed by driver program.Scala> val rdd = sc.parallelize(list(1,2,3,4)) scala> rdd.take(2)<br> Result: Array[Int] = Array(1, 2)
foreach(func) Purpose: execute function for each data element in RDD. usually used to update an accumulator(discussed later) or interacting with external systems.Scala> val rdd = sc.parallelize(list(1,2,3,4)) scala> rdd.foreach(x=>println(“%s10=%s”. format(x,x10))) Result:<br>110=10 410=40 310=30 210=20
first() Purpose: retrieves the first data element in RDD.Similar to take(1) scala> val rdd = sc.parallelize(list(1,2,3,4)) scala> rdd.first() Result: Int = 1
saveAsTextFile(path) Purpose: Writes the content of RDD to a text file or a set of text files to local file system/ HDFSscala> val hamlet = sc.textFile(“/users/akuntamukkala/ temp/gutenburg.txt”) scala>hamlet.filter(_.contains(“Shakespeare”)). saveAsTextFile(“/users/akuntamukkala/temp/ filtered”) <br>Result: akuntamukkala@localhost~/temp/filtered$ ls _SUCCESS part-00000 part-00001

更多 actions 参见 http://spark.apache.org/docs/latest/programming-guide.html#actions

##六、RDD持久性

Apache Spark 中一个主要的能力就是在集群内存中持久化/缓存 RDD 。这将显著地提升交互速度。下表显示了 Spark 中各种选项。

Storage Level |Purpose| --|---| MEMORY_ONLY (Default level)| This option stores RDD in available cluster memory as deserialized Java objects. Some partitions may not be cached if there is not enough cluster memory. Those partitions will be recalculated on the fly as needed.| MEMORY_AND_DISK |This option stores RDD as deserialized Java objects. If RDD does not fit in cluster memory, then store those partitions on the disk and read them as needed.| MEMORY_ONLY_SER| This options stores RDD as serialized Java objects (One byte array per partition). This is more CPU intensive but saves memory as it is more space efficient. Some partitions may not be cached. Those will be recalculated on the fly as needed.| MEMORY_ONLY_DISK_SER |This option is same as above except that disk is used when memory is not sufficient.| DISC_ONLY |This option stores the RDD only on the disk| MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc. |Same as other levels but partitions are replicated on 2 slave nodes|

上面的存储等级可以通过 RDD. cache() 操作上的 persist () 操作访问,可以方便地指定 MEMORY_ONLY 选项。关于持久化等级的更多信息,可以访问这里 http://spark.apache.org/docs/latest/programming-guide.html#rdd-persistence。

Spark 使用 Least Recently Used (LRU) 算法来移除缓存中旧的、不常用的 RDD ,从而释放出更多可用内存。同样还提供了一个 unpersist() 操作来强制移除缓存/持久化的 RDD 。

##七、变量共享

Accumulators。Spark 提供了一个非常便捷地途径来避免可变的计数器和计数器同步问题—— Accumulators 。Accumulators 在一个 Spark context 中通过默认值初始化,这些计数器在 Slaves 节点上可用,但是 Slaves 节点不能对其进行读取。它们的作用就是来获取原子更新,并将其转发到 Master 。 Master 是唯一可以读取和计算所有更新合集的节点。举个例子:

akuntamukkala@localhost~/temp$ cat output.log error warning info trace error info info scala> val nErrors=sc.accumulator(0.0) scala> val logs = sc.textFile(“/Users/akuntamukkala/temp/output.log”) scala> logs.filter(_.contains(“error”)).foreach(x=>nErrors+=1) scala> nErrors.value Result:Int = 2

Broadcast Variables。实际生产中,通过指定 key 在 RDDs 上对数据进行合并的场景非常常见。在这种情况下,很可能会出现给 slave nodes 发送大体积数据集的情况,让其负责托管需要做 join 的数据。因此,这里很可能存在巨大的性能瓶颈,因为网络 IO 比内存访问速度慢 100 倍。为了解决这个问题,Spark 提供了 Broadcast Variables,如其名称一样,它会向 slave nodes 进行广播。因此,节点上的 RDD 操作可以快速访问 Broadcast Variables 值。举个例子,期望计算一个文件中所有路线项的运输成本。通过一个 look-up table指定每种运输类型的成本,这个look-up table 就可以作为 Broadcast Variables 。

akuntamukkala@localhost~/temp$ cat packagesToShip.txt ground express media priority priority ground express media scala> val map = sc.parallelize(Seq((“ground”,1),(“med”,2), (“priority”,5),(“express”,10))).collect().toMap map: scala.collection.immutable.Map[String,Int] = Map(ground -> 1, media -> 2, priority -> 5, express -> 10) scala> val bcMailRates = sc.broadcast(map)

上述命令中,我们建立了一个 broadcast variable,基于服务类别成本的 map 。

scala> val pts = sc.textFile(“/Users/akuntamukkala/temp/packagesToShip.txt”)

在上述命令中,我们通过 broadcast variable 的 mailing rates 来计算运输成本。

scala> pts.map(shipType=>(shipType,1)).reduceByKey(+). map{case (shipType,nPackages)=>(shipType,nPackages*bcMailRates. value(shipType))}.collect()

通过上述命令,我们使用 accumulator 来累加所有运输的成本。详细信息可通过下面的 PDF 查看 http://ampcamp.berkeley.edu/wp-content/uploads/2012/06/matei-zaharia-amp-camp-2012-advanced-spark.pdf。

##八、Spark SQL

通过 Spark Engine,Spark SQL 提供了一个便捷的途径来进行交互式分析,使用一个被称为 SchemaRDD 类型的 RDD 。SchemaRDD 可以通过已有 RDDs 建立,或者其他外部数据格式,比如 Parquet files、JSON 数据,或者在 Hive 上运行 HQL。SchemaRDD 非常类似于 RDBMS 中的表格。一旦数据被导入 SchemaRDD,Spark 引擎就可以对它进行批或流处理。Spark SQL 提供了两种类型的 Contexts——SQLContext 和 HiveContext,扩展了 SparkContext 的功能。

SparkContext 提供了到简单 SQL parser 的访问,而 HiveContext 则提供了到 HiveQL parser 的访问。HiveContext 允许企业利用已有的 Hive 基础设施。

这里看一个简单的 SQLContext 示例。

下面文本中的用户数据通过 “ | ” 来分割。

John Smith|38|M|201 East Heading Way #2203,Irving, TX,75063 Liana Dole|22|F|1023 West Feeder Rd, Plano,TX,75093 Craig Wolf|34|M|75942 Border Trail,Fort Worth,TX,75108 John Ledger|28|M|203 Galaxy Way,Paris, TX,75461 Joe Graham|40|M|5023 Silicon Rd,London,TX,76854

定义 Scala case class 来表示每一行:

case class Customer(name:String,age:Int,gender:String,address: String)

下面的代码片段体现了如何使用 SparkContext 来建立 SQLContext ,读取输入文件,将每一行都转换成 SparkContext 中的一条记录,并通过简单的 SQL 语句来查询 30 岁以下的男性用户。

val sparkConf = new SparkConf().setAppName(“Customers”)
val sc = new SparkContext(sparkConf)
val sqlContext = new SQLContext(sc)
val r = sc.textFile(“/Users/akuntamukkala/temp/customers.txt”) val records = r.map(_.split(‘|’))
val c = records.map(r=>Customer(r(0),r(1).trim.toInt,r(2),r(3))) c.registerAsTable(“customers”)
sqlContext.sql(“select * from customers where gender=’M’ and age <
            30”).collect().foreach(println) Result:[John Ledger,28,M,203 Galaxy Way,Paris,
            TX,75461]

更多使用 SQL 和 HiveQL 的示例请访问下面链接 https://spark.apache.org/docs/latest/sql-programming-guide.html、https://databricks-training.s3.amazonaws.com/data-exploration-using-spark-sql.html。

p11

##九、Spark Streaming

Spark Streaming 提供了一个可扩展、容错、高效的途径来处理流数据,同时还利用了 Spark 的简易编程模型。从真正意义上讲,Spark Streaming 会将流数据转换成 micro batches,从而将 Spark 批处理编程模型应用到流用例中。这种统一的编程模型让 Spark 可以很好地整合批量处理和交互式流分析。下图显示了 Spark Streaming 可以从不同数据源中读取数据进行分析。 p12

Spark Streaming 中的核心抽象是 Discretized Stream(DStream)。DStream 由一组 RDD 组成,每个 RDD 都包含了规定时间(可配置)流入的数据。上图很好地展示了 Spark Streaming 如何通过将流入数据转换成一系列的 RDDs,再转换成 DStream 。每个 RDD 都包含两秒(设定的区间长度)的数据。在 Spark Streaming 中,最小长度可以设置为 0.5 秒,因此处理延时可以达到 1 秒以下。

Spark Streaming 同样提供了 window operators ,它有助于更有效率在一组 RDD ( a rolling window of time)上进行计算。同时,DStream 还提供了一个 API ,其操作符(transformations 和 output operators)可以帮助用户直接操作 RDD 。下面不妨看向包含在 Spark Streaming 下载中的一个简单示例。示例是在 Twitter 流中找出趋势 hashtags ,详见下面代码。

spark-1.0.1/examples/src/main/scala/org/apache/spark/examples/streaming/TwitterPopularTags.scala
val sparkConf = new SparkConf().setAppName(“TwitterPopularTags”)
val ssc = new StreamingContext(sparkConf, Seconds(2))
val stream = TwitterUtils.createStream(ssc, None, filters)

上述代码用于建立 Spark Streaming Context 。Spark Streaming 将在 DStream 中建立一个 RDD ,包含了每 2 秒流入的 tweets 。

val hashTags = stream.flatMap(status => status.getText.split(“ “).filter(_.startsWith(“#”)))

上述代码片段将 Tweet 转换成一组 words ,并过滤出所有以 a# 开头的。

val topCounts60 = hashTags.map((_, 1)).reduceByKeyAndWindow(_ + _, Seconds(60)).map{case (topic, count) => (count, topic)}. transform(_.sortByKey(false))

上述代码展示了如何整合计算 60 秒内一个 hashtag 流入的总次数。

topCounts60.foreachRDD(rdd => {
val topList = rdd.take(10)
println(“\nPopular topics in last 60 seconds (%s
total):”.format(rdd.count())) topList.foreach{case (count, tag) => println(“%s (%s
tweets)”.format(tag, count))} })

上面代码将找出 top 10 趋势 tweets ,然后将其打印。

ssc.start()

上述代码让 Spark Streaming Context 开始检索 tweets 。一起聚焦一些常用操作,假设我们正在从一个 socket 中读入流文本。

al lines = ssc.socketTextStream(“localhost”, 9999, StorageLevel.MEMORY_AND_DISK_SER)

p14 更多 operators 请访问 http://spark.apache.org/docs/latest/streaming-programming-guide.html#transformations

Spark Streaming 拥有大量强大的 output operators ,比如上文提到的 foreachRDD(),了解更多可访问 http://spark.apache.org/docs/latest/streaming-programming-guide.html#output-operations。

##十、附加学习资源

原文链接:Apache Spark:An Engine for Large-Scale Data Processing

本文系 OneAPM 工程师编译整理。OneAPM 是中国基础软件领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客

转载于:https://my.oschina.net/oneapmofficial/blog/478646

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值