sparkstreaming之实时数据流计算实例

来源:互联网 发布:电脑版淘宝是哪个版本 编辑:程序博客网 时间:2024/05/16 08:02

最近在用sparkstreaming的技术来实现公司实时号码热度排序,学习了一下sparkstreaming的相关技术,今天主要要讲一个简单sparkstreaming实时数据流技术的一个示例,帮助大家更好的理解和学习sparkstreaming编程原理。

在开始实例之前我们简单的了解一下sparkstreaming的原理:具体参见:http://m635674608.iteye.com/blog/2248368

Spark Streaming 是Spark核心API的一个扩展,可以实现高吞吐量的、具备容错机制的实时流数据的处理。支持从多种数据源获取数据,包括Kafk、Flume、Twitter、ZeroMQ、Kinesis 以及TCP sockets,从数据源获取数据之后,可以使用诸如map、reduce、join和window等高级函数进行复杂算法的处理。最后还可以将处理结果存储到文件系统,数据库和现场仪表盘。在“One Stack rule them all”的基础上,还可以使用Spark的其他子框架,如集群学习、图计算等,对流数据进行处理。

Spark Streaming处理的数据流图:

Spark的各个子框架,都是基于核心Spark的,Spark Streaming在内部的处理机制是,接收实时流的数据,并根据一定的时间间隔拆分成一批批的数据,然后通过Spark Engine处理这些批数据,最终得到处理后的一批批结果数据。

对应的批数据,在Spark内核对应一个RDD实例,因此,对应流数据的DStream可以看成是一组RDDs,即RDD的一个序列。通俗点理解的话,在流数据分成一批一批后,通过一个先进先出的队列,然后 Spark Engine从该队列中依次取出一个个批数据,把批数据封装成一个RDD,然后进行处理,这是一个典型的生产者消费者模型,对应的就有生产者消费者模型的问题,即如何协调生产速率和消费速率。

Spark Streaming实时计算框架

    Spark是一个类似于MapReduce的分布式计算框架,其核心是弹性分布式数据集,提供了比MapReduce更丰富的模型,可以在快速在内存中对数据集进行多次迭代,以支持复杂的数据挖掘算法和图形计算算法。Spark Streaming是一种构建在Spark上的实时计算框架,它扩展了Spark处理大规模流式数据的能力。

Spark Streaming的优势在于:

  • 能运行在100+的结点上,并达到秒级延迟。
  • 使用基于内存的Spark作为执行引擎,具有高效和容错的特性。
  • 能集成Spark的批处理和交互查询。
  • 为实现复杂的算法提供和批处理类似的简单接口。

基于云梯Spark on Yarn的Spark Streaming总体架构如图1所示。其中Spark on Yarn的启动流程我的另外一篇文章(《程序员》2013年11月期刊《深入剖析阿里巴巴云梯Yarn集群》)有详细描述,这里不再赘述。Spark on Yarn启动后,由Spark AppMaster把Receiver作为一个Task提交给某一个Spark Executor;Receive启动后输入数据,生成数据块,然后通知Spark AppMaster;Spark AppMaster会根据数据块生成相应的Job,并把Job的Task提交给空闲Spark Executor 执行。图中蓝色的粗箭头显示被处理的数据流,输入数据流可以是磁盘、网络和HDFS等,输出可以是HDFS,数据库等。


图1 云梯Spark Streaming总体架构

Spark Streaming的基本原理是将输入数据流以时间片(秒级)为单位进行拆分,然后以类似批处理的方式处理每个时间片数据,其基本原理如图2所示。


图2 Spark Streaming基本原理图

首先,Spark Streaming把实时输入数据流以时间片Δt (如1秒)为单位切分成块。Spark Streaming会把每块数据作为一个RDD,并使用RDD操作处理每一小块数据。每个块都会生成一个Spark Job处理,最终结果也返回多块。

下面介绍Spark Streaming内部实现原理。

使用Spark Streaming编写的程序与编写Spark程序非常相似,在Spark程序中,主要通过操作RDD(Resilient Distributed Datasets弹性分布式数据集)提供的接口,如map、reduce、filter等,实现数据的批处理。而在Spark Streaming中,则通过操作DStream(表示数据流的RDD序列)提供的接口,这些接口和RDD提供的接口类似。图3和图4展示了由Spark Streaming程序到Spark jobs的转换图。


图3 Spark Streaming程序转换为DStream Graph


图4 DStream Graph转换为Spark jobs

在图3中,Spark Streaming把程序中对DStream的操作转换为DStream Graph,图4中,对于每个时间片,DStream Graph都会产生一个RDD Graph;针对每个输出操作(如print、foreach等),Spark Streaming都会创建一个Spark action;对于每个Spark action,Spark Streaming都会产生一个相应的Spark job,并交给JobManager。JobManager中维护着一个Jobs队列, Spark job存储在这个队列中,JobManager把Spark job提交给Spark Scheduler,Spark Scheduler负责调度Task到相应的Spark Executor上执行。

Spark Streaming的另一大优势在于其容错性,RDD会记住创建自己的操作,每一批输入数据都会在内存中备份,如果由于某个结点故障导致该结点上的数据丢失,这时可以通过备份的数据在其它结点上重算得到最终的结果。

正如Spark Streaming最初的目标一样,它通过丰富的API和基于内存的高速计算引擎让用户可以结合流式处理,批处理和交互查询等应用。因此Spark Streaming适合一些需要历史数据和实时数据结合分析的应用场合。当然,对于实时性要求不是特别高的应用也能完全胜任。另外通过RDD的数据重用机制可以得到更高效的容错处理。

了解了sparkstreaming的工作原理后,我们来开始我们的实时处理实例编程吧

首先我们要做一个日志生产器,方便本地模拟线上环境:

直接上代码吧(原理是根据一个原始日志log,然后随机的从中挑选行添加到新生产的日志中,并且生产的数据量呈不断的增长态势)

import java.io._import java.text.SimpleDateFormatimport org.apache.spark.{SparkConf, SparkContext}import java.util.Dateimport java.io.{PrintWriter}import scala.io.Sourceimport scala.util.matching.Regexobject FileGenerater {  def main(args: Array[String]) {    var i=0    while (i<100 )    {      val filename = args(0)      val lines = Source.fromFile(filename).getLines.toList      val filerow = lines.length      val writer = new PrintWriter(new File("/Users/mac/Documents/workspace/output/sparkstreamingtest"+i+".txt" ))      i=i+1      var j=0      while(j<i)      {        writer.write(lines(index(filerow)))        println(lines(index(filerow)))        j=j+1      }      writer.close()      Thread sleep 5000      log(getNowTime(),"/Users/mac/Documents/workspace/output/sparkstreamingtest"+i+".txt generated")    }  }  def log(date: String, message: String)  = {    println(date + "----" + message)  }  /**    * 从每行日志解析出imei和logid    *    **/  def index(length: Int) = {    import java.util.Random    val rdm = new Random    rdm.nextInt(length)  }  def getNowTime():String={    val now:Date = new Date()    val datetimeFormat:SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")    val ntime = datetimeFormat.format( now )    ntime  }  /**    * 根据时间字符串获取时间,单位(秒)    *    **/  def getTimeByString(timeString: String): Long = {    val sf: SimpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss")    sf.parse(timeString).getTime / 1000  }}
下面给出我们程序的configuration:

zhangfusheng.txt内容如下:

安徽宿州市汽车宿州分公司王红岩1895507920538浙江嘉兴市汽车海宁分公司金韩伟1530579315703安徽滁州市汽车滁州分公司严敏1538590614403湖北武汉市汽车湖北汽车服务分公司张晴1890292310870安徽淮北市汽车淮北分公司李亚1530550110484安徽滁州市汽车滁州分公司王旭东15305541210174安徽淮南市汽车淮南分公司尹芳18109643010085湖北省直辖行政单位汽车仙桃分公司汤黎1891705339638湖北null汽车潜江分公司朱疆振189966899479安徽宣城汽车宣城分公司李倩180982299381江苏徐州丰县分公司李萍189148050059340归属地安徽滁州市汽车滁州分公司阚家萍153047959180广东中山汽车服务中心农小萍180701019095归属地湖北孝感汽车孝感分公司黄燕平1899576288595归属地安徽芜湖null邹恒清180553490698537归属地江西null汽车江西分公司产品事业部(汽车服务分公司、互联网安全管理中心)张凯171188089安徽淮南市汽车淮南分公司李磊189577078039湖北省直辖行政单位汽车仙桃分公司朱艳1897703808025浙江温州汽车温州分公司(本部)吴玉春1530500107729归属地安徽淮北市汽车淮北分公司魏薇153052327533湖北省直辖行政单位汽车仙桃分公司王雪纯189720607405湖北宜昌市汽车宜昌分公司刘丽娟1890860057269湖北武汉市汽车湖北汽车服务分公司陶劲松1891827967209安徽淮北汽车合肥分公司刘洁1815611877108归属地湖北null宜昌电信公司鲜艳189086067000安徽淮北市汽车淮北分公司钱玉1056128416837湖北武汉市汽车湖北汽车服务分公司谢真华1871818336757安徽null马鞍山公司张颖1530965906710安徽芜湖市汽车芜湖分公司许丽丽1555353006694安徽合肥市汽车合肥分公司杨华丽153051686666安徽铜陵市汽车铜陵分公司黄琳1536292166665安徽马鞍山汽车马鞍山分公司林花133957266487贵州null汽车贵州分公司10000号运营中心陈宣宏1891013726421安徽合肥市汽车合肥分公司黄乐1530052666271安徽淮南市汽车淮南分公司赵乃艳1530583676263湖北武汉市汽车湖北汽车服务分公司蔡蕾1890769316218湖北null汽车潜江分公司陈晓辉189968986176安徽马鞍山市汽车马鞍山分公司陈凤153053656116安徽合肥市汽车合肥分公司李大燕180968196036
我先来观察一下运行结果:

最后我们就开始coding sparkstreaming的部分代码:(主要要添加scala-sdk-2.10.6和spark-assembly-1.6.2-hadoop2.6.0等jar包)

/**  * Created by mac on 16/8/12.  */import org.apache.spark.SparkConfimport org.apache.spark.streaming._;object SparkStreaming {  def main(args: Array[String]) {    //开本地线程两个处理,local[4]:意思本地起4个进程运行,setAppName("SparkStreaming"):设置运行处理类    val conf = new SparkConf().setMaster("local[4]").setAppName("SparkStreaming")    //每隔5秒计算一批数据local[4]:意思本地起4个进程运行,setAppName("SparkStreaming"):设置运行处理类    val ssc = new StreamingContext(conf, Seconds(5))    // 指定监控的目录    val lines = ssc.textFileStream("/Users/mac/Documents/workspace/output")    //按\t 切分输入数据    val words = lines.flatMap(_.split("\t"))    //计算wordcount    val pairs = words.map(word => (word, 1))    //word ++    val wordCounts = pairs.reduceByKey(_ + _)    //排序结果集打印,先转成rdd,然后排序true升序,false降序,可以指定key和value排序_._1是key,_._2是value    val sortResult = wordCounts.transform(rdd => rdd.sortBy(_._2, false))    sortResult.print()    ssc.start() // 开启计算    ssc.awaitTermination() // 阻塞等待计算  }}

结果:

从结果可以看出,sparkstreaming每次会将设置的时间分片以内发生的增量日志进行一次批量处理,最终输出这个增量处理的结果。

1 0
原创粉丝点击